Angular2: Como carregar dados antes de renderizar o componente?

143

Estou tentando carregar um evento da minha API antes que o componente seja renderizado. Atualmente, estou usando meu serviço de API, que chamo da função ngOnInit do componente.

Meu EventRegistercomponente:

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
    }
}

Meu EventRegistermodelo

<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>

Meu serviço de 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();
        }     
    }     
}

O componente é renderizado antes que a chamada da API na ngOnInitfunção seja concluída, buscando os dados. Portanto, nunca consigo ver o ID do evento no meu modelo de exibição. Portanto, parece que este é um problema do ASYNC. Eu esperava que a ligação do ev( EventRegistercomponente) fizesse algum trabalho depois que a evpropriedade foi definida. Infelizmente, ele não mostra o divmarcado com *ngIf="ev"quando a propriedade é definida.

Pergunta: Estou usando uma boa abordagem? Se não; Qual é a melhor maneira de carregar dados antes que o componente comece a renderizar?

NOTA: A ngOnInitabordagem é usada neste tutorial angular2 .

EDITAR:

Duas soluções possíveis. O primeiro foi abandonar o fetchEvente apenas usar o serviço API na ngOnInitfunção.

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

Segunda solução. Como a resposta dada.

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

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

Respostas:

127

atualizar

original

Quando console.log(this.ev)é executado depois this.fetchEvent();, isso não significa que a fetchEvent()chamada foi feita, apenas significa que está agendada. Quando console.log(this.ev)é executada, a chamada para o servidor nem é feita e, é claro, ainda não retornou um valor.

Altere fetchEvent()para retornar umPromise

 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
    });
 }

alterar ngOnInit()para aguardar Promisea conclusão

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

Na verdade, isso não comprará muito para o seu caso de uso.

Minha sugestão: envolva todo o seu modelo em um <div *ngIf="isDataAvailable"> (template content) </div>

e em ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}
Günter Zöchbauer
fonte
3
Na verdade, seu primeiro comentário funcionou lindamente. Eu apenas removi a fetchEventfunção inteira e apenas coloquei a apiService.get.eventfunção no ngOnInite funcionou como pretendia. Obrigado! Aceito que você responda assim que me permitir.
26616 Tom Aalbers
Por alguma razão, usei essa abordagem no Angular 1.x. IMHO, isso deve ser tomado como um hack, em vez de uma solução adequada. Devemos fazer melhor em angular 5.
windmaomao
O que exatamente você não gosta sobre isso? Eu acho que as duas abordagens estão perfeitamente bem.
Günter Zöchbauer
54

Uma boa solução que eu encontrei é fazer na interface do usuário algo como:

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

Somente quando: isDataLoaded for true, a página será renderizada.

user2965814
fonte
3
Penso que esta solução é apenas para Angular 1 e não Angular> = 2 porque a regra de fluxo de dados unidirecional da Angular proíbe atualizações na exibição depois que ela foi composta. Ambos os ganchos são acionados após a composição do ponto de vista.
zt1983811
1
O div não é destruído. Não pretendemos impedir a renderização, queremos atrasá-la. O motivo pelo qual ele não funciona é porque não há ligação bidirecional no angular2. @phil você pode explicar como funcionou para você?
precisa saber é o seguinte
1
ok eu estava usando ChangeDetectionStrategy.Push, alterá-lo para ChangeDetectionStrategy.Defaulttorná-lo em dois sentidos vinculado ao modelo.
precisa saber é o seguinte
angular 6. Trabalhos.
TDP
Que hack maravilhoso funciona em 2x angular.
nishil bhave 12/07
48

Você pode buscar previamente seus dados usando os Resolvers no Angular2 + . Os Resolvers processam seus dados antes que o Component seja totalmente carregado.

Há muitos casos em que você deseja carregar seu componente apenas se algo estiver acontecendo, por exemplo, navegue até o Painel apenas se a pessoa já estiver conectada; nesse caso, os Resolvers são muito úteis.

Veja o diagrama simples que criei para você, de uma maneira que você pode usar o resolvedor para enviar os dados para o seu componente.

insira a descrição da imagem aqui

A aplicação do Resolver ao seu código é bastante simples, criei os snippets para você ver como o Resolver pode ser criado:

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;
      }
    });
  }
}

e no módulo :

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

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

e você pode acessá-lo no seu componente assim:

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

E na rota, algo assim (geralmente no arquivo app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
Alireza
fonte
1
Eu acho que você perdeu a entrada de configuração de rota sob a Routesmatriz (geralmente no arquivo app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu
1
@Voicu, não é suposto para cobrir todas as partes, mas concordo com, torna a resposta mais abrangente, então eu adicionei ... Graças
Alireza
5
Só para corrigir a última parte, o parâmetro determinação na rota precisa apontar para o próprio resolver, não o tipo de dados, ou sejaresolve: { myData: MyResolver }
Chris Haines
Esse MyData é um objeto de modelo que define no MyService?
Isuru Madusanka 13/09/18
1
essa solução é boa para buscar dados antes do carregamento da página, mas não para verificar o usuário logado nem as funções do usuário. Para isso, você deve usar Guards
Angel Q