Angular 6+ est un guide d'injection de dépendance complet. fourni par rapport aux fournisseurs: []

image

Angular 6 a introduit une nouvelle syntaxe améliorée pour l'incorporation des dépendances de service dans une application ( provideIn ). Malgré le fait qu'Angular 7 ait déjà été publié, ce sujet est toujours d'actualité. Il y a beaucoup de confusion dans les commentaires de GitHub, Slack et Stack Overflow, alors examinons de plus près ce sujet.

Dans cet article, nous considérerons:


  1. Injection de dépendance
  2. Ancienne façon d'injecter des dépendances dans Angular ( fournisseurs: [] );
  3. Une nouvelle façon d'injecter des dépendances dans Angular ( providedIn: 'root' | SomeModule );
  4. Les scénarios UseIn fournissentIn ;
  5. Recommandations pour l'utilisation de la nouvelle syntaxe dans les applications;
  6. Pour résumer.

Injection de dépendance


Vous pouvez ignorer cette section si vous avez déjà une idée de DI .
L' injection de dépendance ( DI ) est un moyen de créer des objets qui dépendent d'autres objets. Le système d'injection de dépendances fournit des objets dépendants lorsqu'il instancie une classe.

- Documentation angulaire

Les explications formelles sont bonnes, mais regardons de plus près ce qu'est l'injection de dépendance.

Tous les composants et services sont des classes. Chaque classe a une méthode constructeur spéciale qui, lorsqu'elle est appelée, crée un objet instance de cette classe, qui est utilisé dans l'application.

Supposons que l'un de nos services comporte le code suivant:

constructor(private http: HttpClient) 

Si vous le créez sans utiliser le mécanisme d'injection de dépendances, vous devez ajouter HttpClient manuellement. Ensuite, le code ressemblera à ceci:

 const myService = new MyService(httpClient) 

Mais où dans ce cas obtenir httpClient ? Il doit également être créé:

 const httpClient = new HttpClient(httpHandler) 

Mais où trouver httpHandler maintenant? Et ainsi de suite, jusqu'à ce que toutes les classes nécessaires soient instanciées. Comme nous pouvons le voir, la création manuelle peut être compliquée et des erreurs peuvent survenir au cours du processus.

Le mécanisme d'injection de dépendance angulaire fait tout cela automatiquement. Tout ce que nous devons faire est de spécifier les dépendances dans le constructeur du composant et elles seront ajoutées sans aucun effort de notre part.

Ancienne façon d'injecter des dépendances dans Angular (fournisseurs: [])


Pour exécuter l'application, Angular doit connaître chaque objet individuel que nous voulons implémenter dans les composants et les services. Avant la sortie d'Angular 6, la seule façon de procéder était de spécifier les services dans la propriété des fournisseurs: [] décorateurs @NgModule , @ omponent et @Directive .

image

Voici trois utilisations principales des fournisseurs: [] :

  1. Dans le décorateur @NgModule du module immédiatement chargé ( avide );
  2. Dans le décorateur @NgModule du module de chargement différé ( paresseux );
  3. Dans les décorateurs @Component et @Directive .

Modules téléchargés avec l'application (Désireux)


Dans ce cas, le service est enregistré dans la portée globale en tant que singleton. Ce sera un singleton même s'il est inclus dans les fournisseurs [] de plusieurs modules. Une seule instance de la classe de service est créée et sera enregistrée au niveau racine de l'application.

Modules de charge différée (paresseux)


Une instance du service connecté au module paresseux sera créée lors de son initialisation. L'ajout d'un tel service au composant impatient du module entraînera une erreur: aucun fournisseur pour MyService! erreur .

Implémentation dans @ Component et @ Directive


Lorsqu'il est implémenté dans un composant ou une directive, une instance distincte du service est créée, qui sera disponible dans ce composant et tous les enfants. Dans cette situation, le service ne sera pas un singleton, son instance sera créée chaque fois que le composant est utilisé et supprimé avec la suppression du composant du DOM.

image

Dans ce cas, RandomService n'est pas implémenté au niveau du module et n'est pas un singleton,
mais enregistré auprès des fournisseurs: [] du composant RandomComponent . Par conséquent, nous obtiendrons un nouveau nombre aléatoire à chaque fois en utilisant <rand®m> </ rand®m> .

Nouvelle façon d'injecter des dépendances dans Angular (providedIn: 'root' | SomeModule)


Dans Angular 6, nous avons obtenu un nouvel outil «Tree-shakable providers» pour injecter des dépendances dans une application, qui peut être utilisé en utilisant la propriété providedIn du décorateur @Injectable .

