Angular2: Jak załadować dane przed renderowaniem komponentu?

143

Próbuję załadować zdarzenie z mojego interfejsu API przed renderowaniem składnika. Obecnie używam mojej usługi API, którą wywołuję z funkcji ngOnInit komponentu.

Mój EventRegisterkomponent:

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

Mój EventRegisterszablon

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

Moja usługa 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();
        }     
    }     
}

Komponent jest renderowany, zanim wywołanie API w ngOnInitfunkcji zakończy się pobieraniem danych. Dlatego nigdy nie widzę identyfikatora zdarzenia w moim szablonie widoku. Więc wygląda na to, że jest to problem ASYNC. Spodziewałem się, że powiązanie ev( EventRegisterkomponentu) wykona trochę pracy po ustawieniu evwłaściwości. Niestety, nie pokazuje on divoznaczonego symbolem, *ngIf="ev"gdy właściwość zostanie ustawiona.

Pytanie: Czy stosuję dobre podejście? Jeśli nie; Jaki jest najlepszy sposób na załadowanie danych przed rozpoczęciem renderowania komponentu?

UWAGA:ngOnInit podejście jest stosowane w tym angular2 tutorialu .

EDYTOWAĆ:

Dwa możliwe rozwiązania. Pierwszym było porzucenie fetchEventi użycie usługi API w ngOnInitfunkcji.

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

Drugie rozwiązanie. Podobnie jak udzielona odpowiedź.

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

ngOnInit() {
    this.fetchEvent().then(event => this.ev = event);
}
Tom Aalbers
źródło

Odpowiedzi:

127

aktualizacja

oryginalny

Kiedy console.log(this.ev)jest wykonywany później this.fetchEvent();, nie oznacza to, że fetchEvent()połączenie zostało wykonane, oznacza to tylko, że zostało zaplanowane. Kiedy console.log(this.ev)jest wykonywany, wywołanie serwera nie jest nawet wykonywane i oczywiście nie zwróciło jeszcze wartości.

Zmień, fetchEvent()aby zwrócić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
    });
 }

zmień, ngOnInit()aby poczekać na Promisezakończenie

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

To faktycznie nie kupi ci dużo dla twojego przypadku użycia.

Moja sugestia: opakuj cały szablon w plik <div *ngIf="isDataAvailable"> (template content) </div>

i w ngOnInit()

isDataAvailable:boolean = false;

ngOnInit() {
    this.fetchEvent().then(() =>
    this.isDataAvailable = true); // Now has value;
}
Günter Zöchbauer
źródło
3
Właściwie twój pierwszy komentarz zadziałał pięknie. Usunąłem tylko całą fetchEventfunkcję i po prostu umieściłem apiService.get.eventfunkcję w ngOnIniti działało tak, jak zamierzałem. Dzięki! Przyjmę Twoją odpowiedź, gdy tylko mi na to pozwoli.
Tom Aalbers
Z jakiegoś powodu zastosowałem to podejście w Angular 1.x. IMHO, to powinno być traktowane jako hack zamiast prawidłowego rozwiązania. Powinniśmy być lepsi w Angular 5.
windmaomao
Co ci się w tym nie podoba? Myślę, że oba podejścia są w porządku.
Günter Zöchbauer
54

Dobrym rozwiązaniem, które znalazłem, jest zrobienie w interfejsie użytkownika czegoś takiego:

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

Strona jest renderowana tylko wtedy, gdy: isDataLoaded ma wartość true.

user2965814
źródło
3
Myślę, że to rozwiązanie jest tylko dla Angular 1, a nie Angular> = 2, ponieważ reguła jednokierunkowego przepływu danych Angulara zabrania aktualizacji widoku po jego utworzeniu. Oba te haki są uruchamiane po utworzeniu widoku komponentu.
zt1983811
1
Element div w ogóle nie zostaje rozdarty. Nie zamierzamy go powstrzymywać, chcemy go opóźnić. Powodem, dla którego nie działa, jest brak dwustronnego wiązania w angular2. @phil czy możesz wyjaśnić, jak to zadziałało?
ishandutta2007
1
ok ChangeDetectionStrategy.Push, używałem, zmieniając go tak, aby ChangeDetectionStrategy.Defaultbył dwukierunkowo powiązany z szablonem.
ishandutta2007
kątowe 6. Działa.
TDP
Cóż za wspaniały hack działa w kątowym 2x.
nishil bhave
48

Możesz pobrać dane z wyprzedzeniem za pomocą programów do rozpoznawania nazw w Angular2 + , programy do rozpoznawania nazw przetwarzają dane przed pełnym załadowaniem komponentu.

Jest wiele przypadków, w których chcesz załadować swój komponent tylko wtedy, gdy coś się dzieje, na przykład nawigacja do Dashboardu tylko wtedy, gdy osoba jest już zalogowana, w tym przypadku Resolwery są bardzo przydatne.

Spójrz na prosty diagram, który dla Ciebie stworzyłem, przedstawiający jeden ze sposobów wykorzystania resolwera do wysyłania danych do Twojego komponentu.

wprowadź opis obrazu tutaj

Zastosowanie resolvera do twojego kodu jest dość proste, stworzyłem fragmenty, aby zobaczyć, jak można utworzyć 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;
      }
    });
  }
}

aw module :

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

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

i możesz uzyskać do niego dostęp w swoim komponencie w następujący sposób:

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

A w Route coś takiego (zwykle w pliku app.routing.ts):

////
{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyResolver}}
////
Alireza
źródło
1
Myślę, że przegapiłeś wpis konfiguracji trasy pod Routestablicą (zwykle w pliku app.routing.ts ):{path: 'yourpath/:id', component: YourComponent, resolve: { myData: MyData}}
Voicu
1
@Voicu, to nie ma obejmować wszystkich części, ale zgadzam się, to czyni odpowiedź bardziej kompleksową, więc dodałem ... Dzięki
Alireza
5
Aby poprawić ostatnią część, parametr rozstrzygania w trasie musi wskazywać na sam przelicznik, a nie typ danych, tj.resolve: { myData: MyResolver }
Chris Haines,
Czy to MyData jest obiektem modelu, który definiuje w MyService?
Isuru Madusanka
1
to rozwiązanie jest dobre do pobierania danych przed załadowaniem strony, ale nie do sprawdzania zalogowanego użytkownika ani ról użytkowników. W tym celu powinieneś użyć Guards
Angel Q