Comprendre l'organisation des entités avec lesquelles vous travaillez n'est pas quelque chose qui est immédiatement obtenu par un développeur écrivant ses premiers projets en Angular.
Et l'un des problèmes que vous pouvez rencontrer est l'utilisation inefficace des modules Angular, en particulier le module d'application surchargé: ils ont créé un nouveau composant, l'ont jeté dedans et le service y est allé aussi. Et tout semble être super, tout fonctionne. Cependant, avec le temps, un tel projet deviendra difficile à maintenir et à optimiser.
Heureusement, Angular offre aux développeurs la possibilité de créer leurs propres modules, et les appelle également modules de fonctionnalités.

Modules de fonctionnalité de domaine
Le module d'application surchargé doit être divisé. Par conséquent, la première chose à faire est de sélectionner de gros morceaux dans l'application et de les placer dans des modules séparés.
Une approche populaire consiste à diviser l'application en modules de fonctionnalités de domaine. Ils sont conçus pour diviser l'interface en fonction d'une tâche clé (domaine) exécutée par chaque partie. Des exemples de modules de fonctionnalité de domaine incluent une page de modification de profil, une page de produit, etc. Autrement dit, tout cela pourrait être sous l'élément de menu.

Toutes les annonces dans les cadres bleus, ainsi que le contenu des autres éléments de menu, méritent leurs propres modules de fonctionnalité de domaine.
Les modules de fonctionnalité de domaine peuvent utiliser un nombre illimité de déclarables (composants, directives, canaux), mais ils exportent uniquement le composant qui représente l'interface utilisateur de ce module. Les modules de fonctionnalité de domaine sont importés, généralement dans un module plus grand.
Les modules de fonctionnalité de domaine ne déclarent généralement pas de services en eux-mêmes. Cependant, s'ils annoncent, la durée de vie de ces services devrait être limitée à la durée de vie du module. Ceci peut être réalisé en utilisant des services de chargement paresseux ou de publicité dans le composant externe du module. Ces méthodes seront discutées plus loin dans l'article.
Chargement paresseux
La division de l'application en modules de fonctionnalité de domaine vous permet d'utiliser le chargement paresseux . Ainsi, vous pouvez supprimer du bundle d'origine ce dont l'utilisateur n'a pas besoin lors de la première ouverture de l'application: profil utilisateur, page produit, page photo, etc. Tout cela peut être chargé à la demande.
Services et injecteurs
L'application est divisée en gros morceaux - modules, et certains de ces modules sont chargés à la demande. Question: où les services mondiaux devraient-ils être annoncés? Et si nous voulions limiter la portée du service?
Injecteurs de modules chargés paresseusement
Contrairement aux éléments déclarables, dont l'existence doit être déclarée dans chaque module où ils sont utilisés, les monotones de services déclarés une fois dans l'un des modules deviennent disponibles dans l'application.
Il s'avère que les services peuvent être déclarés dans n'importe quel module et ne vous inquiétez pas? Pas vraiment comme ça.
Ce qui précède est vrai si l'application utilise uniquement un injecteur global, mais souvent tout est un peu plus intéressant. Les modules chargés paresseusement ont leur propre injecteur (composants aussi, mais plus à ce sujet plus tard). Pourquoi les modules chargés paresseusement créent-ils même leur propre injecteur? La raison réside dans le fonctionnement de l'injection de dépendance dans Angular.
L'injecteur peut être réapprovisionné avec de nouveaux fournisseurs jusqu'à ce qu'il commence à être utilisé. Dès que l'injecteur crée le premier service, il se ferme pour ajouter de nouveaux fournisseurs.
Lorsque l'application démarre, Angular configure d'abord l'injecteur racine, y fixant les fournisseurs qui ont été déclarés dans le module App et dans les modules importés dans celui-ci. Cela va même avant de créer les premiers composants et avant de leur fournir des dépendances.
Dans une situation où le module est chargé paresseusement, l'injecteur global est configuré depuis longtemps et est entré en service. Le module chargé n'a d'autre choix que de créer son propre injecteur. Cet injecteur sera un enfant de l'injecteur utilisé dans le module qui a initié la charge. Cela conduit à un comportement familier aux développeurs javascript dans la chaîne de prototype: si le service n'a pas été trouvé dans l'injecteur d'un module chargé paresseusement, le framework DI ira le chercher dans l'injecteur parent, etc.
Ainsi, les modules chargés paresseusement vous permettent de déclarer des services qui ne seront disponibles que dans le cadre de ce module. Les fournisseurs peuvent également être redéfinis, comme dans les prototypes javascript.
En revenant aux modules de fonctionnalité de domaine, le comportement décrit est un moyen de limiter la durée de vie des fournisseurs qui y sont annoncés.
Module de base
Alors, où les services mondiaux devraient-ils être annoncés, tels que les services d'autorisation, les services API, les services aux utilisateurs, etc.? La réponse simple est dans le module App. Cependant, afin de rétablir l'ordre dans le module App (c'est ce que nous faisons), vous devez déclarer les services globaux dans un module distinct, appelé module Core, et l'importer UNIQUEMENT dans le module App. Le résultat sera le même que si les services étaient déclarés directement dans le module App.
À partir de la version 6, dans l'angulaire, il était possible de déclarer des services globaux sans les importer n'importe où. Tout ce que vous devez faire est d'ajouter l'option providedIn à Injectable et de spécifier la valeur 'root' dedans. Les services ainsi déclarés deviennent disponibles pour l'ensemble de l'application, il n'est donc pas nécessaire de les déclarer dans le module.
En plus du fait que cette approche se penche sur l'avenir brillant de l'angulaire sans modules, elle aide également à tronquer le code inutile.
Test de singleton
Mais que faire si quelqu'un dans le projet souhaite importer le module Core ailleurs? Est-il possible de s'en protéger? Tu peux.
Ajoutez un constructeur au module Core qui vous demande d'y injecter le module Core (c'est vrai, vous-même) et marquez cette annonce avec des décorateurs Facultatif et SkipSelf. Si l'injecteur met une dépendance dans une variable, alors quelqu'un essaie de re-déclarer le module Core.

