Angular2: Wie lade ich Daten, bevor ich die Komponente rendere?

143

Ich versuche, ein Ereignis aus meiner API zu laden, bevor die Komponente gerendert wird. Derzeit verwende ich meinen API-Dienst, den ich über die ngOnInit-Funktion der Komponente aufrufe.

Meine EventRegisterKomponente:

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

Meine EventRegisterVorlage

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

Mein API-Service

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

Die Komponente wird gerendert, bevor der API-Aufruf in der ngOnInitFunktion das Abrufen der Daten abgeschlossen hat. Daher wird die Ereignis-ID in meiner Ansichtsvorlage nie angezeigt. Es sieht also so aus, als wäre dies ein ASYNC-Problem. Ich habe erwartet, dass die Bindung der ev( EventRegisterKomponente) einige Arbeit leistet, nachdem die evEigenschaft festgelegt wurde. Leider wird das mit divgekennzeichnete nicht angezeigt, *ngIf="ev"wenn die Eigenschaft festgelegt wird.

Frage: Benutze ich einen guten Ansatz? Wenn nicht; Was ist der beste Weg, um Daten zu laden, bevor die Komponente mit dem Rendern beginnt?

HINWEIS: Der ngOnInitAnsatz wird in diesem Angular2-Lernprogramm verwendet .

BEARBEITEN:

Zwei mögliche Lösungen. Zuerst musste fetchEventder API-Dienst in der ngOnInitFunktion verworfen und einfach verwendet werden.

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

Zweite Lösung. Wie die gegebene Antwort.

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

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

Antworten:

127

aktualisieren

Original

Wenn danach console.log(this.ev)ausgeführt wird this.fetchEvent();, bedeutet dies nicht, dass der fetchEvent()Anruf abgeschlossen ist, sondern nur, dass er geplant ist. Wenn console.log(this.ev)ausgeführt, wird der Aufruf des Servers nicht einmal getätigt und hat natürlich noch keinen Wert zurückgegeben.

Ändern Sie fetchEvent(), um a zurückzugebenPromise

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

Ändern Sie ngOnInit(), um auf den PromiseAbschluss zu warten

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

Dies wird Ihnen tatsächlich nicht viel für Ihren Anwendungsfall kaufen.

Mein Vorschlag: Wickeln Sie Ihre gesamte Vorlage in eine <div *ngIf="isDataAvailable"> (template content) </div>

und in ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}
Günter Zöchbauer
quelle
3
Eigentlich hat Ihr erster Kommentar wunderbar funktioniert. Ich habe nur die gesamte fetchEventFunktion entfernt und sie einfach in die apiService.get.eventFunktion ngOnIniteingefügt, und es hat so funktioniert, wie ich es beabsichtigt habe. Vielen Dank! Ich werde Ihre Antwort akzeptieren, sobald es mir auch erlaubt.
Tom Aalbers
Aus irgendeinem Grund habe ich diesen Ansatz für Angular 1.x verwendet. IMHO sollte dies als Hack statt als richtige Lösung verstanden werden. Wir sollten es in Winkel 5 besser machen.
Windmaomao
Was genau magst du daran nicht? Ich denke, beide Ansätze sind vollkommen in Ordnung.
Günter Zöchbauer
54

Eine nette Lösung, die ich gefunden habe, besteht darin, auf der Benutzeroberfläche Folgendes zu tun:

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

Nur wenn: isDataLoaded true ist, wird die Seite gerendert.

user2965814
quelle
3
Ich denke, diese Lösung gilt nur für Angular 1, nicht für Angular> = 2, da die unidirektionale Datenflussregel von Angular Aktualisierungen der Ansicht nach ihrer Erstellung verbietet. Beide Hooks werden ausgelöst, nachdem die Ansicht der Komponente erstellt wurde.
zt1983811
1
Der Div wird überhaupt nicht gerissen. Wir wollen nicht verhindern, dass es gerendert wird, wir wollen es verzögern. Der Grund, warum es nicht funktioniert, ist, dass es in angle2 keine bidirektionale Bindung gibt. @phil kannst du erklären, wie es bei dir funktioniert hat?
ishandutta2007
1
ok, ich habe es verwendet ChangeDetectionStrategy.Pushund ChangeDetectionStrategy.Defaultes so geändert , dass es in beide Richtungen mit der Vorlage verbunden ist.
ishandutta2007
eckig 6. Funktioniert.
TDP
Was für ein wunderbarer Hack funktioniert in eckigen 2x.
Nishil Bhave
48

Sie können Pre-Fetch - Daten unter Verwendung von Resolver in Angular2 + verarbeiten Resolver Ihre Daten vor Ihrer Komponente vollständig geladen werden.

In vielen Fällen möchten Sie Ihre Komponente nur laden, wenn bestimmte Ereignisse auftreten. Navigieren Sie beispielsweise nur dann zum Dashboard, wenn die Person bereits angemeldet ist. In diesem Fall sind Resolver sehr praktisch.

Schauen Sie sich das einfache Diagramm an, das ich für Sie erstellt habe, um zu erfahren, wie Sie mit dem Resolver die Daten an Ihre Komponente senden können.

Geben Sie hier die Bildbeschreibung ein

Das Anwenden von Resolver auf Ihren Code ist ziemlich einfach. Ich habe die Snippets erstellt, damit Sie sehen können, wie der Resolver erstellt werden kann:

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

und im Modul :

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

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

und Sie können in Ihrer Komponente wie folgt darauf zugreifen :

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

Und in der Route so etwas (normalerweise in der Datei app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
Alireza
quelle
1
Ich denke, Sie haben Routesden Routenkonfigurationseintrag unter dem Array verpasst (normalerweise in der Datei app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu
1
@ Voicu, es soll nicht alle Teile abdecken, aber ich stimme zu, es macht die Antwort umfassender, also fügte ich hinzu ... Danke
Alireza
5
Um den letzten Teil zu korrigieren, muss der Auflösungsparameter in der Route auf den Resolver selbst zeigen, nicht auf den Datentyp, dhresolve: { myData: MyResolver }
Chris Haines
Ist das MyData ein Modellobjekt, das in MyService definiert wird?
Isuru Madusanka
1
Diese Lösung eignet sich zum Abrufen von Daten vor dem Laden der Seite, jedoch nicht zum Überprüfen protokollierter Benutzer- oder Benutzerrollen. Dafür sollten Sie Guards
Angel Q