web-dev-qa-db-de.com

Warten Sie, bis Angular 2 das Modell lädt/auflöst, bevor Sie Ansicht/Vorlage rendern

In Angular 1.x war der UI-Router mein Hauptwerkzeug dafür. Durch die Rückgabe eines Versprechens für "Auflösungs" -Werte würde der Router einfach warten, bis das Versprechen abgeschlossen ist, bevor Anweisungen gerendert werden.

In Angular 1.x hingegen wird ein Nullobjekt keine Vorlage zum Absturz bringen. Wenn ich also ein temporär unvollständiges Rendering nicht stört, kann ich einfach $digest zum Rendern verwenden, nachdem promise.then() ein zunächst leeres Modellobjekt auffüllt.

Wenn möglich, würde ich von den beiden Ansätzen lieber warten, um die Ansicht zu laden, und die Routennavigation abbrechen, wenn die Ressource nicht geladen werden kann. Dies erspart mir die Arbeit des "Nicht-Navigierens". EDIT: Beachten Sie, dass dies speziell bedeutet, dass diese Frage eine Angular 2-Futures-kompatible Methode oder Best-Practice-Methode dazu erfordert, und den "Elvis-Operator" möglichst zu vermeiden! 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 das gleiche Problem widerspiegeln: Angular 2 Render-Vorlage, nachdem das PROMISE mit Daten geladen wurde . Beachten Sie, dass die Frage keinen Code oder eine akzeptierte Antwort enthält.

68
shannon

Das Paket @angular/router verfügt über die Resolve-Eigenschaft für Routen. So können Sie Daten vor dem Rendern einer Routenansicht problemlos auflösen.

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

Beispiel aus Dokumenten ab heute, 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 gelöste Daten in Ihrer Komponente

Um auf die aufgelösten Daten zur Laufzeit von Ihrer Komponente aus zuzugreifen, gibt es zwei Methoden. Abhängig von Ihren Bedürfnissen können Sie also Folgendes verwenden:

  1. route.snapshot.paramMap, der eine Zeichenfolge zurückgibt, oder 
  2. route.paramMap, der ein Observable zurückgibt, an das Sie .subscribe() können. 

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.

32
TetraDev

Versuchen Sie {{model?.person.name}}. Dies sollte warten, bis das Modell nicht undefined ist und dann rendern.

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

Die Elvis Operator (?.) - und Null-Eigenschaftspfade

Der Angular-Operator "Elvis" (?.) Ist eine fließende und bequeme Methode, um Nullwerte und undefinierte Werte in Eigenschaftspfaden zu schützen. Hier schützt es vor einem Ansichtsfehler, wenn der aktuelle Nullwert null ist.

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

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

Was passiert, wenn die folgende datengebundene title -Eigenschaft null ist?

The title is {{ title }}

Die Ansicht wird immer noch dargestellt, 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 firstName eines Null-Helden angezeigt wird.

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

JavaScript löst einen Nullreferenzfehler aus, und auch 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 das Heldeneigentum 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 zu werfen ist das Richtige.

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

Während wir auf Daten warten, sollte die Ansicht ohne Beanstandung gerendert werden, und der Null-Eigenschaftspfad sollte wie die Titeleigenschaft leer angezeigt werden.

Leider stürzt unsere App ab, wenn currentHero 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, wissend, dass der Ausdruck herausspringt, wenn er auf die erste Null trifft.

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

Diese Ansätze sind wertvoll, aber sie können umständlich sein, insbesondere wenn der Eigenschaftspfad lang ist. Stellen Sie sich vor, Sie würden sich irgendwo in einem langen Eigenschaftspfad wie a.b.c.d gegen eine Null schützen.

Der Angular-Operator "Elvis" (?.) Ist eine flüssigere und bequemere Möglichkeit, um Nullwerte in Eigenschaftspfaden zu verhindern. Der Ausdruck springt heraus, wenn er den ersten Nullwert erreicht. Die Anzeige 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 bei langen Grundstückspfaden perfekt:

a?.b?.c?.d

84
tehaaron

BEARBEITEN: Das Winkelteam hat den @Resolve-Dekorator veröffentlicht. Es muss noch etwas geklärt werden, wie es funktioniert, aber bis dahin nehme ich die Antwort eines anderen hier und gebe Links zu anderen Quellen:


EDIT: Diese Antwort funktioniert nur für Angular 2 BETA. Der Router ist seit dieser Bearbeitung nicht für Angular 2 RC freigegeben. Ersetzen Sie stattdessen bei Verwendung von Angular 2 RC die Verweise auf router durch router-deprecated, um den Beta-Router weiterhin verwenden zu können.

Die Implementierung von Angular2-future erfolgt über den @Resolve-Dekorator. Bis dahin ist das nächstliegende Faksimile CanActivate Komponentendekorateur von Brandon Roberts. siehe https://github.com/angular/angular/issues/6611

Obwohl Beta 0 die Bereitstellung aufgelöster Werte für die Komponente nicht unterstützt, ist dies geplant, und es gibt auch eine Problemumgehung, die hier beschrieben wird: Resolve In Angular2 Routes verwenden

Ein Beta 1-Beispiel kann hier gefunden werden: http://run.plnkr.co/BAqA98lphi4rQZAd/#/resolved . Es verwendet eine sehr ähnliche Problemumgehung, verwendet jedoch RouteData-Objekt etwas genauer als RouteParams.

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

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

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

6
shannon

Setze einen lokalen Wert mit dem Beobachter

Vergessen Sie auch nicht, den Wert mit Dummy-Daten zu initialisieren, um uninitialized-Fehler 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);
      );
    }
}

Bei diesem Modell handelt es sich um ein Datenmodell, 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 keine Fehlermeldung ausgegeben, wenn die Vorlage vor dem Empfang der Daten gerendert wird.

Wenn Sie die Daten beibehalten möchten, um unnötige HTTP-Anforderungen zu vermeiden, sollten Sie dies idealerweise in einem Objekt speichern, das über einen eigenen Beobachter verfügt, den Sie abonnieren können.

4
Evan Plaice

Implementieren Sie die routerOnActivate in Ihrem @Component und 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 ist. Weitere Informationen finden Sie hier im ersten Kommentar von Brandon: 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- eingehend/

EDIT: Das Winkelteam plant zu diesem Zweck einen @Resolve-Dekorateur.

2
Langley

Eine schöne Lösung, die ich gefunden habe, ist auf der UI etwas zu tun:

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

Nur wenn: vendorServicePricing, quantityPricing und service geladen sind, wird die Seite gerendert.

2
Linu Radu

ich verwende Angular 6 mit dem Universal-Add-On .__ Ich habe die Antworten speziell die zweite gelesen. Aber ich habe den Punkt noch nicht verstanden. Ich habe einen Resolver, der aufruft und HttpClient XHR anfordert und an Komponente sendet, und die Komponente auf ngOnInit bindet Daten an die Ansicht. Das Problem liegt jedoch im Ansichtsquellenmodus. Es sind keine Daten aus der XHR-Antwort vorhanden, die in der Ansicht angezeigt werden.

0