Transclusion, Injection et Procrastination

Il y a quelques mois, Kapunahele Wong et moi faisions un brainstorming sur des idées de propositions de talk que nous pourrions faire ensemble. C’était à peu près au moment où j’ai décidé d’explorer Ivy, donc j’ai pensé que cela pourrait être un bon ajustement pour une conférence. Kapunahele, cependant, avait une idée différente:

Twitter, là où les choses amusantes se passent!

C’était la première fois que j’entendais parler des arbres injecteurs, donc j’étais intrigué. Kapunahele a partagé avec moi certaines de ses recherches préliminaires, et j’ai appris que l’algorithme de résolution des dépendances de l’Injecteur Angular n’était pas aussi simple que je le pensais auparavant.

En fait, depuis que les NgModules ont été introduits dans la 5e release candidate d’Angular 2.0.0, je ne fournissais que des services au niveau du module, en utilisant la propriété familière providers:

Ou depuis Angular 6, en utilisant la propriété providedIn du décorateur Injectable:

De toute façon, j’ai toujours déclaré mes services au niveau du module, et je n’ai jamais prêté trop d’attention aux autres possibilités.

Enter Injector Trees 🌴

Kapunahele et moi avons décidé de soumettre notre exposé sur les Injector Trees à Angular Connect. Plusieurs mois plus tard, le message suivant a atterri dans ma boîte de réception :

Nous étions gonflés à bloc, notre exposé a été accepté pour la conférence ! 😊

Nous avons passé les semaines suivantes à explorer tous les coins cachés du fonctionnement de l’injection de dépendances dans Angular. Dans ce billet de blog, je vais partager certains d’entre eux avec vous.

Démarrer simplement

Nous allons commencer avec une application Angular simple. Cette app a un service « Kingdom » et un seul composant qui injecte Kingdom et affiche le nom du Royaume:

J’ai décidé d’aller avec un Dragon pour l’exemple, car j’aime les utiliser au lieu des points-virgules dans mon code.

Pour rendre les choses un peu plus intéressantes, pimentons notre app avec un composant Licorne. Ce composant imprime le nom du Royaume où il vit:

Donc nous avons un composant d’application, et une licorne à l’intérieur. Super!

Maintenant, que se passe-t-il lorsque nous changeons la définition de notre AppComponent pour fournir une valeur différente pour le KingdomService?

Nous pouvons le faire en ajoutant la ligne suivante à la déclaration du composant:

providers: 

Comment cela affectera-t-il notre application ? Essayons-le et voyons:

Comme vous pouvez le voir, la valeur que nous avons définie pour KingdomService dans notre AppComponent a pris le pas sur le service défini dans notre AppModule (il n’y a pas été directement défini, plutôt en utilisant providedIn, mais le résultat est le même).

Arbre des éléments, arbre des modules

La raison pour laquelle nous voyons des zombies est la façon dont la résolution de dépendance fonctionne dans Angular. Elle recherche d’abord l’arbre des éléments, et seulement ensuite l’arbre des modules. Considérons UnicornComponent. Il injecte une instance de KingdomService à l’intérieur de son constructeur :

constructor(public kingdom: KingdomService) {}

Lorsqu’Angular crée ce composant, il regarde d’abord s’il existe des providers définis sur le même élément que le composant. Ces providers ont pu être enregistrés sur le composant lui-même, ou en utilisant une directive. Dans ce cas, nous n’avons pas fourni de valeur pour KingdomService à l’intérieur du UnicornComponent, et nous n’avons pas non plus de directive sur l’élément <app-unicorn>.

La recherche continue ensuite en haut de l’arbre des éléments, en allant jusqu’à AppComponent. Ici, Angular trouve que nous avons une valeur pour KingdomService, donc il injecte cette valeur et arrête la recherche là. Donc, dans ce cas, Angular n’a même pas regardé l’arbre des modules.

Angular est paresseux !

Comme nous, les programmeurs, Angular est aussi un procrastinateur. Il ne crée pas d’instances de services à moins qu’il n’en ait vraiment besoin. Vous pouvez le confirmer en ajoutant une instruction console.log au constructeur de KingdomService (vous pouvez également ajouter une alert('I love marquees') si vous vous sentez nostalgique aujourd’hui).

Vous verrez que l’instruction console.log n’est jamais exécutée – car Angular ne crée pas le service. Si vous supprimez la déclaration providers: de la AppComponent (ou la déplacez vers la UnicornComponent, de sorte qu’elle ne s’applique qu’à la licorne et à ses éléments enfants), vous devriez commencer à voir le message de journal dans votre console.

Maintenant, Angular n’a pas le choix – il ne trouve pas la KingdomService en regardant dans l’arbre des éléments. Donc, il va d’abord dans l’arbre des injecteurs de modules, puis voit que nous avons fourni le service à cet endroit, et enfin crée une instance de celui-ci. Par conséquent, le code à l’intérieur du constructeur s’exécute, et vous serez en mesure de voir l’impression de débogage que vous avez mise là.

Invasion de directives!

