The playground

More information here

Glattere UX beim Laden von Seiten mit Winkelauflösern

Als Entwickler möchten Sie immer Ihren Code optimieren. Dazu gehört die Geschwindigkeit, mit der Sie dem Benutzer die vollständig geladene Benutzeroberfläche präsentieren, die häufig von Daten aus einer Datenbank abhängt. Zwangsläufig suchen Sie nach Möglichkeiten, Daten sofort nach der Navigation zu einer neuen Seite / einem neuen Bereich Ihrer Anwendung aufzulösen, ohne dass der Benutzer […]

Als Entwickler möchten Sie immer Ihren Code optimieren. Dazu gehört die Geschwindigkeit, mit der Sie dem Benutzer die vollständig geladene Benutzeroberfläche präsentieren, die häufig von Daten aus einer Datenbank abhängt.

Zwangsläufig suchen Sie nach Möglichkeiten, Daten sofort nach der Navigation zu einer neuen Seite / einem neuen Bereich Ihrer Anwendung aufzulösen, ohne dass der Benutzer auf das stößt, was als Page JANK bezeichnet wird.

Dies ist der Fall, wenn sich die Seite beim Laden bestimmter Komponenten auf und ab bewegt. Es kann die UX bis zu dem Punkt erheblich beeinflussen, wo es ‚buggy‘ aussieht.

Angular bietet einen intuitiven Ansatz zum Vorab-Abrufen von Daten, bevor die Route geladen wird. bevor die navigierte Route aufgelöst wird.

Es wird ein Winkelauflöser genannt.

Ein Angular Resolver ist im Wesentlichen ein Angular Service. Eine injizierbare Klasse, die Sie einem Routingmodul in der Routenkonfiguration bereitstellen. Dieser spezielle Diensttyp wird injiziert und ausgeführt, wenn die enthaltende Route navigiert wird.

Der Resolver löst dann die Daten vor dem Laden der Seite auf, die über den Dienst ActivatedRoute verfügbar werden. Dies bietet eine einfache und effiziente Möglichkeit, um sicherzustellen, dass Ihr Benutzer die Daten so schnell wie möglich hat, bevor eine Komponente, die für das anfängliche Laden der Seite wichtig ist, diese benötigt.

Eine andere Möglichkeit, einen Winkelauflöser zu verwenden, besteht darin, ihn als Methode zum sofortigen Auffüllen der SEO-Metadaten zu verwenden.

Mit einem Resolver stellen Sie sicher, dass Daten vorhanden sind, bevor die Seite geladen wurde, und stellen sicher, dass bei der Initialisierung alles vorhanden ist, was es benötigt.

Lassen Sie uns den Winkelauflöser aufschlüsseln

Ein Winkelauflöser ist eine Klasse, die die Resolve Schnittstelle implementiert. Die Resolve -Schnittstelle erfordert, dass Sie eine Funktion innerhalb der Klasse mit dem Namen resolve implementieren.

Hier ist die Resolve-Schnittstellensignatur…

export interface Resolve<T> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return 'data';
}
}

Wie wir an der Schnittstellensignatur sehen können, ist ein generisches Argument erforderlich T welches der Typ unserer aufgelösten Daten sein wird.

Die resolve-Funktion gibt eine ObservablePromise oder nur die Daten vom Typ T zurück. Daher gibt es einen Vorschlag, dass dies asynchron gehandhabt werden sollte, insbesondere wenn die Daten aus der Datenbank abgerufen werden.

Der Hauptzweck der resolve-Funktion besteht darin, dass sie abgeschlossen werden muss. Diese Tatsache muss beim Abrufen von Daten in Observablen beachtet werden. Das Observable muss abgeschlossen sein. Immerhin ist es ein Resolver.

Wenn das Observable nicht abgeschlossen wird, werden die Daten niemals aufgelöst und die Seite wird niemals geladen. Daher müssen Sie den Punkt definieren, an dem Sie keine Werte mehr annehmen müssen und die Daten auflösen können, da Sie alles, was Sie benötigen, aus der Datenbank haben. Bei Verwendung asynchroner Datenströme wie Observables ist dies ein Anwendungsfall für Pipeable Operatoren von RXJS.

Die Pipeable-Operatoren, die einem in den Sinn kommen, wenn man darüber nachdenkt, einen Datenstrom basierend auf einer Bedingung zu vervollständigen, sind eine Kombination aus filtertakefirst . Mit dieser Kombination können Sie alle Werte filtern, die Sie nicht übernehmen möchten, z. B. null oder undefined oder ein leeres Array , dann take der erste gültige Wert mit take(1).

Operatoren, die benötigt werden, um eine Observable frühzeitig abzuschließen, wenn Probleme oder Fehler auftreten, in denen Sie null zurückgeben oder umleiten möchten, sind catchError und timeout . Eine Kombination aus timeout und catchError ist nützlich, wenn Ihre Daten zu lange dauern und Sie null zurückgeben möchten, damit Sie es innerhalb der Komponente erneut versuchen können oder umleiten möchten.

