Angular2: ¿Cómo cargar datos antes de representar el componente?

143

Estoy tratando de cargar un evento desde mi API antes de que el componente se procese. Actualmente estoy usando mi servicio API que llamo desde la función ngOnInit del componente.

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

Mi EventRegisterplantilla

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

Mi servicio 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();
        }     
    }     
}

El componente se procesa antes de que la llamada API en la ngOnInitfunción termine de recuperar los datos. Por lo tanto, nunca puedo ver la identificación del evento en mi plantilla de vista. Parece que este es un problema ASYNC. Esperaba que el enlace del ev( EventRegistercomponente) hiciera un trabajo después de que evse estableciera la propiedad. Lamentablemente, no muestra el divmarcado con *ngIf="ev"cuando se establece la propiedad.

Pregunta: ¿Estoy usando un buen enfoque? Si no; ¿Cuál es la mejor manera de cargar datos antes de que el componente comience a renderizarse?

NOTA: El ngOnInitenfoque se utiliza en este tutorial angular2 .

EDITAR:

Dos posibles soluciones. Primero fue deshacerse del fetchEventy simplemente usar el servicio API en la ngOnInitfunción.

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

Segunda solución Como la respuesta dada.

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

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

Respuestas:

127

actualizar

original

Cuando console.log(this.ev)se ejecuta después this.fetchEvent();, esto no significa que la fetchEvent()llamada está hecha, solo significa que está programada. Cuando console.log(this.ev)se ejecuta, la llamada al servidor ni siquiera se realiza y, por supuesto, aún no ha devuelto un valor.

Cambiar fetchEvent()para devolver unPromise

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

cambiar ngOnInit()para esperar a Promiseque se complete

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

Esto en realidad no te comprará mucho para tu caso de uso.

Mi sugerencia: envuelva toda su plantilla en un <div *ngIf="isDataAvailable"> (template content) </div>

y en ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}
Günter Zöchbauer
fuente
3
En realidad, tu primer comentario funcionó maravillosamente. Solo eliminé toda la fetchEventfunción y solo puse la apiService.get.eventfunción en el ngOnInity funcionó como asistí. ¡Gracias! Aceptaré tu respuesta tan pronto como me lo permita.
Tom Aalbers
Por alguna razón, utilicé este enfoque en Angular 1.x. En mi humilde opinión, esto debe tomarse como un truco en lugar de una solución adecuada. Deberíamos hacerlo mejor en angular 5.
windmaomao
¿Qué es exactamente lo que no te gusta de eso? Creo que ambos enfoques están perfectamente bien.
Günter Zöchbauer
54

Una buena solución que he encontrado es hacer en la interfaz de usuario algo como:

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

Solo cuando: isDataLoaded es verdadero, se representa la página.

usuario2965814
fuente
3
Creo que esta solución es solo para Angular 1, no Angular> = 2 porque la regla de flujo de datos unidireccional de Angular prohíbe las actualizaciones de la vista después de que se haya compuesto. Ambos ganchos se disparan después de que se ha compuesto la vista del componente.
zt1983811
1
El div no se renderiza en absoluto. No tenemos la intención de evitar que se procese, queremos retrasarlo. La razón por la que no funciona es porque no hay enlace bidireccional en angular2. @phil, ¿puedes explicar cómo te funcionó?
ishandutta2007
1
ok estaba usando ChangeDetectionStrategy.Push, cambiarlo a ChangeDetectionStrategy.Defaulthace que sea unida en dos direcciones con la plantilla.
ishandutta2007
angular 6. Obras.
TDP
Qué truco maravilloso funciona en angular 2x.
nishil bhave
48

Puede buscar previamente sus datos utilizando Resolvers en Angular2 + , los Resolvers procesan sus datos antes de que su Componente se cargue por completo.

Hay muchos casos en los que desea cargar su componente solo si sucede algo, por ejemplo, vaya al Tablero solo si la persona ya inició sesión, en este caso, los Resolvers son muy útiles.

Mire el diagrama simple que creé para usted para una de las formas en que puede usar el resolutor para enviar los datos a su componente.

ingrese la descripción de la imagen aquí

Aplicar Resolver a tu código es bastante simple, creé los fragmentos para que veas cómo se puede crear el 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;
      }
    });
  }
}

y en el módulo :

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

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

y puedes acceder a él en tu Componente así:

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

Y en la ruta algo como esto (generalmente en el archivo app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
Alireza
fuente
1
Creo que te perdiste la entrada de configuración de ruta debajo de la Routesmatriz (generalmente en el archivo app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu
1
@Voicu, no se supone que cubra todas las partes, pero estoy de acuerdo, hace que la respuesta sea más completa, así que agregué ... Gracias
Alireza
55
Solo para corregir la última parte, el parámetro resolver en la ruta debe apuntar al resolutor en sí, no al tipo de datos, es decirresolve: { myData: MyResolver }
Chris Haines,
¿Es MyData un objeto modelo que define en MyService?
Isuru Madusanka
1
Esta solución es buena para obtener datos antes de que se cargue la página, pero no para verificar el usuario registrado ni los roles de usuario. Para eso deberías usar Guardias
Angel Q