Vous pouvez imaginer fourniIn comme l'implémentation de dépendances dans le sens inverse: avant que le module ne décrive les services auxquels il sera connecté, maintenant le service définit le module auquel il est connecté.

Le service peut être intégré à la racine de l'application ( fourni dans: «racine» ) ou dans n'importe quel module ( fourni dans: SomeModule ). providedIn: 'root' est une abréviation pour l'implémentation dans l' AppModule .

image

Analysons les principaux scénarios d'utilisation de la nouvelle syntaxe:

  1. Implémentation dans le module racine de l'application ( fourniIn: 'root' );
  2. Implémentation dans le module immédiatement chargé ( avide );
  3. Implémentation dans le module avec chargement différé ( paresseux ).

Implémentation dans le module racine de l'application (fourniIn: 'root')


Il s'agit de l'option d'injection de dépendance la plus courante. Dans ce cas, le service ne sera ajouté à l'application groupée que s'il est effectivement utilisé, c'est-à-dire intégré dans un composant ou un autre service.

Lors de l'utilisation de la nouvelle approche, il n'y aura pas beaucoup de différence dans une application SPA monolithique, où tous les services écrits sont utilisés, mais fournis dans: 'root' sera utile lors de l'écriture de bibliothèques.

Auparavant, tous les services de bibliothèque devaient être ajoutés aux fournisseurs: [] de son module. Après l'importation de la bibliothèque dans l'application, tous les services ont été ajoutés au bundle, même si un seul a été utilisé. Dans le cas de providedIn: 'root', il n'est pas nécessaire de connecter le module de bibliothèque. Intégrez simplement le service dans le composant souhaité.

Module de chargement différé (paresseux) et fourni dans: 'root'


Que se passe-t-il si vous implémentez le service avec providedIn: 'root' dans le module paresseux ?

