Warten Sie, bis Angular 2 das Modell geladen / aufgelöst hat, bevor Sie die Ansicht / Vorlage rendern

73

In Angular 1.x war UI-Router mein Hauptwerkzeug dafür. Wenn der Router ein Versprechen für "Auflösungs" -Werte zurückgibt, wartet er einfach, bis das Versprechen erfüllt ist, bevor er Anweisungen rendert.

Alternativ kann in Angular 1.x ein Nullobjekt eine Vorlage nicht zum Absturz bringen. Wenn mir also ein vorübergehend unvollständiges Rendern nichts ausmacht, kann ich es einfach $digestzum Rendern verwenden, nachdem promise.then()ein ursprünglich leeres Modellobjekt gefüllt wurde.

Wenn möglich, würde ich es vorziehen, mit dem Laden der Ansicht zu warten und die Routennavigation abzubrechen, wenn die Ressource nicht geladen werden kann. Dies erspart mir die Arbeit des "Navigierens". BEARBEITEN: Beachten Sie, dass dies speziell bedeutet, dass diese Frage eine Angular 2-Futures-kompatible oder Best-Practice-Methode dazu anfordert und den "Elvis-Operator" nach Möglichkeit meidet! Daher habe ich diese Antwort nicht ausgewählt.

Keine dieser beiden Methoden funktioniert jedoch in Angular 2.0. Sicherlich ist dafür eine Standardlösung geplant oder verfügbar. Weiß jemand was es ist?

@Component() {
    template: '{{cats.captchans.funniest}}'
}
export class CatsComponent {

    public cats: CatsModel;

    ngOnInit () {
        this._http.get('/api/v1/cats').subscribe(response => cats = response.json());
    }
}

Die folgende Frage kann dasselbe Problem widerspiegeln: Angular 2-Rendervorlage, nachdem das PROMISE mit Daten geladen wurde . Beachten Sie, dass die Frage keinen Code oder keine akzeptierte Antwort enthält.

Shannon
quelle
1
Sie können faulen Laden Artikel von @TGH
Eric Martinez
1
@shannon der ganze Router wurde gerade veraltet und neu geschrieben. Hoffentlich sprechen sie heute bei NgConf darüber.
Langley
@Langley meinst du, sie werden den Angular 2 Router komplett neu schreiben?
Pascal
@Pascal es scheint so, werfen Sie einen Blick darauf: angle.io/docs/ts/latest/guide/router-deprecated.html
Langley
na ja ... als sie auch das 'veraltete Beta-Winkel 2' schreiben könnten, das durch das 'RC-Winkel 2' ersetzt wird. Mal sehen, ob der RC-Router mit unendlichen Kinder-Routern wie Aureliajs umgehen kann ;-)
Pascal

Antworten:

38

Das Paket @angular/routerhat die ResolveEigenschaft für Routen. So können Sie Daten einfach auflösen, bevor Sie eine Routenansicht rendern.

Siehe: https://angular.io/docs/ts/latest/api/router/index/Resolve-interface.html

Beispiel aus Dokumenten vom heutigen 28. August 2017:

class Backend {
  fetchTeam(id: string) {
    return 'someTeam';
  }
}

@Injectable()
class TeamResolver implements Resolve<Team> {
  constructor(private backend: Backend) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
    return this.backend.fetchTeam(route.params.id);
  }
}

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamCmp,
        resolve: {
          team: TeamResolver
        }
      }
    ])
  ],
  providers: [TeamResolver]
})
class AppModule {}

Jetzt wird Ihre Route erst aktiviert, wenn die Daten aufgelöst und zurückgegeben wurden.

Zugriff auf aufgelöste Daten in Ihrer Komponente

Um zur Laufzeit von Ihrer Komponente aus auf die aufgelösten Daten zuzugreifen, gibt es zwei Methoden. Je nach Ihren Anforderungen können Sie also Folgendes verwenden:

  1. route.snapshot.paramMap was eine Zeichenfolge zurückgibt, oder die
  2. route.paramMapDies gibt ein Observable zurück, zu dem Sie können .subscribe().

Beispiel:

  // the no-observable method
  this.dataYouResolved= this.route.snapshot.paramMap.get('id');
  // console.debug(this.licenseNumber);

  // or the observable method
  this.route.paramMap
     .subscribe((params: ParamMap) => {
        // console.log(params);
        this.dataYouResolved= params.get('id');
        return params.get('dataYouResolved');
        // return null
     });
  console.debug(this.dataYouResolved);

