Angular2: как загрузить данные перед рендерингом компонента?

Я пытаюсь загрузить событие из своего API до того, как компонент будет отрисован. В настоящее время я использую службу API, которую вызываю из функции компонента ngOnInit.

Мой EventRegister компонент:

import {Component, OnInit, ElementRef} from "angular2/core";
import {ApiService} from "../../services/api.service";
import {EventModel} from '../../models/EventModel';
import {Router, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, RouteConfig, RouteParams, RouterLink} from 'angular2/router';
import {FORM_PROVIDERS, FORM_DIRECTIVES, Control} from 'angular2/common';

@Component({
    selector: "register",
    templateUrl: "/events/register"
    // provider array is added in parent component
})

export class EventRegister implements OnInit {
    eventId: string;
    ev: EventModel;

    constructor(private _apiService: ApiService, 
                        params: RouteParams) {
        this.eventId = params.get('id');
    }

    fetchEvent(): void {
        this._apiService.get.event(this.eventId).then(event => {
            this.ev = event;
            console.log(event); // Has a value
            console.log(this.ev); // Has a value
        });
    }

    ngOnInit() {
        this.fetchEvent();
        console.log(this.ev); // Has NO value
    }
}

Мой EventRegister шаблон

<register>
    Hi this sentence is always visible, even if `ev property` is not loaded yet    
    <div *ngIf="ev">
        I should be visible as soon the `ev property` is loaded. Currently I am never shown.
        <span>{{event.id }}</span>
    </div>
</register>

Моя служба API

import "rxjs/Rx"
import {Http} from "angular2/http";
import {Injectable} from "angular2/core";
import {EventModel} from '../models/EventModel';

@Injectable()
export class ApiService {
    constructor(private http: Http) { }
    get = {
        event: (eventId: string): Promise<EventModel> => {
            return this.http.get("api/events/" + eventId).map(response => {
                return response.json(); // Has a value
            }).toPromise();
        }     
    }     
}

Компонент отображается до того, как вызов API в функции ngOnInit завершит выборку данных. Поэтому мне никогда не удается увидеть идентификатор события в моем шаблоне представления. Похоже, это проблема ASYNC. Я ожидал, что привязка ev (компонент EventRegister) выполнит некоторую работу после установки свойства ev. К сожалению, он не показывает div, помеченный *ngIf="ev", когда свойство установлено.

Вопрос: Я использую хороший подход? Если не; Как лучше всего загрузить данные до начала рендеринга компонента?

ПРИМЕЧАНИЕ. В этом angular2 используется подход ngOnInit учебник.

РЕДАКТИРОВАТЬ:

Два возможных решения. Сначала нужно было отказаться от fetchEvent и просто использовать службу API в функции ngOnInit.

ngOnInit() {
    this._apiService.get.event(this.eventId).then(event => this.ev = event);
}

Второе решение. Как и полученный ответ.

fetchEvent(): Promise<EventModel> {
    return this._apiService.get.event(this.eventId);
}

ngOnInit() {
    this.fetchEvent().then(event => this.ev = event);
}

person Tom Aalbers    schedule 26.02.2016    source источник
comment
auth gaurds также работает с этой целью   -  person Praveen Gubbala    schedule 27.12.2020
comment
Почему вы используете Http, а не HttpClient? Если вы используете HttpClient, вам не придется преобразовывать ответ в json, как это происходит автоматически.   -  person J.Doe    schedule 27.12.2020


Ответы (3)


обновить

оригинал

Когда console.log(this.ev) выполняется после this.fetchEvent();, это не означает, что вызов fetchEvent() выполнен, это означает только то, что он запланирован. Когда выполняется console.log(this.ev), вызов сервера даже не выполняется и, конечно же, еще не вернул значение.

Измените fetchEvent(), чтобы вернуть Promise

     fetchEvent(){
        return  this._apiService.get.event(this.eventId).then(event => {
            this.ev = event;
            console.log(event); // Has a value
            console.log(this.ev); // Has a value
        });
     }

измените ngOnInit(), чтобы дождаться завершения Promise

    ngOnInit() {
        this.fetchEvent().then(() =>
        console.log(this.ev)); // Now has value;
    }

На самом деле это не принесет вам много денег для вашего варианта использования.

Мое предложение: оберните весь шаблон в <div *ngIf="isDataAvailable"> (template content) </div>

и в ngOnInit()

    isDataAvailable:boolean = false;

    ngOnInit() {
        this.fetchEvent().then(() =>
        this.isDataAvailable = true); // Now has value;
    }