Utilisation de l'approche décrite dans BrowserModule .
Cette approche peut être utilisée avec des modules et des services.
Annonce de service dans le composant
Nous avons déjà envisagé un moyen de limiter la portée des fournisseurs utilisant le chargement différé, mais en voici un autre.
Chaque instance de composant a son propre injecteur, et pour le configurer, tout comme le décorateur NgModule, le décorateur de composant a la propriété provider. Et aussi - une propriété supplémentaire de viewProviders. Les deux servent à configurer les composants de l'injecteur, cependant, les fournisseurs déclarés dans chacune des méthodes ont des étendues différentes.
Pour comprendre la différence, vous avez besoin d'un bref historique.
Un composant se compose de la vue et du contenu.

Afficher les composants

Composants de contenu
Tout ce qui se trouve dans le fichier html du composant est sa vue, tandis que ce qui est passé entre les balises d'ouverture et de fermeture du composant est son contenu.
Le résultat obtenu:

Le résultat
Ainsi, les fournisseurs ajoutés aux fournisseurs sont disponibles à la fois dans la vue du composant dans lequel ils sont déclarés et pour le contenu qui est transmis au composant. Alors que viewProviders, comme son nom l'indique, rend les services visibles uniquement pour les visualiser et les ferme au contenu.
Malgré le fait qu'il est recommandé de déclarer des services dans l'injecteur racine, il existe des scénarios lorsque l'utilisation du composant injecteur est à portée de main:
La première est lorsque chaque nouvelle instance de composant doit avoir sa propre instance de service. Par exemple, un service qui stocke des données spécifiques à chaque nouvelle instance d'un composant.
Pour un autre scénario, nous devons nous rappeler que, bien que les modules de fonctionnalité de domaine puissent déclarer certains fournisseurs dont ils ont besoin uniquement, il est souhaitable que ces fournisseurs meurent avec ces modules. Dans ce cas, nous déclarerons le fournisseur dans le composant le plus externe, celui qui est exporté depuis le module.
Par exemple, le module de fonctionnalité de domaine qui est responsable du profil utilisateur. Nous déclarerons le service nécessaire uniquement pour cette partie de l'application chez les fournisseurs du composant le plus externe, UserProfileComponent. Désormais, tous les déclarables déclarés dans le balisage de ce composant, ainsi que passés dans le contenu, recevront la même instance de service.
Composants réutilisables
Que faire des composants que nous voulons réutiliser? Il n'y a pas non plus de réponse définitive à cette question, mais il existe des approches éprouvées.
Module partagé
Tous les composants utilisés dans le projet peuvent être stockés dans un module, en les exportant et en les important dans les modules du projet où ces composants peuvent être nécessaires.
Dans un tel module, vous pouvez mettre les composants d'un bouton, une liste déroulante, un bloc de texte stylisé, etc., ainsi que des directives et des tuyaux personnalisés.
Un tel module est généralement appelé SharedModule.
Il est important de noter que SharedModule ne doit pas déclarer de services. Ou déclarez utiliser l'approche forRoot. Nous parlerons de lui un peu plus tard.
Bien que l'approche SharedModules fonctionne, il y a quelques points:
- Nous n'avons pas rendu la structure de l'application plus propre, nous avons simplement déplacé le gâchis d'un endroit à un autre;
- Cette approche ne se penche pas sur le brillant avenir d'Angular, dans lequel il n'y aura pas de modules.
Une approche qui est dépourvue de ces lacunes est et implique la création d'un module pour chaque composant.
Module per Component ou SCAM (module angulaire à un seul composant)
Lors de la création d'un nouveau composant, vous devez le placer dans votre propre module. Vous devez importer les dépendances de composants dans le même module.

