Angular2: come caricare i dati prima del rendering del componente?


143

Sto cercando di caricare un evento dalla mia API prima che il componente venga visualizzato. Attualmente sto usando il mio servizio API che chiamo dalla funzione ngOnInit del componente.

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

Il mio EventRegistermodello

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

Il mio servizio 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();
        }     
    }     
}

Il componente viene renderizzato prima che la chiamata API nella ngOnInitfunzione venga eseguita recuperando i dati. Quindi non riesco mai a vedere l'ID evento nel mio modello di visualizzazione. Quindi sembra che questo sia un problema ASYNC. Mi aspettavo che il binding del ev( EventRegistercomponente) facesse un po 'di lavoro dopo aver evimpostato la proprietà. Purtroppo non mostra il divcontrassegnato con *ngIf="ev"quando viene impostata la proprietà.

Domanda: sto usando un buon approccio? Altrimenti; Qual è il modo migliore per caricare i dati prima che il componente inizi a eseguire il rendering?

NOTA: l' ngOnInitapproccio è utilizzato in questo tutorial di angular2 .

MODIFICARE:

Due possibili soluzioni. Il primo era quello di abbandonare fetchEvente usare semplicemente il servizio API nella ngOnInitfunzione.

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

Seconda soluzione. Come la risposta data.

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

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

Risposte:


127

aggiornare

originale

Quando console.log(this.ev)viene eseguito dopo this.fetchEvent();, ciò non significa che la fetchEvent()chiamata sia terminata , ciò significa solo che è pianificata. Quando console.log(this.ev)viene eseguita, la chiamata al server non viene nemmeno effettuata e ovviamente non ha ancora restituito un valore.

Modifica fetchEvent()per restituire aPromise

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

cambiare ngOnInit()per attendere il Promisecompletamento

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

Questo in realtà non ti comprerà molto per il tuo caso d'uso.

Il mio consiglio: avvolgi l'intero modello in un <div *ngIf="isDataAvailable"> (template content) </div>

e dentro ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}

3
In realtà, il tuo primo commento ha funzionato magnificamente. Ho rimosso solo l'intera fetchEventfunzione e ho appena inserito la apiService.get.eventfunzione ngOnInite ha funzionato come intendo. Grazie! Accetterò la tua risposta non appena me lo consentirà.
Tom Aalbers,

Per qualche ragione, ho usato questo approccio su Angular 1.x. IMHO, questo dovrebbe essere preso come un trucco invece di una soluzione adeguata. Dovremmo fare di meglio in
Angular

Cosa non ti piace esattamente? Penso che entrambi gli approcci vadano benissimo.
Günter Zöchbauer,

54

Una bella soluzione che ho trovato è fare sull'interfaccia utente qualcosa come:

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

Solo quando: isDataLoaded è true la pagina viene renderizzata.


3
Penso che questa soluzione sia solo per Angular 1 e non Angular> = 2 perché la regola del flusso di dati unidirezionale di Angular proibisce gli aggiornamenti alla vista dopo che è stata composta. Entrambi questi hook si attivano dopo aver composto la vista del componente.
zt1983811

1
Il div non si arrabbia affatto. Non intendiamo impedirne il rendering, vogliamo ritardarlo. Il motivo per cui non funziona è perché non esiste un legame bidirezionale in angular2. @phil puoi spiegare come ha funzionato per te?
ishandutta2007,

1
ok stavo usando ChangeDetectionStrategy.Push, cambiandolo per ChangeDetectionStrategy.Defaultrenderlo a due vie associato con il modello.
ishandutta2007,

angolare 6. Funziona.
TDP,

Che trucco meraviglioso funziona in 2x angolare.
nishil bhave, il

48

Puoi pre-recuperare i tuoi dati usando Resolver in Angular2 + , i Resolver elaborano i tuoi dati prima che il tuo componente sia completamente caricato.

Ci sono molti casi in cui vuoi caricare il tuo componente solo se sta succedendo qualcosa, ad esempio vai su Dashboard solo se la persona ha già effettuato l'accesso, in questo caso i resolver sono così utili.

Guarda il semplice diagramma che ho creato per te per uno dei modi in cui puoi usare il resolver per inviare i dati al tuo componente.

inserisci qui la descrizione dell'immagine

Applicare Resolver al tuo codice è piuttosto semplice, ho creato i frammenti per farti vedere come è possibile creare 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;
      }
    });
  }
}

e nel modulo :

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

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

e puoi accedervi nel tuo Componente in questo modo:

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

E nel percorso qualcosa del genere (di solito nel file app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////

1
Penso che tu abbia perso la voce di configurazione del percorso sotto l' Routesarray (di solito nel file app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu

1
@Voicu, non dovrebbe coprire tutte le parti, ma sono d'accordo, rende la risposta più completa, quindi ho aggiunto ... Grazie
Alireza

5
Solo per correggere l'ultima parte, il parametro di risoluzione nella rotta deve puntare al risolutore stesso, non al tipo di dati, ovveroresolve: { myData: MyResolver }
Chris Haines,

MyData è un oggetto modello che definisce in MyService?
Isuru Madusanka,

1
questa soluzione è utile per il recupero dei dati prima del caricamento della pagina ma non per il controllo dell'utente registrato o dei ruoli utente. Per questo dovresti usare Guards
Angel Q,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.