J’ai mentionné que les directives peuvent également fournir des valeurs pour l’injection de dépendances. Faisons l’expérience de cela. Nous allons définir une nouvelle directive appInvader, qui va changer la valeur du royaume en 👾.
Pourquoi ? Parce qu’elles étaient si adorables dans la conférence VR + Angular qu’Alex Castillo et moi avons donnée à ng-conf.

Puis, nous allons ajouter un autre élément <app-unicorn>, et lui appliquer la nouvelle directive appInvader:

Comme prévu, la nouvelle licorne vit dans le royaume de 👾. C’est parce que la directive a fourni une valeur pour KingdomService. Et comme expliqué ci-dessus, Angular commence la recherche à partir de l’élément actuel, en regardant le composant et toutes les directives, et seulement s’il ne peut pas trouver la valeur demandée à cet endroit, il continue à remonter l’arbre des éléments (et ensuite les modules).

Regardons quelque chose d’un peu plus compliqué:

Ajouter une forêt de projection de contenu à l’application !

Nous allons ajouter un nouveau composant Forêt à notre app, et mettre certaines des licornes à l’intérieur de cette forêt, parce que c’est là que vivent les licornes (un gars au hasard a dit ça sur quora, donc ça doit être vrai).

Le composant Forêt est simplement un conteneur, qui utilise la projection de contenu pour afficher ses enfants au-dessus d’un fond vert « forestier » :

Donc, nous voyons les éléments du composant AppForest sur un fond d’herbe, et ensuite, tout le contenu projeté au-dessus d’un fond vert vif. Et puisque nous avons fourni une valeur pour KingdomService à l’intérieur de notre composant d’application, tout ce qui est à l’intérieur en hérite (sauf la seule licorne avec la directive appInvader).

Mais que se passe-t-il si nous fournissons une nouvelle valeur pour KingdomService à l’intérieur du ForestComponent ? Est-ce que le contenu projeté (qui a été défini dans le modèle pour AppComponent) obtiendra également cette nouvelle valeur pour le royaume ? Ou sera-t-il toujours dans le royaume 🧟 ? Pouvez-vous deviner ?

Ils avaient l’habitude de l’appeler Transclusion. Maintenant, on l’appelle « Projection de contenu ». Photo by ng-conf

Le magicien de la forêt

Nous allons ajouter une seule ligne à notre exemple précédent, en fournissant un 🧙 royaume pour le ForestComponent:

providers: 

Et voici le résultat:

Maintenant c’est intéressant – nous voyons un mélange de royaumes à l’intérieur de la forêt ! L’élément de la forêt lui-même vit dans le royaume 🧙, mais le contenu projeté semble avoir une personnalité divisée : les licornes appartiennent également au royaume 🧙, mais le texte au-dessus d’elles montre 🧟 royaume?

Nous avons défini à la fois ces licornes et le texte au même endroit, lignes 12-15 du modèle app.component.html. Cependant, ce qui importe est l’endroit où le composant lui-même a été créé dans le DOM. Le texte de la ligne 12 est en fait le même que celui de la ligne 4 – nous lisons la propriété kingdom de la même instance AppComponent. L’élément DOM de ce composant est en fait un ancêtre de l’élément DOM <app-forest>. Donc, lorsque cette instance AppComponent a été créée, elle a été injectée avec le royaume 🧟.

Les deux éléments <app-unicorn>, sont, cependant, à l’intérieur des éléments DOM <app-forest>, donc lorsque leurs instances de UnicornComponents sont créées, angular remonte en fait le DOM et voit la valeur que nous avons fournie pour le KingdomService à l’intérieur du ForestComponent, et donc ces licornes sont injectées avec le royaume 🧙.

Vous pouvez obtenir un comportement différent si vous changez providers en viewProviders lors de la définition du ForestComponent. Vous pouvez en savoir plus sur les View Providers ici, et aussi consulter cet exemple de code, où j’ai changé ForestComponent pour utiliser les View Providers, donc maintenant même les licornes à l’intérieur de la forêt sont injectées avec le royaume 🧟. Merci à Lars Gyrup Brink Nielsen de m’avoir signalé cela !

J’ai transclus le T-Rex de Chrome dans cet article de blog

Poursuivez l’exploration !

J’espère que vous venez d’apprendre quelque chose de nouveau sur le système d’injection de dépendances d’Angular. Ce n’est qu’une des choses que Kapunahele et moi avons explorées lorsque nous avons préparé notre exposé pour AngularConnect. Nous vous invitons à aller plus loin et nous partagerons également les diapositives et le lien vers la vidéo après la conférence. Oh, et il y aura aussi du codage en direct. Ça va être très amusant !

Si vous souhaitez en savoir plus sur les tenants et aboutissants de l’Angular Injector, voici quelques articles que j’ai trouvés très utiles :

  • Ce que vous avez toujours voulu savoir sur l’injection de dépendances d’Angular
  • Un cas curieux du décorateur @Host et des injecteurs d’éléments dans Angular
  • Injecteurs de dépendances hiérarchiques (Angular Docs)

Et si vous participez à AngularConnect, vous êtes invité à venir dire bonjour !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.