Angular2: Comment charger des données avant de rendre le composant?

143

J'essaie de charger un événement depuis mon API avant que le composant ne soit rendu. Actuellement, j'utilise mon service API que j'appelle depuis la fonction ngOnInit du composant.

Mon EventRegistercomposant:

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

Mon EventRegistermodèle

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

Mon service 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();
        }     
    }     
}

Le composant est rendu avant que l'appel d'API dans la ngOnInitfonction ne récupère les données. Je ne vois donc jamais l'ID d'événement dans mon modèle de vue. Il semble donc que ce soit un problème ASYNC. Je m'attendais à ce que la liaison du ev( EventRegistercomposant) fasse un certain travail une fois la evpropriété définie. Malheureusement, il n'affiche pas le divmarqué avec *ngIf="ev"lorsque la propriété est définie.

Question: Est-ce que j'utilise une bonne approche? Si non; Quelle est la meilleure façon de charger des données avant que le composant ne commence à être rendu?

REMARQUE: l' ngOnInitapproche est utilisée dans ce didacticiel angular2 .

ÉDITER:

Deux solutions possibles. La première était d'abandonner le fetchEventet d'utiliser simplement le service API dans la ngOnInitfonction.

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

Deuxième solution. Comme la réponse donnée.

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

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

Réponses:

127

mettre à jour

original

Quand console.log(this.ev)est exécuté après this.fetchEvent();, cela ne signifie pas que l' fetchEvent()appel est terminé, cela signifie seulement qu'il est planifié. Quand console.log(this.ev)est exécuté, l'appel au serveur n'est même pas effectué et n'a bien sûr pas encore renvoyé de valeur.

Changer fetchEvent()pour retourner 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
    });
 }

modifier ngOnInit()pour attendre la Promisefin de

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

Cela ne vous achètera pas grand-chose pour votre cas d'utilisation.

Ma suggestion: enveloppez l'intégralité de votre modèle dans un <div *ngIf="isDataAvailable"> (template content) </div>

et en ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}
Günter Zöchbauer
la source
3
En fait, votre premier commentaire a fonctionné à merveille. J'ai seulement supprimé la fetchEventfonction entière et mis simplement la apiService.get.eventfonction dans le ngOnInitet cela a fonctionné comme prévu. Merci! J'accepterai votre réponse dès que cela me le permettra aussi.
Tom Aalbers
Pour une raison quelconque, j'ai utilisé cette approche sur Angular 1.x. À mon humble avis, cela devrait être considéré comme un hack au lieu d'une solution appropriée. Nous devrions faire mieux en angulaire 5.
windmaomao
Qu'est-ce que vous n'aimez pas exactement à ce sujet? Je pense que les deux approches sont parfaitement bien.
Günter Zöchbauer
54

Une bonne solution que j'ai trouvée est de faire sur l'interface utilisateur quelque chose comme:

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

Uniquement lorsque: isDataLoaded est true, la page est rendue.

user2965814
la source
3
Je pense que cette solution est uniquement pour Angular 1 et non Angular> = 2 car la règle de flux de données unidirectionnel d'Angular interdit les mises à jour de la vue une fois qu'elle a été composée. Ces deux crochets se déclenchent une fois la vue du composant composée.
zt1983811
1
Le div n'est pas du tout monté. Nous n'avons pas l'intention de l'empêcher de s'afficher, nous voulons le retarder. La raison pour laquelle cela ne fonctionne pas est qu'il n'y a pas de liaison bidirectionnelle dans angular2. @phil pouvez-vous expliquer comment cela a fonctionné pour vous?
ishandutta2007
1
ok j'utilisais ChangeDetectionStrategy.Push, le changer en le ChangeDetectionStrategy.Defaultrend lié dans les deux sens avec le modèle.
ishandutta2007
angulaire 6. Fonctionne.
TDP
Quel merveilleux hack fonctionne en 2x angulaire.
nishil bhave le
48

Vous pouvez pré-récupérer vos données en utilisant des résolveurs dans Angular2 + , les résolveurs traitent vos données avant le chargement complet de votre composant.

Dans de nombreux cas, vous souhaitez charger votre composant uniquement s'il se passe quelque chose, par exemple, accédez au tableau de bord uniquement si la personne est déjà connectée, dans ce cas, les résolveurs sont très pratiques.

Regardez le diagramme simple que j'ai créé pour vous pour une des façons dont vous pouvez utiliser le résolveur pour envoyer les données à votre composant.

entrez la description de l'image ici

L'application de Resolver à votre code est assez simple, j'ai créé les extraits pour vous permettre de voir comment le Resolver peut être créé:

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

et dans le module :

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

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

et vous pouvez y accéder dans votre composant comme ceci:

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

Et dans la Route quelque chose comme ça (généralement dans le fichier app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
Alireza
la source
1
Je pense que vous avez manqué l'entrée de configuration de l'itinéraire sous le Routestableau (généralement dans le fichier app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu
1
@Voicu, ce n'est pas censé couvrir toutes les parties, mais je suis d'accord, cela rend la réponse plus complète, alors j'ai ajouté ... Merci
Alireza
5
Juste pour corriger la dernière partie, le paramètre de résolution de l'itinéraire doit pointer vers le résolveur lui-même, pas le type de données, c'estresolve: { myData: MyResolver }
Chris Haines
Est-ce MyData un objet de modèle qui définit dans MyService?
Isuru Madusanka
1
cette solution est bonne pour récupérer des données avant le chargement de la page mais pas pour vérifier les utilisateurs enregistrés ni les rôles des utilisateurs. Pour cela, vous devriez utiliser Guards
Angel Q