Inyección de dependencia angular explicada con ejemplos

¿Qué es la inyección de dependencia?

Motivación

La inyección de dependencia a menudo se conoce más simplemente como DI. El paradigma existe en Angular. Mantiene el código flexible, comprobable y mutable. Las clases pueden heredar lógica externa sin saber cómo crearla. Los consumidores de esas clases tampoco necesitan saber nada.

DI evita que las clases y los consumidores tengan que saber más de lo necesario. Sin embargo, el código es tan modular como antes gracias a los mecanismos que soportan DI en Angular.

Los servicios son un benefactor clave de DI. Se basan en el paradigma para inyectarse en varios consumidores. Luego, esos consumidores pueden aprovechar el servicio que se brinda y / o reenviarlo a otro lugar.

El servicio no está solo. Directivas, tuberías, componentes, etc.: todos los esquemas de Angular se benefician de DI de una forma u otra.

Inyectores

Los inyectores son estructuras de datos que almacenan instrucciones que detallan dónde y cómo se forman los servicios. Actúan como intermediarios dentro del sistema Angular DI.

Las clases de módulo, directiva y componente contienen metadatos específicos de los inyectores. Una nueva instancia de inyector acompaña a cada una de estas clases. De esta forma, el árbol de la aplicación refleja su jerarquía de inyectores.

Los providers: []metadatos aceptan servicios que luego se registran con la clase 'injector. Este campo de proveedor agrega las instrucciones necesarias para que funcione un inyector. Una clase (asumiendo que tiene dependencias) crea una instancia de un servicio tomando su clase como su tipo de datos. El inyector alinea este tipo y crea una instancia de ese servicio en nombre de la clase.

Por supuesto, la clase solo puede instanciar para qué tiene instrucciones el inyector. Si el propio inyector de la clase no tiene el servicio registrado, entonces consulta a su padre. Y así sucesivamente hasta llegar a un inyector con el servicio o la raíz de la aplicación.

Los servicios pueden registrarse en cualquier inyector dentro de la aplicación. Los servicios van en el providers: []campo de metadatos de módulos de clase, directivas o componentes. Los niños de la clase pueden instanciar un servicio registrado en el inyector de la clase. Los inyectores para niños recurren a los inyectores para padres, después de todo.

Inyección de dependencia

Eche un vistazo a los esqueletos de cada clase: servicio, módulo, directiva y componente.

// service import { Injectable } from '@angular/core'; @Injectable({ providedIn: /* injector goes here */ }) export class TemplateService { constructor() { } }
// module import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; @NgModule({ imports: [ CommonModule ], declarations: [], providers: [ /* services go here */ ] }) export class TemplateModule { }
// directive import { Directive } from '@angular/core'; @Directive({ selector: '[appTemplate]', providers: [ /* services go here */ ] }) export class TemplateDirective { constructor() { } }
//component import { Component } from '@angular/core'; @Component({ selector: 'app-template', templateUrl: './template.component.html', styleUrls: ['./template.component.css'], providers: [ /* services go here */ ] }) export class TemplateComponent { // class logic ... }

Cada esqueleto puede registrar servicios a un inyector. De hecho, TemplateService es un servicio. A partir de Angular 6, los servicios ahora pueden registrarse con inyectores usando @Injectablemetadatos.

En todo caso

Observe los metadatos providedIn: string( @Injectable) y providers: []( @Directive, @Componety @Module). Indican a los inyectores dónde y cómo crear un servicio. De lo contrario, los inyectores no sabrían cómo crear una instancia.

¿Qué pasa si un servicio tiene dependencias? ¿A dónde irían los resultados? Los proveedores responden a esas preguntas para que los inyectores puedan instanciar correctamente.

Los inyectores forman la columna vertebral del marco DI. Almacenan instrucciones para crear instancias de servicios para que los consumidores no tengan que hacerlo. ¡Reciben instancias de servicio sin necesidad de saber nada sobre la dependencia de la fuente!

También debo señalar que otros esquemas sin inyectores aún pueden utilizar la inyección de dependencia. No pueden registrar servicios adicionales, pero aún pueden crear instancias desde inyectores.

Servicio

Los providedIn: stringmetadatos de @Injectableespecifica con qué inyector registrarse. Usando este método, y dependiendo de si se usa el servicio, el servicio puede o no registrarse con el inyector. Angular llama a esto sacudir árboles .

De forma predeterminada, el valor se establece en ‘root’. Esto se traduce en el inyector de raíz de la aplicación. Básicamente, configurar el campo para ‘root’que el servicio esté disponible en cualquier lugar.

Nota rápida

Como se mencionó anteriormente, los niños que se inyectan recurren a sus padres. Esta estrategia alternativa garantiza que los padres no tengan que volver a registrarse para cada inyector. Consulte este artículo sobre servicios e inyectores para ver una ilustración de este concepto.

