Angular 9 und Ivy: Faulen Laden von Komponenten

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 Ivy

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

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

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

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

Groß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 Anwendungspakete

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

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

Source: https://habr.com/ru/post/de484618/


All Articles