Chargement de composants paresseux dans Angular? Peut-être que nous parlons de modules de chargement paresseux utilisant le routeur angulaire? Non, nous parlons des composants. La version actuelle d'Angular ne prend en charge que le chargement de module paresseux. Mais Ivy donne au développeur de nouvelles opportunités pour travailler avec des composants.

La charge paresseuse que nous avons utilisée jusqu'à présent: les routes
Le chargement paresseux est un excellent mécanisme. Dans Angular, vous pouvez utiliser ce mécanisme sans avoir à faire presque aucun effort en déclarant des itinéraires qui prennent en charge le chargement paresseux. Voici un exemple de la
documentation Angular
qui le démontre:
const routes: Routes = [ { path: 'customer-list', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) } ];
Grâce au code ci-dessus, un fragment séparé pour le
customers.module
sera créé, qui sera chargé lorsque l'utilisateur naviguera le long de l'itinéraire de la
customer-list
des
customer-list
.
C'est un très bon moyen de réduire la taille du bundle principal du projet et d'accélérer le chargement initial de l'application.
Mais malgré cela, ne serait-il pas agréable de pouvoir contrôler plus précisément le chargement paresseux? Par exemple, que faire si nous pouvions organiser le chargement paresseux de composants individuels?
Jusqu'à présent, cela n'a pas été possible. Mais tout a changé avec l'avènement d'Ivy.
Ivy et le concept de "localité"
Les modules sont le concept de base et le bloc de construction de base de chaque application angulaire. Les modules déclarent des composants, des directives, des tuyaux, des services.
Une application angulaire moderne ne peut exister sans modules. Une des raisons à cela est le fait que ViewEngine ajoute toutes les métadonnées nécessaires aux modules.
Ivy, en revanche, adopte une approche différente. Dans Ivy, un composant peut exister sans module. Cela est possible grâce au concept de localité. Son essence est que toutes les métadonnées sont locales au composant.
Expliquons cela en analysant le bundle es2015 généré à l'aide d'Ivy.
Ensemble Es2015 généré à l'aide de IvyDans la section
Component code
du
Component code
, vous pouvez voir que le système Ivy a enregistré le code du composant. Il n'y a rien de spécial ici. Mais Ivy ajoute ensuite des métadonnées au code.
La première partie des métadonnées est représentée sur la figure comme une
Component factory
. L'usine sait instancier un composant. Dans la section
Component metadata
du
Component metadata
, Ivy place des attributs supplémentaires, tels que le
type
et les
selectors
, c'est-à-dire tout ce dont le composant a besoin pendant l'exécution du programme.
Il convient de mentionner qu'Ivy ajoute ici la fonction de
template
. Il est affiché dans la
Compiled version of your template
section de
Compiled version of your template
. Arrêtons-nous plus en détail sur ce fait intéressant.
La fonction
template
est une version compilée de notre code HTML. Elle suit les instructions Ivy pour créer un DOM. Ceci est différent du fonctionnement de ViewEngine.
Le système ViewEngine prend le code et le contourne. Angular exécutera alors le code si nous l'utilisons.
Et avec l'approche utilisée par Ivy, le composant qui invoque les commandes angulaires est en charge de tout. Cette modification permet aux composants d'exister indépendamment, ce qui conduit au fait que l'algorithme de tremblement d'arbre peut être appliqué au code de base angulaire.
Un vrai exemple de chargement de composants paresseux
Maintenant que nous savons que le chargement de composants paresseux est possible, considérons-le avec un exemple réel. À savoir, nous allons créer une application Quiz qui pose des questions à l'utilisateur avec des options de réponse.
L'application affiche une image de la ville et des options à partir desquelles vous devez choisir le nom de cette ville. Dès que l'utilisateur sélectionne une option en cliquant sur le bouton correspondant, ce bouton change immédiatement, indiquant si la réponse était bonne ou mauvaise. Si l'arrière-plan du bouton devient vert, la réponse était correcte. Si l'arrière-plan devient rouge, cela signifie que la réponse était incorrecte.
Une fois la réponse à la question en cours reçue, le programme affiche la question suivante. Voici à quoi ça ressemble.
Démo du quizLes questions que le programme pose à l'utilisateur sont représentées par le composant
QuizCardComponent
.
Concept de chargement de composants paresseux
Illustrons d'abord l'idée générale du chargement paresseux du composant
QuizCardComponent
.
Le processus du composant QuizCardComponentAprès que l'utilisateur a commencé le quiz en cliquant sur le bouton
Start quiz
, nous commençons le chargement paresseux du composant. Une fois le composant à notre disposition, nous le plaçons dans un conteneur.
Nous réagissons aux événements
questionAnsvered
d'un composant «paresseux» de la même manière que nous réagirions aux événements d'un composant régulier. À savoir, après l'occurrence de l'événement, nous affichons à l'écran la prochaine carte avec la question.
Analyse de code
Afin de comprendre ce qui se passe lors du chargement paresseux d'un composant, nous commençons par une version simplifiée de
QuizCardComponent
, qui affiche les propriétés de la question.
Ensuite, nous développerons ce composant en utilisant des composants de matériau angulaire. Et à la fin, nous ajusterons la réaction aux événements générés par le composant.
QuizCardComponent
le chargement paresseux d'une version simplifiée du composant
QuizCardComponent
, qui a le modèle suivant:
<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>
La première étape consiste à créer un élément conteneur. Pour ce faire, nous pouvons soit utiliser un élément réel, comme
<div>
, ou - recourir à
ng-container
, ce qui nous permet de nous passer d'un niveau supplémentaire de code HTML. Voici à quoi ressemble la déclaration de l'élément conteneur dans laquelle nous plaçons le composant «paresseux»:
<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>
Dans le composant, vous devez accéder au conteneur. Pour ce faire, nous utilisons l'annotation
@ViewChild
et nous faisons savoir que nous voulons lire
ViewContainerRef
.
Notez que dans Angular 9, la propriété
static
dans l'annotation
@ViewChild
est définie sur
false
par défaut:
@ViewChild('quizContainer', {read: ViewContainerRef}) quizContainer: ViewContainerRef;
Nous avons maintenant un conteneur dans lequel nous voulons ajouter le composant "paresseux". Ensuite, nous avons besoin de
ComponentFactoryResolver
et
Injector
. Les deux peuvent être acquis en recourant à la méthodologie de l'injection de dépendance.
L'entité
ComponentFactoryResolver
est un simple registre qui établit la relation entre les composants et les classes
ComponentFactory
générées automatiquement qui peuvent être utilisées pour instancier des composants:
constructor(private quizservice: QuizService, private cfr: ComponentFactoryResolver, private injector: Injector) { }
Nous avons maintenant tout ce qui est nécessaire pour atteindre l'objectif. Travaillons sur le contenu de la méthode
startQuiz
et organisons le chargement paresseux du composant:
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();
Nous pouvons utiliser la commande
import
ECMAScript pour organiser le chargement paresseux de
QuizCardComponent
. Une expression d'importation renvoie une promesse. Vous pouvez travailler avec lui soit en utilisant la construction
async/await
.then
soit en utilisant le gestionnaire
.then
. Après avoir résolu la promesse, nous utilisons la déstructuration pour créer une instance du composant.
De nos jours, vous devez utiliser
ComponentFactory
pour fournir une compatibilité descendante. À l'avenir, la ligne correspondante n'est plus nécessaire, car nous pourrons travailler directement avec le composant.
L'usine
ComponentFactory
nous donne
componentRef
. Nous passons
componentRef
et
Injector
à la méthode
createComponent
du conteneur.
La méthode
createComponent
renvoie un
ComponentRef
où l'instance de composant est contenue. Nous utilisons l'
instance
pour transmettre les
@Input
du composant
@Input
.
À l'avenir, tout cela peut probablement être fait en utilisant la méthode Angular
renderComponent
. Cette méthode est encore expérimentale. Cependant, il est très probable que cela deviendra une méthode angulaire régulière.
Voici des documents utiles sur ce sujet.
C'est tout ce qui est nécessaire pour organiser le chargement paresseux d'un composant.
Chargement de composants paresseuxAprès avoir appuyé
Start quiz
bouton
Start quiz
, le chargement paresseux du composant commence. Si vous ouvrez l'onglet
Network
des outils de développement, vous pouvez voir le processus de chargement paresseux du fragment de code représenté par le fichier
quiz-card-quiz-card-component.js
. Après le chargement et le traitement du composant est affiché, l'utilisateur voit une carte de question.
Extension de composant
Nous
QuizCardComponent
actuellement le composant
QuizCardComponent
. C’est très bien. Mais notre application n'est pas encore particulièrement fonctionnelle.
Corrigeons cela en y ajoutant des fonctionnalités et des composants supplémentaires de matériau angulaire:
<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>
Nous avons inclus de beaux composants matériels dans le composant. Et les modules correspondants?
Ils peuvent bien sûr être ajoutés à l'
AppModule
. Mais cela signifie que ces modules se chargeront en mode "gourmand". Et ce n'est pas une bonne idée. De plus, l'assemblage du projet échouera, avec le message suivant:
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.
Que faire Comme vous l'avez probablement déjà compris, ce problème est complètement résoluble. Cela peut être fait à l'aide de modules.
Mais cette fois, nous les utiliserons un peu différemment qu'auparavant. Nous allons ajouter un petit module dans le même fichier que
QuizCardComponent
(dans le fichier
quizcard.component.ts
):
@NgModule({ declarations: [QuizCardComponent], imports: [CommonModule, MatCardModule, MatButtonModule] }) class QuizCardModule { }
Notez qu'il n'y a pas de déclaration d'
export
.
Cette spécification de module appartient exclusivement à notre composant, chargé en mode paresseux. Par conséquent, le seul composant déclaré dans le module sera
QuizCardComponent
. La section d'importation importe uniquement les modules dont notre composant a besoin.
Nous n'exportons pas le nouveau module afin que les modules chargés en mode gourmand ne puissent pas l'importer.
Redémarrez l'application et regardez comment elle se comporte lorsque vous cliquez sur le
Start quiz
.
Analyse d'application modifiéeSuper! Le composant
QuizCardComponent
chargé en mode
ViewContainer
et ajouté au
ViewContainer
. Toutes les dépendances nécessaires en sont chargées.
Nous analyserons le bundle d'application à l'aide de l'
webpack-bundle-analyze
fourni par le module npm correspondant. Il vous permet de visualiser le contenu des fichiers produits par Webpack et d'examiner le schéma résultant.
Analyse du bundle d'applicationsLa taille du bundle principal de l'application est d'environ 260 Ko. Si nous téléchargions le composant
QuizCardComponent
avec lui, la taille des données téléchargées serait d'environ 270 Ko. Il s'avère que nous avons pu réduire la taille du paquet principal de 10 Ko, en ne chargeant qu'un seul composant en mode paresseux. Bon résultat!
Le code de
QuizCardComponent
après l'assemblage obtient un fichier séparé. Si vous analysez le contenu de ce fichier, il s'avère qu'il n'y a pas seulement le code
QuizCardComponent
, mais aussi les modules Material utilisés dans ce composant.
Même si
QuizCardComponent
utilise
MatButtonModule
et
MatCardModule
, seul
MatCardModule
pénètre dans le
MatCardModule
code fini. La raison en est que nous utilisons également le
MatButtonModule
dans l'
AppModule
, affichant le bouton
Start quiz
. En conséquence, le code correspondant tombe dans un autre fragment du bundle.
Nous avons maintenant organisé le chargement paresseux de
QuizCardComponent
. Ce composant affiche une carte, conçue dans le style du matériel, contenant une image, une question et des boutons avec des options de réponse. Que se passe-t-il maintenant si vous cliquez sur l'un de ces boutons?
Le bouton, selon qu'il contient la bonne ou la mauvaise réponse, devient vert ou rouge lorsqu'il est enfoncé. Que se passe-t-il d'autre? Rien. Et nous avons besoin qu'après avoir répondu à une question, une carte d'une autre question soit affichée. Réparez-le.
Gestion des événements
L'application n'affiche pas de nouvelle carte de question lorsque vous cliquez sur le bouton de réponse car nous n'avons pas encore mis en place de mécanismes pour répondre aux événements des composants chargés en mode paresseux. Nous savons déjà que le composant
QuizCardComponent
génère des événements à l'aide de
EventEmitter
. Si vous regardez la définition de la classe
EventEmitter
, vous pouvez découvrir qu'il s'agit d'un descendant de
Subject
:
export declare class EventEmitter<T extends any> extends Subject<T>
Cela signifie que
EventEmitter
a une méthode d'
subscribe
, qui vous permet de configurer la réponse du système aux événements qui se produisent:
instance.questionAnswered.pipe( takeUntil(instance.destroy$) ).subscribe(() => this.showNewQuestion());
Ici, nous nous
showNextQuestion
au flux
questionAnswered
et appelons la méthode
showNextQuestion
, qui exécute la logique
lazyLoadQuizCard
:
async showNewQuestion() { this.lazyLoadQuizCard(); }
La construction
takeUntil(instance.destroy$)
est nécessaire pour effacer l'abonnement qui est exécuté après la destruction du composant. Si le hook
ngOnDestroy
du cycle de vie du composant
ngOnDestroy
est appelé,
destroy$
est appelé avec
next
et
complete
.
Le composant étant déjà chargé, le système n'effectue pas de requête HTTP supplémentaire. Nous utilisons le contenu du fragment de code que nous avons déjà, créons un nouveau composant et le mettons dans le conteneur.
Crochets de cycle de vie des composants
Presque tous les crochets du cycle de vie des composants sont automatiquement appelés lorsque vous travaillez avec le composant
QuizCardComponent
à l'aide de la technique de chargement paresseux. Mais un crochet ne suffit pas. Pouvez-vous comprendre lequel?
Crochets de cycle de vie des composantsC'est le
ngOnChanges
plus important de
ngOnChanges
. Puisque nous mettons nous-mêmes à jour les propriétés d'entrée de l'instance de composant, nous sommes également responsables de l'appel du
ngOnChanges
cycle de vie
ngOnChanges
.
Pour appeler le hook
ngOnChanges
de l'instance de
ngOnChanges
, vous devez construire vous-même un objet
SimpleChange
:
(instance as any).ngOnChanges({ question: new SimpleChange(null, instance.question, true) });
Nous appelons manuellement
ngOnChanges
instance de composant et lui passons un objet
SimpleChange
. Cet objet indique que cette modification est la première, que la valeur précédente est
null
et que la valeur actuelle est une question.
Super! Nous avons chargé le composant avec des modules tiers, répondu aux événements qu'il a générés et configuré l'appel des hooks nécessaires du cycle de vie du composant.
Voici le code source du projet fini sur lequel nous travaillions ici.
Résumé
Le chargement de composants paresseux offre aux développeurs Angular de grandes opportunités pour optimiser les performances des applications. À sa disposition se trouvent les outils pour affiner très finement la composition des matériaux chargés en mode paresseux. Auparavant, lorsqu'il était possible de charger uniquement des itinéraires en mode paresseux, nous n'avions pas une telle précision.
Malheureusement, lorsque vous utilisez des modules tiers dans le composant, nous devons également prendre soin des modules. Cependant, il convient de rappeler qu'à l'avenir, cela pourrait changer.
Le moteur Ivy a introduit le concept de localité, grâce auquel les composants peuvent exister indépendamment. Ce changement est le fondement de l'avenir d'Angular.
Chers lecteurs! Envisagez-vous d'utiliser la technique de chargement de composants paresseux dans vos projets Angular?