Los servicios registrados son singletons . Es decir, las instrucciones para crear una instancia del servicio existen en un solo inyector. Esto supone que no se ha registrado explícitamente en otro lugar.

Módulo, directiva y componente

Los módulos y componentes tienen cada uno su propia instancia de inyector. Esto es evidente dado el providers: []campo de metadatos. Este campo toma una matriz de servicios y los registra con el inyector del módulo o clase de componente. Este enfoque sucede en las @NgModule, @Directiveo @Componentdecoradores.

Esta estrategia omite la agitación de árboles o la eliminación opcional de los servicios no utilizados de los inyectores. Las instancias de servicio viven de sus inyectores durante la vida útil del módulo o componente.

Creación de instancias de referencias

Las referencias al DOM se pueden instanciar desde cualquier clase. Tenga en cuenta que las referencias siguen siendo servicios. Se diferencian de los servicios tradicionales en la representación del estado de otra cosa. Estos servicios incluyen funciones para interactuar con su referencia.

Las directivas necesitan constantemente referencias DOM. Las directivas realizan mutaciones en sus elementos anfitriones a través de estas referencias. Vea el siguiente ejemplo. El inyector de la directiva instancia una referencia del elemento host en el constructor de la clase.

// directives/highlight.directive.ts import { Directive, ElementRef, Renderer2, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor( private renderer: Renderer2, private host: ElementRef ) { } @Input() set appHighlight (color: string) { this.renderer.setStyle(this.host.nativeElement, 'background-color', color); } }
// app.component.html 

Highlighted Text!

Renderer2también se crea una instancia. ¿De qué inyector provienen estos servicios? Bueno, el código fuente de cada servicio proviene de @angular/core. Estos servicios deben registrarse con el inyector raíz de la aplicación.

import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { HighlightDirective } from './directives/highlight.directive'; @NgModule({ declarations: [ AppComponent, HighlightDirective ], imports: [ BrowserModule ], providers: [], bootstrap: [ AppComponent ] }) export class AppModule { }

¿¡Una matriz de proveedores vacía !? No temer. Angular registra muchos servicios con el inyector raíz automáticamente. Esto incluye ElementRefy Renderer2. En este ejemplo, estamos administrando el elemento host a través de su interfaz derivada de la instanciación de ElementRef. Renderer2nos permite actualizar el DOM a través del modelo de vista de Angular.

You can read more about views from this article. They are the preferred method for DOM/view updates in Angular applications.

It is important recognize the role that injectors play in the above example. By declaring variable types in the constructor, the class obtains valuable services. Each parameter’s data type maps to a set of instructions within the injector. If the injector has that type, it returns an instance of said type.

Instantiating Services

The Services and Injectors article explains this section to an extent. Though, this section rehashes the previous section or the most part. Services will often provide references to something else. They may just as well provide an interface extending a class’ capabilities.

The next example will define a logging service that gets added to a component’s injector via its providers: [] metadata.

// services/logger.service.ts import { Injectable } from '@angular/core'; @Injectable() export class LoggerService { callStack: string[] = []; addLog(message: string): void { this.callStack = [message].concat(this.callStack); this.printHead(); } clear(): void { this.printLog(); this.callStack = []; console.log(“DELETED LOG”); } private printHead(): void  null);  private printLog(): void { this.callStack.reverse().forEach((log) => console.log(message)); } }
// app.component.ts import { Component } from '@angular/core'; import { LoggerService } from './services/logger.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', providers: [LoggerService] }) export class AppComponent { constructor(private logger: LoggerService) { } logMessage(event: any, message: string): void { event.preventDefault(); this.logger.addLog(`Message: ${message}`); } clearLog(): void { this.logger.clear(); } }
// app.component.html 

Log Example

SUBMIT

Delete Logged Messages

CLEAR

Focus on the AppComponent constructor and metadata. The component injector receives instructions from the provider’s metadata field containing LoggerService. The injector then knows what to instantiate LoggerService from requested in the constructor.

The constructor parameter loggerService has the type LoggerService which the injector recognizes. The injector follows through with the instantiation as mentioned.

Conclusion

Dependency injection (DI) is a paradigm. The way it works in Angular is through a hierarchy of injectors. A class receives its resources without having to create or know about them. Injectors receive instruction and instantiate a service depending on which one was requested.

DI shows up a lot in Angular. The official Angular documentation explains why the paradigm is so prevalent. They also go on to describe the numerous use-cases for DI in Angular way beyond what was discussed in this article. Check it out by clicking below!

More on dependency injection:

  • Intro to Angular dependency injection
  • Quick intro to dependency injection