Techniquement, «root» signifie AppModule , mais Angular est suffisamment intelligent pour ajouter un service à l'ensemble paresseux d'un module s'il n'est implémenté que dans ses composants et services. Mais il y a un problème (bien que certaines personnes prétendent que c'est une fonctionnalité). Si vous introduisez ultérieurement le service utilisé uniquement dans le module paresseux dans le module principal, le service sera transféré vers le bundle principal. Dans les grandes applications avec de nombreux modules et services, cela peut entraîner des problèmes de suivi des dépendances et un comportement imprévisible.

Faites attention! L'implémentation d'un service dans plusieurs modules peut conduire à des dépendances cachées qui sont difficiles à comprendre et impossibles à démêler.

Heureusement, il existe des moyens d'empêcher cela, et nous les examinerons ci-dessous.

Injection de dépendances dans un module immédiatement chargé (impatient)


En règle générale, ce cas n'a pas de sens et au lieu de cela, nous pouvons utiliser providedIn: 'root' . La connexion d'un service dans EagerModule peut être utilisée pour l'encapsulation et empêchera la mise en œuvre sans connecter de module, mais dans la plupart des cas, cela n'est pas nécessaire.

Si vous avez vraiment besoin de limiter la portée du service, il est plus facile d'utiliser l'ancienne méthode des fournisseurs: [] , car elle ne conduira certainement pas à des dépendances cycliques.

Si possible, essayez d'utiliser providedIn: 'root' dans tous les modules désireux.

Remarque L'avantage des modules à chargement différé (paresseux)


L'une des principales caractéristiques d'Angular est la possibilité de diviser facilement l'application en fragments, ce qui offre les avantages suivants:

  1. La petite taille du bundle principal de l'application, grâce à laquelle l'application se charge et démarre plus rapidement;
  2. Le module de chargement différé est bien isolé et est connecté dans l'application une fois dans la propriété loadChildren de l'itinéraire correspondant.

Grâce au chargement retardé, un module entier avec des centaines de services et de composants peut être supprimé ou déplacé vers une application ou une bibliothèque distincte, avec peu ou pas d'effort.

Un autre avantage de l'isolement du module paresseux est qu'une erreur commise dans celui-ci n'affectera pas le reste de l'application. Vous pouvez maintenant dormir paisiblement même le jour de la sortie.

Implémentation dans un module à chargement différé (fourni dans: LazyModule)


image

L'injection de dépendances dans un module spécifique empêche l'utilisation du service dans d'autres parties de l'application. Cela préserve la structure de dépendance, ce qui est particulièrement utile pour les grandes applications où l'injection de dépendances désordonnée peut conduire à la confusion.

Fait intéressant: si vous implémentez le service paresseux dans la partie principale de l'application, l'assembly (même AOT) échouera sans erreur, mais l'application plantera avec l'erreur «Aucun fournisseur pour LazyService».

Le problème de la dépendance cyclique


image

Vous pouvez reproduire l'erreur comme suit:

  1. Créez le module LazyModule ;
  2. Nous créons le service LazyService et nous nous connectons à l'aide de providedIn: LazyModule ;
  3. Nous créons le composant LazyComponent et le connectons au LazyModule ;
  4. Ajoutez LazyService au constructeur du composant LazyComponent ;
  5. Nous obtenons une erreur avec une dépendance cyclique.

Schématiquement, cela ressemble à ceci: service -> module -> composant -> service .

Vous pouvez résoudre ce problème en créant un sous-module LazyServiceModule , qui sera connecté à LazyModule . Connectez les services au sous-module.
image

Dans ce cas, vous devrez créer un module supplémentaire, mais cela ne nécessitera pas beaucoup d'efforts et offrira les avantages suivants:

  1. Cela empêchera l'introduction du service dans d'autres modules d'application;
  2. Un service sera ajouté au bundle uniquement s'il est intégré dans un composant ou un autre service utilisé dans le module.

Incorporation d'un service dans un composant (fourni dans: SomeComponent)


Est-il possible d'incorporer un service dans @Component ou @Directive en utilisant la nouvelle syntaxe?

Pas pour le moment!

Pour créer une instance du service pour chaque composant, vous devez toujours utiliser les fournisseurs: [] dans les décorateurs @ omponent ou @Directive .

image

Meilleures pratiques pour l'utilisation de la nouvelle syntaxe dans les applications


Bibliothèques


providedIn: 'root' est bon pour créer des bibliothèques. Il s'agit d'un moyen très pratique de connecter uniquement la partie directement utilisée de la fonctionnalité à l'application principale et de réduire la taille de l'assemblage final.

Un exemple pratique est la bibliothèque ngx-model , qui a été réécrite en utilisant la nouvelle syntaxe et s'appelle désormais @ angular-extensions / model . Dans la nouvelle implémentation, il n'est pas nécessaire de connecter NgxModelModule à l'application, il suffit juste d'incorporer ModelFactory dans le composant nécessaire. Les détails de la mise en œuvre peuvent être trouvés ici .

Modules de téléchargement différé (paresseux)


Utilisez le module distinct fourni In: LazyServicesModule pour les services et branchez-le sur LazyModule . Cette approche encapsule les services et les empêche d'être connectés à d'autres modules. Cela établira des limites et aidera à créer une architecture évolutive.

D'après mon expérience, une introduction accidentelle dans le module principal ou supplémentaire (à l'aide de providedIn: 'root') peut prêter à confusion et n'est pas la meilleure solution!

providedIn: 'root' fonctionnera également correctement, mais lors de l'utilisation de providedIn: LazyServideModule, nous obtenons une erreur «fournisseur manquant» lors de l'implémentation dans d'autres modules et nous pouvons corriger l'architecture. Déplacez le service vers un emplacement plus approprié dans la partie principale de l'application.

Quand faut-il recourir aux prestataires: []?


Dans les cas où il est nécessaire de configurer le module. Par exemple, connectez le service uniquement à SomeModule.forRoot (someConfig) .

image

D'un autre côté, dans cette situation, vous pouvez utiliser providedIn: 'root'. Cela garantira que le service ne sera ajouté à l'application qu'une seule fois.

Conclusions


  1. Utilisez providedIn: 'root' pour enregistrer le service en tant que singleton, disponible dans toute l'application.
  2. Pour le module inclus dans le bundle principal, utilisez providedIn: 'root' , not providedIn: EagerlyImportedModule . Dans des cas exceptionnels, utilisez des fournisseurs: [] pour l'encapsulation.
  3. Créez un sous-module avec des services pour limiter leur portée fournie dans: LazyServiceModule lors de l'utilisation du chargement différé .
  4. Branchez le module LazyServiceModule dans Le LazyModule pour éviter une dépendance circulaire.
  5. Utilisez les fournisseurs: [] dans les décorateurs @ omponent et @Directive pour créer une nouvelle instance de service pour chaque nouvelle instance de composant. Une instance de service sera également disponible dans tous les composants enfants.
  6. Limitez toujours la portée des dépendances pour améliorer l'architecture et éviter de confondre les dépendances.

Les références


Article d'origine.
Angular est une communauté russophone.
Meetups Angulaire en Russie

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


All Articles