Lazy Component Loading in Angular? Vielleicht sprechen wir über Lazy Loading-Module mit dem Angular-Router? Nein, wir sprechen über die Komponenten. Die aktuelle Version von Angular unterstützt nur das verzögerte Laden von Modulen. Ivy bietet dem Entwickler jedoch neue Möglichkeiten bei der Arbeit mit Komponenten.

Die faulen Lasten, die wir bisher benutzt haben: Routen
Lazy Loading ist ein großartiger Mechanismus. In Angular können Sie diesen Mechanismus ohne großen Aufwand verwenden, indem Sie Routen deklarieren, die das langsame Laden unterstützen. Hier ist ein Beispiel aus der Angular-
Dokumentation , das dies demonstriert:
const routes: Routes = [ { path: 'customer-list', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) } ];
Dank des obigen Codes wird ein separates Fragment für
customers.module
erstellt, das geladen wird, wenn der Benutzer entlang der
customer-list
navigiert.
Dies ist eine sehr gute Möglichkeit, um das Hauptpaket des Projekts zu verkleinern und das erstmalige Laden der Anwendung zu beschleunigen.
Aber wäre es nicht schön, das langsame Laden genauer steuern zu können? Was wäre zum Beispiel, wenn wir das verzögerte Laden einzelner Komponenten organisieren könnten?
Bisher war dies nicht möglich. Aber mit dem Aufkommen von Ivy änderte sich alles.
Efeu und das Konzept der "Lokalität"
Module sind das Grundkonzept und der Grundbaustein jeder Angular-Anwendung. Die Module deklarieren Komponenten, Richtlinien, Rohre, Dienste.
Eine moderne Angular-Anwendung kann ohne Module nicht existieren. Ein Grund dafür ist die Tatsache, dass ViewEngine den Modulen alle erforderlichen Metadaten hinzufügt.
Ivy geht dagegen anders vor. In Ivy kann eine Komponente ohne Modul existieren. Dies ist dank des Konzepts der Lokalität möglich. Das Wesentliche ist, dass alle Metadaten für die Komponente lokal sind.
Lassen Sie uns dies erklären, indem wir das mit Ivy generierte Paket es2015 analysieren.
Es2015-Bundle generiert mit IvyIm Abschnitt
Component code
können Sie sehen, dass das Ivy-System den Komponentencode gespeichert hat. Hier gibt es nichts Besonderes. Aber dann fügt Ivy dem Code einige Metadaten hinzu.
Das erste Stück Metadaten ist in der Abbildung als
Component factory
. Die Fabrik weiß, wie eine Komponente instanziiert wird. Im Abschnitt
Component metadata
Ivy zusätzliche Attribute wie
type
und
selectors
,
selectors
alles, was die Komponente während der Programmausführung benötigt.
Erwähnenswert ist, dass Ivy hier die
template
Funktion hinzufügt. Es wird in der
Compiled version of your template
angezeigt. Lassen Sie uns auf diese interessante Tatsache näher eingehen.
Die
template
Funktion ist eine kompilierte Version unseres HTML-Codes. Sie folgt den Anweisungen von Ivy, um ein DOM zu erstellen. Dies unterscheidet sich von der Funktionsweise von ViewEngine.
Das ViewEngine-System nimmt den Code und umgeht ihn. Angular führt den Code dann aus, wenn wir ihn verwenden.
Und mit dem von Ivy verwendeten Ansatz ist die Komponente, die die Angular-Befehle aufruft, für alles verantwortlich. Durch diese Änderung können Komponenten unabhängig voneinander existieren. Dies führt dazu, dass der Tree-Shake-Algorithmus auf den Angular-Basiscode angewendet werden kann.
Ein echtes Beispiel für das langsame Laden von Komponenten
Jetzt, da wir wissen, dass ein verzögertes Laden von Komponenten möglich ist, betrachten wir dies anhand eines konkreten Beispiels. Wir werden nämlich eine Quizanwendung erstellen, die den Benutzern Fragen mit Antwortoptionen stellt.
Die Anwendung zeigt ein Bild der Stadt und Optionen an, aus denen Sie den Namen dieser Stadt auswählen müssen. Sobald der Benutzer eine Option durch Klicken auf die entsprechende Schaltfläche auswählt, ändert sich diese Schaltfläche sofort und zeigt an, ob die Antwort richtig oder falsch war. Wenn der Hintergrund der Schaltfläche grün wird, war die Antwort korrekt. Wenn der Hintergrund rot wird, war die Antwort falsch.
Nachdem die Antwort auf die aktuelle Frage eingegangen ist, zeigt das Programm die folgende Frage an. So sieht es aus.
Quiz DemoDie Fragen, die das Programm dem Benutzer stellt, werden von der Komponente
QuizCardComponent
.
Lazy Komponentenladekonzept
Lassen Sie uns zunächst die allgemeine Idee des verzögerten Ladens der
QuizCardComponent
Komponente
QuizCardComponent
.
Der QuizCardComponent-KomponentenprozessNachdem der Benutzer das Quiz durch Klicken auf die Schaltfläche
Start quiz
starten gestartet hat, wird das verzögerte Laden der Komponente gestartet. Nachdem das Bauteil zu unserer Verfügung steht, legen wir es in einen Behälter.
Wir reagieren auf
questionAnsvered
Ereignisse einer „faulen“ Komponente genauso wie auf Ereignisse einer regulären Komponente. Nach dem Eintreten des Ereignisses zeigen wir die nächste Karte mit der Frage auf dem Bildschirm an.
Code-Analyse
Um zu verstehen, was beim verzögerten Laden einer Komponente passiert, beginnen wir mit einer vereinfachten Version von
QuizCardComponent
, in der die Eigenschaften der Frage
QuizCardComponent
.
Dann werden wir diese Komponente mit Winkelmaterialkomponenten erweitern. Und am Ende werden wir die Reaktion auf die von der Komponente erzeugten Ereignisse anpassen.
Lassen Sie uns das verzögerte Laden einer vereinfachten Version der
QuizCardComponent
Komponente mit der folgenden Vorlage organisieren:
<h1>Here's the question</h1> <ul> <li><b>Image: </b> {{ question.image }}</li> <li><b>Possible selections: </b> {{ question.possibleSelections.toString() }}</li> <li><b>Correct answer: </b> {{ question.correctAnswer }}</li> </ul>
Der erste Schritt besteht darin, ein Containerelement zu erstellen. Dazu können wir entweder ein reales Element wie
<div>
oder - auf
ng-container
zurückgreifen, wodurch wir auf eine zusätzliche HTML-Code-Ebene verzichten können. So sieht die Deklaration des Container-Elements aus, in das wir die „Lazy“ -Komponente einfügen:
<mat-toolbar color="primary"> <span>City quiz</span> </mat-toolbar> <button *ngIf="!quizStarted" mat-raised-button color="primary" class="start-quiz-button" (click)="startQuiz()">Start quiz</button> <ng-container #quizContainer class="quiz-card-container"></ng-container>
In der Komponente müssen Sie auf den Container zugreifen. Dazu verwenden wir die Annotation
@ViewChild
und
@ViewChild
uns mit, dass
ViewContainerRef
gelesen werden
ViewContainerRef
.
Beachten Sie, dass in Angular 9 die
static
Eigenschaft in der Annotation
@ViewChild
standardmäßig auf
false
ist:
@ViewChild('quizContainer', {read: ViewContainerRef}) quizContainer: ViewContainerRef;
Jetzt haben wir einen Container, in dem wir die "Lazy" -Komponente hinzufügen möchten. Als nächstes benötigen wir
ComponentFactoryResolver
und
Injector
. Beide können durch Rückgriff auf die Methode der Abhängigkeitsinjektion erworben werden.
Die
ComponentFactoryResolver
Entität ist eine einfache Registrierung, mit der die Beziehungen zwischen Komponenten und automatisch generierten
ComponentFactory
Klassen festgelegt werden, mit denen Komponenten instanziiert werden können:
constructor(private quizservice: QuizService, private cfr: ComponentFactoryResolver, private injector: Injector) { }
Jetzt haben wir alles, was nötig ist, um das Ziel zu erreichen.
startQuiz
den Inhalt der
startQuiz
Methode
startQuiz
und das verzögerte Laden der Komponente organisieren:
const {QuizCardComponent} = await import('./quiz-card/quiz-card.component'); const quizCardFactory = this.cfr.resolveComponentFactory(QuizCardComponent); const {instance} = this.quizContainer.createComponent(quizCardFactory, null, this.injector); instance.question = this.quizservice.getNextQuestion();
Wir können den Befehl
import
ECMAScript verwenden, um das verzögerte Laden von
QuizCardComponent
zu organisieren. Ein Import-Ausdruck gibt ein Versprechen zurück. Sie können damit entweder mit dem Konstrukt
async/await
oder mit dem Handler
.then
. Nachdem wir das Versprechen gelöst haben, verwenden wir die Destrukturierung, um eine Instanz der Komponente zu erstellen.
In diesen Tagen müssen Sie
ComponentFactory
, um die Abwärtskompatibilität zu gewährleisten. Zukünftig wird die entsprechende Leitung nicht mehr benötigt, da wir direkt mit dem Bauteil arbeiten können.
Die
ComponentFactory
Factory gibt uns
componentRef
. Wir übergeben
componentRef
und
Injector
an die
createComponent
Methode des Containers.
Die
createComponent
Methode gibt ein
ComponentRef
in dem die Komponenteninstanz enthalten ist. Wir übergeben mit
instance
die
@Input
der Komponente
@Input
.
In Zukunft kann dies wahrscheinlich mit der Angular
renderComponent
Methode erfolgen. Diese Methode ist noch experimentell. Es ist jedoch sehr wahrscheinlich, dass es sich um eine reguläre Angular-Methode handelt.
Hier finden Sie hilfreiche Materialien zu diesem Thema.
Dies ist alles, was benötigt wird, um das verzögerte Laden einer Komponente zu organisieren.
Lazy Component LoadingNachdem Sie auf die Schaltfläche
Start quiz
geklickt haben, beginnt das verzögerte Laden der Komponente. Wenn Sie die Registerkarte "
Network
der Entwicklertools öffnen, können Sie sehen, wie Sie das Codefragment, das in der Datei "
quiz-card-quiz-card-component.js
dargestellt wird, träge laden. Nach dem Laden und Bearbeiten der Komponente wird dem Benutzer eine Fragekarte angezeigt.
Komponentenerweiterung
Wir laden gerade die
QuizCardComponent
Komponente. Es ist sehr gut. Unsere Anwendung ist jedoch noch nicht besonders funktionsfähig.
Lassen Sie uns das beheben, indem Sie zusätzliche Features und Komponenten von Angular Material hinzufügen:
<mat-card class="quiz-card"> <mat-card-header> <div mat-card-avatar class="quiz-header-image"></div> <mat-card-title>Which city is this?</mat-card-title> <mat-card-subtitle>Click on the correct answer below</mat-card-subtitle> </mat-card-header> <img class="image" mat-card-image [src]="'assets/' + question.image" alt="Photo of a Shiba Inu"> <mat-card-actions class="answer-section"> <button [disabled]="answeredCorrectly !== undefined" *ngFor="let selection of question.possibleSelections" mat-stroked-button color="primary" [ngClass]="{ 'correct': answeredCorrectly && selection === question.correctAnswer, 'wrong': answeredCorrectly === false && selection === question.correctAnswer }" (click)="answer(selection)"> {{selection}} </button> </mat-card-actions> </mat-card>
Wir haben einige schöne Materialkomponenten in die Komponente aufgenommen. Was ist mit den entsprechenden Modulen?
Sie können natürlich zum
AppModule
hinzugefügt werden. Dies bedeutet jedoch, dass diese Module im "gierigen" Modus geladen werden. Und das ist keine gute Idee. Darüber hinaus schlägt die Montage des Projekts mit der folgenden Meldung fehl:
ERROR in src/app/quiz-card/quiz-card.component.html:9:1 - error TS-998001: 'mat-card' is not a known element: 1. If 'mat-card' is an Angular component, then verify that it is part of this module. 2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
Was zu tun ist? Wie Sie wahrscheinlich bereits verstanden haben, ist dieses Problem vollständig lösbar. Dies kann mit Hilfe von Modulen erfolgen.
Aber dieses Mal werden wir sie ein wenig anders als zuvor verwenden. Wir werden der gleichen Datei wie
QuizCardComponent
ein kleines Modul
QuizCardComponent
(in der Datei
quizcard.component.ts
):
@NgModule({ declarations: [QuizCardComponent], imports: [CommonModule, MatCardModule, MatButtonModule] }) class QuizCardModule { }
Beachten Sie, dass es keine
export
.
Diese Modulspezifikation gehört ausschließlich zu unserer Komponente und wird im Lazy Mode geladen. Folglich ist die einzige Komponente, die im Modul deklariert ist,
QuizCardComponent
. Der
import
nur die Module, die unsere Komponente benötigt.
Das neue Modul wird nicht exportiert, sodass im Greedy-Modus geladene Module es nicht importieren können.
Starten Sie die Anwendung neu und überprüfen Sie, wie sie sich verhält, wenn Sie auf die
Start quiz
klicken.
Geänderte AnwendungsanalyseGroßartig! Die
QuizCardComponent
Komponente
QuizCardComponent
im Lazy-Modus geladen und dem
ViewContainer
hinzugefügt. Alle notwendigen Abhängigkeiten werden damit geladen.
Wir werden das Anwendungspaket mit dem
webpack-bundle-analyze
Tool
webpack-bundle-analyze
, das vom entsprechenden npm-Modul bereitgestellt wird. Sie können den Inhalt der von Webpack erstellten Dateien visualisieren und das resultierende Schema untersuchen.
Analyse der AnwendungspaketeDie Größe des Hauptpakets der Anwendung beträgt ungefähr 260 KB. Wenn wir die
QuizCardComponent
Komponente zusammen mit ihr herunterladen,
QuizCardComponent
die Größe der heruntergeladenen Daten ungefähr 270 KB. Es stellte sich heraus, dass wir die Größe des Hauptpakets um 10 KB reduzieren konnten, indem wir nur eine Komponente im Lazy-Modus geladen haben. Gutes Ergebnis!
Der Code von
QuizCardComponent
nach dem Zusammenbau in eine separate Datei übertragen. Wenn Sie den Inhalt dieser Datei analysieren, stellt sich heraus, dass nicht nur der
QuizCardComponent
Code, sondern auch die in dieser Komponente verwendeten Materialmodule vorhanden sind.
Obwohl
QuizCardComponent
MatButtonModule
und
MatCardModule
,
MatCardModule
nur
MatCardModule
in die fertige
MatCardModule
. Der Grund dafür ist, dass wir das
MatButtonModule
im
AppModule
und die Schaltfläche
Start quiz
anzeigen. Infolgedessen fällt der entsprechende Code in ein anderes Fragment des Bündels.
Jetzt haben wir das faule Laden von
QuizCardComponent
. Diese Komponente zeigt eine Karte im Materialstil an, die ein Bild, eine Frage und Schaltflächen mit Antwortoptionen enthält. Was passiert jetzt, wenn Sie auf eine dieser Schaltflächen klicken?
Die Schaltfläche wird, je nachdem, ob sie die richtige oder die falsche Antwort enthält, beim Drücken grün oder rot. Was ist sonst noch los? Nichts Und wir brauchen, dass nach Beantwortung einer Frage eine Karte einer anderen Frage angezeigt wird. Repariere es.
Ereignisbehandlung
Die Anwendung zeigt keine neue Fragekarte an, wenn Sie auf die Antwortschaltfläche klicken, da wir noch keine Mechanismen zum Reagieren auf Ereignisse von Komponenten eingerichtet haben, die im verzögerten Modus geladen wurden. Wir wissen bereits, dass die
QuizCardComponent
Komponente mithilfe von
EventEmitter
Ereignisse
EventEmitter
. Wenn Sie sich die Definition der
EventEmitter
Klasse
EventEmitter
, können Sie feststellen, dass es sich um einen Nachkommen der
Subject
Klasse handelt:
export declare class EventEmitter<T extends any> extends Subject<T>
Dies bedeutet, dass
EventEmitter
über eine
subscribe
, mit der Sie die Reaktion des Systems auf auftretende Ereignisse konfigurieren können:
instance.questionAnswered.pipe( takeUntil(instance.destroy$) ).subscribe(() => this.showNewQuestion());
Hier abonnieren wir den
questionAnswered
Stream und rufen die
showNextQuestion
Methode auf, die die
lazyLoadQuizCard
Logik ausführt:
async showNewQuestion() { this.lazyLoadQuizCard(); }
Das
takeUntil(instance.destroy$)
wird benötigt, um die Subskription zu löschen, die ausgeführt wird, nachdem die Komponente zerstört wurde. Wenn der Hook
ngOnDestroy
Komponentenlebenszyklus von
ngOnDestroy
aufgerufen wird, wird
destroy$
mit
next
und
complete
aufgerufen.
Da die Komponente bereits geladen ist, führt das System keine zusätzliche HTTP-Anforderung durch. Wir verwenden den Inhalt des bereits vorhandenen Codefragments, erstellen eine neue Komponente und legen sie in den Container.
Haken für den Komponentenlebenszyklus
Nahezu alle Komponenten-Lifecycle-Hooks werden automatisch aufgerufen, wenn mit der
QuizCardComponent
Komponente mithilfe der Lazy-Loading-Technik gearbeitet wird. Aber ein Haken reicht nicht aus. Kannst du welche verstehen?
Haken für den KomponentenlebenszyklusDies ist der wichtigste Haken von
ngOnChanges
. Da wir die Eingabeeigenschaften der Komponenteninstanz selbst aktualisieren, sind wir auch dafür verantwortlich, den Lifecycle-Hook
ngOnChanges
.
Um den Hook
ngOnChanges
der
ngOnChanges
, müssen Sie selbst ein
SimpleChange
Objekt
SimpleChange
:
(instance as any).ngOnChanges({ question: new SimpleChange(null, instance.question, true) });
Wir rufen
ngOnChanges
Komponenteninstanz
ngOnChanges
manuell auf und übergeben ihr ein
SimpleChange
Objekt. Dieses Objekt gibt an, dass diese Änderung die erste ist, dass der vorherige Wert
null
ist und dass der aktuelle Wert eine Frage ist.
Großartig! Wir haben die Komponente mit Modulen von Drittanbietern geladen, auf die von ihr erzeugten Ereignisse reagiert und den Aufruf der erforderlichen Hooks des Komponentenlebenszyklus eingerichtet.
Hier ist der Quellcode für das fertige Projekt, an dem wir hier gearbeitet haben.
Zusammenfassung
Das langsame Laden von Komponenten bietet Angular-Entwicklern hervorragende Möglichkeiten zur Optimierung der Anwendungsleistung. Ihm stehen die Werkzeuge zur Verfügung, um die Zusammensetzung von Materialien, die im Lazy Mode geladen werden, sehr fein abzustimmen. Früher, als es möglich war, nur Routen im Lazy-Modus zu laden, hatten wir keine solche Genauigkeit.
Wenn wir Module von Drittanbietern in der Komponente verwenden, müssen wir uns leider auch um die Module kümmern. Es sei jedoch daran erinnert, dass sich dies in Zukunft ändern kann.
Die Ivy-Engine führte das Konzept der Lokalität ein, dank derer Komponenten unabhängig voneinander existieren können. Diese Veränderung ist die Grundlage für die Zukunft von Angular.
Sehr geehrte Leser! Planen Sie, in Ihren Angular-Projekten die Lazy-Component-Loading-Technik zu verwenden?