Wenn sich Ihre Daten nicht schnell auflösen, auf komplexen Filtern, Logik und großen Mengen an Datenbankaufrufen basieren, treten wahrscheinlich von Zeit zu Zeit Probleme auf.

Es ist am besten, die geringste Anzahl von Datenbankaufrufen und die Mindestdaten zu bestimmen, die zum erfolgreichen und ordnungsgemäßen Laden der Seite erforderlich sind.

Folglich können Sie davon profitieren, vor der Implementierung Ihres Resolvers einige Zeit damit zu verbringen, sich darauf zu konzentrieren, den ‚above the Fold‘ -Inhalt von Daten zu trennen, die beim Initialisieren der Seite geladen werden können.

Daher können Sie die für eine reibungslose UX erforderlichen Daten von den übrigen Daten trennen, die von der Komponente und nicht vom Resolver aufgerufen werden könnten.

Sie können sich dann ausschließlich mit dem minimalen Inhalt über dem Fold über den Resolver befassen.

Dieser dynamische Ansatz zum Laden von Seiten kann durch die Verwendung von Skeletten unterstützt werden. Wenn der Benutzer sofort nach unten scrollt, können Sie dem Benutzer den Hinweis geben, dass der Inhalt geladen wird, und anschließend die UX verbessern.

Schritt 1: Erstellen des Resolvers

Wir müssen den Winkelauflöser erstellen. Es gibt jedoch keinen Angular CLI-Befehl, der einen Resolver generiert. Daher müssen wir den Dekorator (Resolver-Metadaten) selbst schreiben.

Glücklicherweise sind es nur ein paar Codezeilen, die das Boilerplate für einen Resolver bilden, und wir können den injizierbaren Decorator aus einem vorhandenen Service nehmen, wenn Sie Schwierigkeiten haben, sich daran zu erinnern.

Kommentieren Sie die Profilauflöserklasse mit einem injizierbaren Dekorateur

Zunächst stellen wir dem injizierbaren Dekorateur in der Konfiguration providedIn: any zur Verfügung.

@Injectable({ providedIn: 'any'})

Wir werden dann unseren Resolver benennen, indem wir die Konvention Resolver anhängen . In diesem Beispiel lösen wir Profildaten (Benutzerdaten) auf, daher nennen wir sie ProfileResolver .

Da es sich um einen Resolver handelt und Angular die Funktion von Resolvern erkennt, können wir die Klasse Resolve implementieren, die die Signatur bereitstellt, die wir in unserer Resolve-Funktion implementieren müssen, um die Daten erfolgreich aufzulösen.

@Injectable({providedIn: 'any'})
export class ProfileResolver implements Resolve<Profile> {
}

Unsere resolve-Funktion gibt ein Observable mit Daten zurück, die der Profile Schnittstelle entsprechen. Daher werden wir die Profile Schnittstelle als generisches Argument für die Resolver-Klasse und die resolve() Funktion bereitstellen. Auf diese Weise haben wir die Anforderungen an einen Resolver erfüllt.

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return;
}

Die resolve-Implementierung gibt uns zwei Parameter, wenn sie benötigt werden, route und state . Diese werden automatisch ausgefüllt und sind über unsere resolve() -Funktion zugänglich.

Als nächstes müssen wir echte Daten aus der Datenbank auflösen, was unser nächster Schritt sein wird.

Abrufen der Daten aus der Datenbank

Um die Daten für unsere resolve-Funktion abzurufen, müssen wir den Dienst injizieren, der die Daten bereitstellt. einer, der mit der Datenbank interagiert.

Wir nehmen, was wir brauchen, um es schnell zu lösen, damit der Benutzer schnell und erfolgreich navigiert. Für die Zwecke dieser Demo werden wir uns keine Gedanken über den zugrunde liegenden Dienst machen, der sich mit der Datenbank befasst. Wir werden den Dienst einfach mithilfe der Abhängigkeitsinjektion in das Konstruktorargument für unsere ProfileResolver -Klasse injizieren.

Da unsere Daten in Form eines beobachtbaren Datenstroms mit mehreren asynchron emittierenden Werten vorliegen, müssen wir nur take(1) mit dem pipeable Operator take von rxjs/operator importiert werden . Andernfalls würde das Observable niemals abgeschlossen und der Resolver niemals … aufgelöst.

Wir brauchen nur einen Wert und take vervollständigt das Observable für uns.

Es ist so einfach, einen Resolver zu erstellen; Wir müssen nur das Observable in der resolve() Funktion zurückgeben, deren Abonnement angular übernimmt.

Die ProfileResolver-Klasse

Wir behandeln alle Fehler beim Abrufen der Daten, indem wir auf eine übergeordnete Route umleiten.

Bonus: Füllen Sie dynamische SEO-Metadaten schnell aus, bevor die Route geladen wurde