Ich hoffe das hilft.

TetraDev
quelle
Es funktioniert, aber wie können Sie in Ihrer Komponente auf die Daten zugreifen, die von der Auflösung zurückgegeben werden?
Dryoezoe
3
Sieht so aus, als könnten Sie es aus dem Snapshot stackoverflow.com/a/38313301/1138984
ask_io
1
Resolver funktioniert nicht für die erste Route in der Anwendung.
Karl
1
Ihr Code ist etwas veraltet. Die neuere Syntax finden Sie unter angle.io/api/router/Resolve .
Javan R.
85

Versuchen Sie, {{model?.person.name}}dies sollte warten, bis das Modell nicht mehr vorhanden ist, undefinedund dann rendern.

Winkel 2 bezeichnet diese ?.Syntax als Elvis-Operator . Ein Verweis darauf in der Dokumentation ist schwer zu finden. Hier ist eine Kopie davon, falls sie geändert / verschoben werden:

Die Pfade Elvis Operator (?.) Und null

Der Angular-Operator „Elvis“ (?.) Ist eine fließende und bequeme Methode, um sich vor Null- und undefinierten Werten in Eigenschaftspfaden zu schützen. Hier ist es, um vor einem Fehler beim Rendern der Ansicht zu schützen, wenn der aktuelle Hero null ist.

The current hero's name is {{currentHero?.firstName}}

Lassen Sie uns das Problem und diese spezielle Lösung näher erläutern.

Was passiert, wenn die folgende datengebundene Titeleigenschaft null ist?

The title is {{ title }}

Die Ansicht wird weiterhin gerendert, der angezeigte Wert ist jedoch leer. wir sehen nur "Der Titel ist" mit nichts danach. Das ist vernünftiges Verhalten. Zumindest stürzt die App nicht ab.

Angenommen, der Vorlagenausdruck enthält einen Eigenschaftspfad wie in diesem nächsten Beispiel, in dem der Vorname eines Nullhelden angezeigt wird.

The null hero's name is {{nullHero.firstName}}

JavaScript löst einen Nullreferenzfehler aus, ebenso wie Angular:

TypeError: Cannot read property 'firstName' of null in [null]

Schlimmer noch, die gesamte Ansicht verschwindet.

Wir könnten behaupten, dass dies ein vernünftiges Verhalten ist, wenn wir glauben, dass die Heldeneigenschaft niemals null sein darf. Wenn es niemals null sein darf und dennoch null ist, haben wir einen Programmierfehler gemacht, der abgefangen und behoben werden sollte. Eine Ausnahme auszulösen ist das Richtige.

Andererseits können Nullwerte im Eigenschaftspfad von Zeit zu Zeit in Ordnung sein, insbesondere wenn wir wissen, dass die Daten irgendwann eintreffen werden.

Während wir auf Daten warten, sollte die Ansicht ohne Beanstandung gerendert werden und der Pfad der Null-Eigenschaft sollte genauso leer angezeigt werden wie die Eigenschaft title.

Leider stürzt unsere App ab, wenn der aktuelle Hero null ist.

Wir könnten dieses Problem mit NgIf umgehen

<!--No hero, div not displayed, no error --> <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>

Oder wir könnten versuchen, Teile des Eigenschaftspfads mit && zu verketten, da wir wissen, dass der Ausdruck ausfällt, wenn er auf die erste Null trifft.

The null hero's name is {{nullHero && nullHero.firstName}}

Diese Ansätze haben ihre Berechtigung, können jedoch umständlich sein, insbesondere wenn der Eigenschaftspfad lang ist. Stellen Sie sich vor, Sie schützen sich irgendwo in einem langen Eigenschaftspfad wie abcd vor einer Null

Der Angular-Operator „Elvis“ (?.) Ist eine flüssigere und bequemere Methode, um sich vor Nullen in Eigenschaftspfaden zu schützen. Der Ausdruck wird gelöscht, wenn er den ersten Nullwert erreicht. Das Display ist leer, aber die App läuft weiter und es gibt keine Fehler.

<!-- No hero, no problem! --> The null hero's name is {{nullHero?.firstName}}

Es funktioniert auch perfekt mit langen Eigenschaftspfaden:

