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


Estoy intentando 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 EventRegister componente:

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

<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 a la API en la función ngOnInit termine de obtener los datos. Así que nunca puedo ver el id de evento en mi plantilla de vista. Así que parece que esto es una ASÍNCRONA problema. Esperaba la unión de la ev (EventRegister component) para hacer algún trabajo después de establecer la propiedad ev. Lamentablemente no muestra el div marcado 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 enfoque ngOnInit se utiliza en este tutorial angular2.

EDITAR:

Dos posibles soluciones. Primero fue deshacerse de fetchEvent y simplemente usar el servicio API en la función ngOnInit.

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);
}
Author: Tom Aalbers, 2016-02-26

3 answers

Update

Original

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

Cambie fetchEvent() para devolver un 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
    });
 }

Cambiar ngOnInit() a esperar a que el Promise para completar

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

Esto en realidad no le comprará mucho para su caso de uso.

Mi sugerencia: Envuelve toda tu 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;
}
 89
Author: Günter Zöchbauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-15 22:01:56

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 true la página es renderizada.

 29
Author: user2965814,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-26 09:31:02

Puede pre-obtener sus datos mediante el uso de Resolutores en Angular2+, los resolutores procesan sus datos antes de que su Componente se cargue completamente.

Hay muchos casos en los que desea cargar su componente solo si hay cierta cosa sucediendo, por ejemplo, navegue al Panel de control solo si la persona ya ha iniciado sesión, en este caso los resolutores son muy útiles.

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

introduzca la descripción de la imagen aquí

Aplicar Resolver a tu código es bastante simple, he creado 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 la aplicación.enrutamiento.archivo ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
////
 22
Author: Alireza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-19 16:15:23