Die Vorteile des sofortigen Auffüllens unserer <meta> -Tags liegen auf der Hand. Je schneller unsere SEO-Metadaten ausgefüllt werden, desto schneller spiegelt unsere SEO den Seiteninhalt korrekt und genau wider.Dies bedeutet, dass es für Roboter, wie sie von Suchmaschinen wie Google und Bing betrieben werden, einfacher und schneller ist, Ihre Website zu crawlen und den Inhalt abzurufen.

Dies ist bei vorgerenderten Seiten oder solchen, die von Angular Universal gerendert werden, nicht so wichtig, da das gesamte Rendering abgeschlossen ist, bevor die Roboter den Inhalt erhalten.

Wenn Sie sich jedoch auf die (oft fragwürdige) Fähigkeit von Google-Robotern verlassen, Javascript für Ihre SEO zu analysieren, oder Sie haben eine On-Demand-Lösung wie Puppeteer, die die Gewissheit benötigt, dass die SEO korrekt ist, bevor das gerenderte DOM zurückgegeben wird, dann sollte ein Resolver, der SEO enthält, helfen. Es hilft also, wenn der Crawler zeitlich begrenzt ist.

Es trennt auch Bedenken von der Komponente, so dass sich die Komponente mit nichts SEO-Bezogenem befassen muss. Einer der Hauptgründe, warum ich Resolver mag.

Schritt 2: Injizieren Sie den Resolver in das Routing-Modul

Das ProfileRoutingModule, in dem wir unseren Resolver bereitstellen, ist ein Lazy Loaded-Modul. Daher ist unser Stammpfad leer mit dem Parameter token userSlug , den wir benötigen, um die korrekten Profildaten abzurufen.

Um unseren Resolver bereitzustellen, geben wir einfach ein Objekt mit dem Namen unserer Daten als Schlüssel und dem spezifischen Resolver als Wert an, der für die Auflösung dieser Daten verantwortlich ist.

Sie können den Schlüssel beliebig benennen, aber wir nennen ihn einfach data.

Unser ProfileRoutingModule

Das ist alles, was im Routing-Modul benötigt wird, damit wir unseren Resolver verwenden können.

Als nächstes müssen wir unsere Daten in der Komponente abrufen und verwenden.

Schritt 3: Initialisieren Sie die Komponente mit aufgelösten Daten

Nachdem wir unsere Daten zur Routenaktivierung aufgelöst haben, können Sie über den Dienst ActivatedRoute auf die Daten zugreifen. Da wir es mit Observablen zu tun haben, erstellen wir in der gesamten Anwendung einen Stream, der an die Eigenschaft data bindet, die unsere aufgelösten Daten sein wird.

Zuerst injizieren wir die ActivatedRoute in den Konstruktor unserer ProfileComponent . Als nächstes weisen wir this.route.data dem profile$ observable zu. Wir möchten auch zur Verwendung eines Observable wechseln, wenn aktualisierte Daten aus der Datenbank eintreffen, damit wir bei der Interaktion mit der App über neue Daten verfügen.

Dazu verwenden wir startWith so dass wir unseren Stream mit dem Wert starten, der von this.route.snapshot.data leicht zugänglich ist. Wir greifen dann auf die data Eigenschaft wie this.route.snapshot.datastartWith gibt zunächst einen Wert als erste Emission unseres Streams an.

Unsere Profilkomponente

Was sofort zugängliche Daten für die Komponente bewirken

Sofort zugängliche Daten reduzieren den Zeitaufwand für das Laden der einzelnen Teile dieser Seite, was vom Benutzer beobachtet wird. Wenn Sie keinen solchen Resolver verwenden, wird die Seite möglicherweise fragmentiert geladen, was optisch nicht ansprechend ist.

Folglich müssen Sie darauf achten, welche Elemente Ihrer HTML-Vorlagen von welchen Daten abhängen. Sie sollten dann Ihren Resolver schreiben, um diese Elemente und den Gesamteffekt auf das Laden von Seiten UX zu unterstützen.

Es gibt mehrere Möglichkeiten, wie die Komponente fragmentiert geladen werden kann

  • Eine davon ist, wenn Sie eine ngIf in mehreren Teilen Ihrer HTML-Vorlage haben.
  • Ein anderer ist ngFor .

Es empfiehlt sich, die Anzahl der einzelnen ngIf zu begrenzen, die Sie schreiben, um die Größenänderung des Browsers zu begrenzen.

Das Laden der Seite vor dem Abrufen der Daten kann dazu führen, dass Teile Ihrer Seite ständig springen, verzögert und in der Größe geändert werden, wodurch die UX leidet.

Die Implementierung eines Resolvers könnte den Unterschied zwischen 3-5 Sekunden Springen und Größenänderung gegenüber 0,5 Sekunden ausmachen, was oft zu schnell ist, um der gesamten UX zu schaden.

Das war’s! Wir haben einen Resolver mit einer verbesserten UX beim Laden von Seiten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.