a?.b?.c?.d

Tehaaron
quelle
Vielen Dank. Dies funktioniert gut als Problemumgehung, aber letztendlich halte ich es für sinnvoller, das Objekt und alle seine untergeordneten Elemente als obligatorisch zu behandeln und nur darauf zu warten, dass es zum Rendern verfügbar ist, wie in anderen Antworten vorgeschlagen (wenn ich sie zum Arbeiten bringen kann) ). Andernfalls wird diese Dekoration auf allen meinen Seiten auf jedem Wert angezeigt, und ich bin in dieser Hinsicht wieder bei der Angular 1.x-Funktionalität. Es gibt wahrscheinlich auch Leistungsprobleme (zusätzliche Dirty Checks).
Shannon
3
@ Shannon Ich glaube nicht, dass dies der Fall ist. Bitte beachten Sie meine Update-Antwort und die ng2-Dokumente.
Tehaaron
Der Grund für meinen Verdacht bezüglich der Leistung ist, dass das Observable(das gleiche gilt für a Promise) bereits ausgepackt wurde. Jetzt können Sie nur noch mit unserem bewährten Angular 1.x-Mechanismus, dem Dirty Check, feststellen, wann er sich geändert hat. Vergleichen Sie dies mit der Verwendung von a Observable, die von der asyncPipe in der Vorlage selbst unterstützt wird. Angular intern kann (ich vermute es) das Rohr so ​​verdrahten, dass keine Beobachter erforderlich sind, und der Knoten wird nur gerendert, wenn ein Observable eintrifft. Es ist effektiv ein Nachrichtenbus mit ereignisgesteuerten Aktionen.
Shannon
Ein weiteres kleines Problem ist, dass die Tatsache, dass Elvis-Bediener überall eingesetzt werden, das Ziel der Änderung des Herzens des Angular-Teams in Bezug auf das stille Schlucken von Fehlern bis zu einem gewissen Grad zunichte macht. Sie haben wahrscheinlich selbst Probleme damit festgestellt und festgestellt, dass Vorlagen (und die gesamte Anwendung) schwieriger zu debuggen sind, wenn von Funktionen erwartet wird, dass sie Nullwerte verarbeiten. Wenn ich eine Auswahl treffen muss, was angezeigt werden soll, beispielsweise in einem A: B-Szenario, muss ich jetzt auch zusätzliche ngIfLogik implementieren . Ich möchte wirklich, dass der Ansatz "Warten auf das Rendern der Komponente" funktioniert.
Shannon
3
Dieser Operator wird jetzt als "sicherer Navigationsoperator" bezeichnet .
ssc-hrep3
7

BEARBEITEN: Das eckige Team hat den @Resolve-Dekorateur veröffentlicht. Es muss noch geklärt werden, wie es funktioniert, aber bis dahin werde ich die Antwort eines anderen hier nehmen und Links zu anderen Quellen bereitstellen:


BEARBEITEN: Diese Antwort funktioniert nur für Angular 2 BETA. Der Router ist zum Zeitpunkt dieser Bearbeitung nicht für Angular 2 RC freigegeben. Ersetzen Sie stattdessen bei Verwendung von Angular 2 RC Verweise auf routerdurch router-deprecated, um den Beta-Router weiterhin zu verwenden.

Die Angular2-zukünftige Möglichkeit, dies zu implementieren, erfolgt über den @Resolve-Dekorator. Bis dahin ist das nächstgelegene Faksimile CanActivatelaut Brandon Roberts Component Decorator. Siehe https://github.com/angular/angular/issues/6611

Beta 0 unterstützt zwar nicht die Bereitstellung aufgelöster Werte für die Komponente, ist jedoch geplant, und es gibt auch eine hier beschriebene Problemumgehung : Verwenden von Resolve In Angular2-Routen

Ein Beta 1-Beispiel finden Sie hier: http://run.plnkr.co/BAqA98lphi4rQZAd/#/resolved . Es wird eine sehr ähnliche Problemumgehung verwendet, das RouteDataObjekt wird jedoch etwas genauer verwendet als RouteParams.