person Günter Zöchbauer    schedule 26.02.2016
comment
На самом деле, ваш первый комментарий отлично сработал. Я только удалил всю fetchEvent функцию и просто поместил apiService.get.event функцию в ngOnInit, и она сработала так, как я задумал. Спасибо! Я приму твой ответ, как только это позволит мне. - person Tom Aalbers; 26.02.2016
comment
По какой-то причине я использовал этот подход в Angular 1.x. ИМХО, это следует воспринимать как взлом, а не как правильное решение. Мы должны добиться большего в угловом 5. - person windmaomao; 02.05.2018
comment
Что именно вам в этом не нравится? Я думаю, что оба подхода прекрасны. - person Günter Zöchbauer; 02.05.2018

Хорошее решение, которое я нашел, - это сделать в пользовательском интерфейсе что-то вроде:

<div *ngIf="isDataLoaded">
 ...Your page...
</div

Страница отображается только тогда, когда isDataLoaded имеет значение true.

person user2965814    schedule 26.07.2017
comment
Я думаю, что это решение предназначено только для Angular 1, а не для Angular ›= 2, потому что правило однонаправленного потока данных Angular запрещает обновление представления после того, как оно было составлено. Оба этих крючка срабатывают после того, как представление компонента было составлено. - person zt1983811; 17.11.2017
comment
Div вообще не отображается. Мы не собираемся останавливать его от рендеринга, мы хотим его отложить. Причина, по которой это не работает, заключается в том, что в angular2 нет двусторонней привязки. @phil, можешь объяснить, как это сработало? - person ishandutta2007; 21.11.2017
comment
хорошо, я использовал ChangeDetectionStrategy.Push, изменение его на ChangeDetectionStrategy.Default делает его двусторонним связанным с шаблоном. - person ishandutta2007; 21.11.2017
comment
угловой 6. Работает. - person TDP; 23.10.2018

Вы можете выполнить предварительную выборку своих данных с помощью преобразователей в Angular2 +. Разрешители обрабатывают ваши данные до полной загрузки вашего Компонента.

Есть много случаев, когда вы хотите загрузить свой компонент только в том случае, если что-то происходит, например, переходите на панель инструментов, только если человек уже вошел в систему, в этом случае резольверы очень удобны.

Посмотрите на простую диаграмму, которую я создал для вас, чтобы понять, как вы можете использовать преобразователь для отправки данных вашему компоненту.

введите описание изображения здесь

Применить Resolver к вашему коду довольно просто, я создал для вас фрагменты, чтобы вы увидели, как можно создать Resolver:

import { Injectable } from '@angular/core';
import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';
import { MyData, MyService } from './my.service';

@Injectable()
export class MyResolver implements Resolve<MyData> {
  constructor(private ms: MyService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<MyData> {
    let id = route.params['id'];

    return this.ms.getId(id).then(data => {
      if (data) {
        return data;
      } else {
        this.router.navigate(['/login']);
        return;
      }
    });
  }
}

и в модуле:

import { MyResolver } from './my-resolver.service';

@NgModule({
  imports: [
    RouterModule.forChild(myRoutes)
  ],
  exports: [
    RouterModule
  ],
  providers: [
    MyResolver
  ]
})
export class MyModule { }

и вы можете получить к нему доступ в своем компоненте следующим образом:

/////
 ngOnInit() {
    this.route.data
      .subscribe((data: { mydata: myData }) => {
        this.id = data.mydata.id;
      });
  }
/////

А в Route примерно так (обычно в файле app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
person Alireza    schedule 08.06.2017
comment
Я думаю, вы пропустили запись конфигурации маршрута в массиве Routes (обычно в файле app.routing.ts): {path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}} - person Voicu; 19.01.2018
comment
@Voicu, он не должен охватывать все части, но я согласен, это делает ответ более исчерпывающим, поэтому я добавил ... Спасибо - person Alireza; 19.01.2018
comment
Чтобы исправить последнюю часть, параметр разрешения в маршруте должен указывать на сам преобразователь, а не на тип данных, то есть resolve: { myData: MyResolver } - person Chris Haines; 23.02.2018
comment
Это MyData - объект модели, который определяется в MyService? - person Isuru Madusanka; 13.09.2018
comment
это решение подходит для получения данных до загрузки страницы, но не для проверки зарегистрированного пользователя или ролей пользователей. Для этого вы должны использовать Guard. - person Angel Q; 27.11.2019