Chaque fois qu'un certain composant est nécessaire à n'importe quel endroit de l'application, il suffit d'importer le module de ce composant.
En anglais, cette approche est appelée module par composant ou SCAM - module angulaire à un seul composant. Bien que le nom contienne le mot composant, cette approche s'applique également aux canaux et directives (SPAM, SDAM).
L'avantage le plus important de cette approche est probablement la facilitation des tests de composants. Étant donné que le module créé pour le composant l'exporte et contient déjà toutes les dépendances dont il a besoin, pour configurer TestBed, placez simplement ce module dans les importations.
Cette approche contribue à l'ordre et à la structure du code de projet, et nous prépare également à l'avenir sans modules, où pour utiliser un composant dans la disposition d'un autre, il suffit de déclarer les dépendances dans la directive Component. Vous pouvez regarder l'avenir un peu à travers cet article .
ModuleWithProviders Interface
Si un projet contenant une déclaration de services XYZ est démarré dans un projet, et il se trouve qu'au fil du temps ce module a commencé à être utilisé partout, chaque importation de ce module tentera d'ajouter des services XYZ à l'injecteur correspondant, ce qui entraînera inévitablement des collisions. Angular a un ensemble de règles pour ce cas, qui peuvent ne pas correspondre à ce que le développeur attend. Cela est particulièrement vrai pour le module chargé paresseusement de l'injecteur.
Pour éviter les problèmes de collision, Angular fournit l'interface ModuleWithProviders , qui vous permet d'attacher des fournisseurs au module, tout en laissant les fournisseurs du module lui-même intacts. Et c'est exactement ce qui est nécessaire dans le cas décrit ci-dessus.
Stratégies forRoot (), forChild ()
Pour que les services soient fixés avec précision dans l'injecteur global, le module avec les fournisseurs n'est importé que dans l'AppModule. Du côté du module importé, il vous suffit de créer une méthode statique qui renvoie ModuleWithProviders, qui a historiquement reçu le nom forRoot.

Les méthodes qui renvoient ModuleWithProviders peuvent être n'importe quel nombre et elles peuvent être nommées comme vous le souhaitez. forRoot est une convention plus pratique qu'une exigence.
Par exemple, RouterModule a une méthode forChild statique, qui est utilisée pour configurer le routage dans des modules chargés paresseusement.
Conclusion:
- Séparez l'interface utilisateur par les tâches clés et créez votre propre module pour chaque partie sélectionnée: en plus d'une compréhension plus pratique de la structure du code du projet, obtenez l'opportunité de charger paresseusement des parties de l'interface
- Utilisez des injecteurs de modules et de composants chargés paresseusement si l'architecture de l'application l'exige
- Publiez des annonces de service globales dans un module distinct, le module Core, et importez-le uniquement dans le module d'application. Cela vous aidera à nettoyer le module d'application.
- Mieux, utilisez l'option providedIn avec la valeur racine du décorateur injectable.
- Utilisez le piratage avec les décorateurs facultatifs et SkipSelf pour empêcher la réimportation de modules et de services
- Stockez les composants, les directives et les tuyaux réutilisables dans le module partagé
- Cependant, la meilleure approche, qui regarde également vers l'avenir et facilite les tests, est de créer un module pour chaque composant (directives et tuyaux aussi)
- Utilisez l'interface ModuleWithProviders si vous souhaitez éviter les conflits de fournisseur. Une approche populaire consiste à implémenter la méthode forRoot pour ajouter des fournisseurs au module racine