@CanActivate((to) => {
    return new Promise((resolve) => {
        to.routeData.data.user = { name: 'John' }

Beachten Sie außerdem, dass es auch eine Beispielumgehung für den Zugriff auf "aufgelöste" Werte für verschachtelte / übergeordnete Routen und andere Funktionen gibt, die Sie erwarten, wenn Sie den 1.x UI-Router verwendet haben.

Beachten Sie, dass Sie alle Dienste, die Sie dazu benötigen, auch manuell einfügen müssen, da die Angular Injector-Hierarchie derzeit im CanActivate-Dekorator nicht verfügbar ist. Durch einfaches Importieren eines Injektors wird eine neue Injektorinstanz ohne Zugriff auf die Anbieter von erstellt bootstrap(). Daher möchten Sie wahrscheinlich eine anwendungsweite Kopie des Bootstrap-Injektors speichern. Brandons zweiter Plunk-Link auf dieser Seite ist ein guter Ausgangspunkt: https://github.com/angular/angular/issues/4112

Shannon
quelle
1
Ihr empfohlener plnker: run.plnkr.co/BAqA98lphi4rQZAd/#/resolved ist nicht mehr verfügbar. Vielen Dank, dass Sie diese Funktion aktiviert haben. Vielleicht können Sie Ihrer Antwort die aktuelle Funktion hinzufügen, die für das RC1-Release-Problem geplant ist: github.com/angular/angular/issues/4015
Philip
RouteData ist unveränderlich.
Hristo Venev
@HristoVenev: Bezieht sich Ihr Kommentar (und die damit verbundene Ablehnung) auf die Anwendbarkeit der Lösung, während der Angular 2 Beta-Router noch aktuell ist?
Shannon
@shannon RouteDataist auch für den Beta-Router ( router-deprecated) unveränderlich .
Hristo Venev
Ja, mir ist bekannt, dass RouteData als "unveränderlich" dokumentiert ist. Lassen Sie uns für einen Moment ignorieren, was dies für einen JavaScript-Eigenschaftssatz bedeutet. Kommen wir zum Kern Ihres Punktes. Sagen Sie, dass die Lösung nicht funktioniert oder dass es eine bessere Lösung gibt? Zuletzt habe ich überprüft, dass meine Anwendung keine Mängel in diesem Bereich aufweist. Diese Lösung wurde vom Angular-Team vorgeschlagen. Es wurde keine konkurrierende Lösung von jemand anderem angeboten. Obwohl es schön wäre, die Anleitung der Dokumentation zu befolgen, ist Resolve noch nicht implementiert, sodass die Anleitung keine Ablehnung verdient.
Shannon
4

Stellen Sie mit dem Beobachter einen lokalen Wert ein

... vergessen Sie auch nicht, den Wert mit Dummy-Daten zu initialisieren, um uninitializedFehler zu vermeiden .

export class ModelService {
    constructor() {
      this.mode = new Model();

      this._http.get('/api/v1/cats')
      .map(res => res.json())
      .subscribe(
        json => {
          this.model = new Model(json);
        },
        error => console.log(error);
      );
    }
}

Dies setzt voraus, dass Model ein Datenmodell ist, das die Struktur Ihrer Daten darstellt.

Ein Modell ohne Parameter sollte eine neue Instanz erstellen, bei der alle Werte initialisiert (aber leer) sind. Auf diese Weise wird beim Rendern der Vorlage vor dem Empfang der Daten kein Fehler ausgegeben.

Wenn Sie die Daten beibehalten möchten, um unnötige http-Anforderungen zu vermeiden, sollten Sie diese im Idealfall in ein Objekt einfügen, das über einen eigenen Beobachter verfügt, den Sie abonnieren können.

Evan Scholle
quelle
Das ergibt für mich keinen Sinn, Evan. Umgeht dies nicht das leistungsfähigere beobachtbare Muster von Winkel 2, indem die beobachtbare Sequenz zu einem einfachen Objekt abgeflacht wird, das schmutzige Überprüfungen benötigt? Außerdem würde ich es wirklich vorziehen, nicht eine Reihe leerer Initialisierer warten zu müssen, einen für jedes Modell in meinem Client. Ich hoffe, es gibt einen anderen Weg.
Shannon
Ich stimme voll und ganz zu, dass ein ngIfauch eine funktionale Problemumgehung ist - und es erfordert weniger doppelten Code als Ihr anderer Vorschlag in Fällen, in denen erwartet wird, dass Ergebnisobjekte komplex sind. Es hat jedoch die gleiche Schwäche, da es Observabledie Vorlage auspackt und erfordert, dass die Vorlage schmutzige Überprüfungen durchführt (oder unnötig neu rendert ). Ich weiß, dass Sie in Ihrem letzten Kommentar nicht damit einverstanden waren, aber wenn Sie weiter darüber nachdenken, werden Sie wahrscheinlich zustimmen. Siehe auch meinen Kommentar zu @tehaaron, dessen Antwort einen ähnlichen Effekt hatte.
Shannon
@shannon Schmutziges Einchecken in Angular2 ist billig, aber das ist irrelevant, weil es die Frage nicht beantwortet. Was Sie möchten, ist ein vorgewärmter Datencache vor dem Laden der Seite, sodass kein erneutes Rendern des Flashs erforderlich ist. Dazu müssen Sie Ihre Daten als separaten Dienst laden und eine Möglichkeit zum Auslösen der Daten vor dem Laden der Seite auslösen.
Evan Plaice
(Forts.) Dienste werden erst erstellt, wenn sie zum ersten Mal injiziert werden. Eine andere Komponente muss also die Konstruktion auslösen. Eine Möglichkeit wäre, den Dienst in eine übergeordnete Komponente einzufügen. Eine andere Möglichkeit wäre, die Route verzögert zu laden und den Dienst vor dem Laden der Komponente zu starten.
Evan Plaice
Dirty Checking ist bei kleinen Mengen immer billig, und ich habe nichts gegen die Verwendung in Angular 1.x. Ich bin damit einverstanden, dass der Vorteil der Einfachheit die Kosten überwiegt. Es wurde jedoch eine grundlegende architektonische Entscheidung getroffen, einen ObservableMechanismus in Angular 2 zu unterstützen, und die Leistungsverbesserung auf Seiten mit einer großen Anzahl von Elementen war ein Grund. Es ist eine höhere Leistung und dennoch sehr einfach, als Verbraucher "richtig" zu werden. Also würde ich es gerne benutzen.
Shannon
3

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

<div *ngIf="vendorServicePricing && quantityPricing && service">
 ...Your page...
</div

Erst wenn: vendorServicePricing, quantityPricingund servicegeladen werden , die Seite gerendert wird.

Radu Linu
quelle
Dies sollte an erster Stelle stehen, da dies die einfachste Lösung ist. Keine Notwendigkeit von Resolve oder ?."notnull-Operator"
Javan R.
Was ist, wenn Sie eine wirklich große Vorlage haben? Alles in einer großen IF-Klausel speichern? Es wäre schöner, ein Mittel zum Abfangen von Pre-Rendering wie in React zu haben und einfach null zurückzugeben, bis Sie einige Daten haben.
Raul Rene
2

Implementieren Sie das routerOnActivatein Ihrem @Componentund geben Sie Ihr Versprechen zurück:

https://angular.io/docs/ts/latest/api/router/OnActivate-interface.html

BEARBEITEN: Dies funktioniert explizit NICHT, obwohl die aktuelle Dokumentation zu diesem Thema etwas schwer zu interpretieren sein kann. Weitere Informationen finden Sie in Brandons erstem Kommentar hier: https://github.com/angular/angular/issues/6611

BEARBEITEN: Die zugehörigen Informationen auf der ansonsten normalerweise genauen Auth0-Site sind nicht korrekt: https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in- Tiefe/

BEARBEITEN: Das eckige Team plant zu diesem Zweck einen @Resolve-Dekorateur.

Langley
quelle
Hat die Rückgabe eines Versprechens ngOnInitden gleichen Effekt?
Agconti
1
Ich denke nicht, aber es würde nicht helfen, wenn dies der Fall ist, da diese Funktion erst danach aufgerufen wird. Von: angle.io/docs/ts/latest/api/core/… wird "ngOnInit direkt aufgerufen, nachdem die datengebundenen Eigenschaften der Direktive zum ersten Mal überprüft wurden"
Langley,
Interessant: Es scheint albern, dass sie es in allen Demos verwenden, wenn Sie keine Daten damit initialisieren können.
Agconti
2
Das Dokument sagt> * Wenn routerOnActivateein Versprechen zurückgegeben wird, wartet die Routenänderung, bis das Versprechen erfüllt ist, um * untergeordnete Komponenten zu instanziieren und zu aktivieren.
Günter Zöchbauer
2
Der elvis-Bediener darf keine Zweiwege-Bindungen verwenden.
Langley