
Angular 6 introdujo una nueva sintaxis mejorada para incrustar dependencias de servicios en una aplicación (
provideIn ). A pesar de que Angular 7 ya se ha lanzado, este tema sigue siendo relevante.
Hay mucha confusión en los comentarios de GitHub, Slack y Stack Overflow, así que echemos un vistazo más de cerca a este tema.
En este artículo consideraremos:
- Inyección de dependencia
- Antigua forma de inyectar dependencias en Angular ( proveedores: [] );
- Una nueva forma de inyectar dependencias en Angular ( provideIn: 'root' | SomeModule );
- Escenarios UseIn provideIn ;
- Recomendaciones para usar la nueva sintaxis en aplicaciones;
- Para resumir.
Inyección de dependencia
Puede omitir esta sección si ya tiene una idea sobre
DI .
La inyección de dependencia ( DI ) es una forma de crear objetos que dependen de otros objetos. El sistema de inyección de dependencia proporciona objetos dependientes cuando crea una instancia de una clase.
- Documentación angular
Las explicaciones formales son buenas, pero echemos un vistazo más de cerca a lo que es la inyección de dependencia.
Todos los componentes y servicios son clases. Cada clase tiene un método de
constructor especial que, cuando se llama, crea un objeto de instancia de esta clase, que se usa en la aplicación.
Supongamos que en uno de nuestros servicios existe el siguiente código:
constructor(private http: HttpClient)
Si lo crea sin utilizar el mecanismo de inyección de dependencia, debe agregar
HttpClient manualmente. Entonces el código se verá así:
const myService = new MyService(httpClient)
Pero, ¿dónde en este caso obtener
httpClient ? También necesita ser creado:
const httpClient = new HttpClient(httpHandler)
Pero, ¿dónde conseguir
httpHandler ahora? Y así sucesivamente, hasta que se instancian todas las clases necesarias. Como podemos ver, la creación manual puede ser complicada y pueden producirse errores en el proceso.
El mecanismo de inyección de dependencia angular hace todo esto automáticamente. Todo lo que tenemos que hacer es especificar las dependencias en el constructor del componente y se agregarán sin ningún esfuerzo de nuestra parte.Antigua forma de inyectar dependencias en Angular (proveedores: [])
Para ejecutar la aplicación, Angular necesita saber acerca de cada objeto individual que queremos implementar en componentes y servicios. Antes del lanzamiento de Angular 6, la única forma de hacerlo era especificar los servicios en la propiedad del
proveedor: [] decoradores
@NgModule ,
@Component y
@Directive .

Aquí hay tres usos principales de los
proveedores: [] :
- En el decorador @NgModule del módulo cargado inmediatamente ( ansioso );
- En el decorador @NgModule del módulo de carga diferida ( perezoso );
- En decoradores @Component y @Directive .
Módulos descargados con la aplicación (Eager)
En este caso, el servicio se registra en el ámbito global como singleton. Será un singleton incluso si se incluye en los
proveedores [] de varios módulos. Se crea una única instancia de la clase de servicio que se registrará en el nivel raíz de la aplicación.
Módulos de carga diferida (perezosa)
Se creará una instancia del servicio conectado al módulo
diferido durante su inicialización. Agregar dicho servicio al componente
ansioso del módulo dará como resultado un error: ¡
No hay proveedor para MyService! errorImplementación en @ Componente y @ Directiva
Cuando se implementa en un componente o directiva, se crea una instancia separada del servicio, que estará disponible en este componente y en todos los elementos secundarios.
En esta situación, el servicio no será un singleton, su instancia se creará cada vez que se use el componente y se elimine junto con la eliminación del componente del DOM.
En este caso,
RandomService no se implementa a nivel de módulo y no es un singleton,
pero registrado con
proveedores: [] del componente
RandomComponent . Como resultado, obtendremos un nuevo número aleatorio cada vez que
usemos <rand®m> </ rand®m> .
Nueva forma de inyectar dependencias en Angular (proporcionado en: 'root' | SomeModule)
En Angular 6, obtuvimos una nueva herramienta de
"proveedores que se pueden sacudir en árbol" para
inyectar dependencias en una aplicación, que se puede usar utilizando la propiedad
proporcionadaIn del decorador
@Injectable .
Puede imaginar que se proporcione como la implementación de dependencias en la dirección opuesta: antes de que el módulo describiera los servicios en los que se conectará, ahora el servicio define el módulo al que está conectado.El servicio se puede incrustar en la raíz de la aplicación (
proporcionada en: 'raíz' ) o en cualquier módulo (
proporcionado en: algún módulo).
provideIn : 'root' es una abreviatura para implementación en
AppModule .

Analicemos los escenarios principales para usar la nueva sintaxis:
- Implementación en el módulo raíz de la aplicación ( proporcionado en: 'raíz' );
- Implementación en el módulo cargado inmediatamente ( ansioso );
- Implementación en el módulo con carga retrasada ( perezosa ).
Implementación en el módulo raíz de la aplicación (proporcionado en: 'raíz')
Esta es la opción de inyección de dependencia más común. En este caso, el servicio se agregará a la aplicación de paquete solo si realmente se usa, es decir, incrustado en un componente u otro servicio.
Cuando se utiliza el nuevo enfoque, no habrá mucha diferencia en una aplicación SPA monolítica, donde se utilizan todos los servicios escritos, sin embargo,
siempre que In: 'root' sea útil al escribir bibliotecas.
Anteriormente, todos los servicios de la biblioteca debían agregarse a los
proveedores: [] de su módulo. Después de importar la biblioteca a la aplicación, todos los servicios se agregaron al paquete, incluso si solo se usó uno. En el caso de
provideIn: 'root' no hay necesidad de conectar el módulo de la biblioteca. Simplemente inserte el servicio en el componente deseado.
Módulo de carga retrasada (diferido) y proporcionado en: 'root'
¿Qué sucede si implementa el servicio con
provideIn: 'root' en el módulo
diferido ?
Técnicamente,
'root' significa
AppModule , pero Angular es lo suficientemente inteligente como para agregar un servicio al paquete
diferido de un módulo si solo se implementa en sus componentes y servicios. Pero hay un problema (aunque algunas personas afirman que esta es una característica). Si luego introduce el servicio utilizado solo en el módulo
diferido en el módulo principal, el servicio se transferirá al paquete principal. En aplicaciones grandes con muchos módulos y servicios, esto puede generar problemas de seguimiento de dependencias y comportamientos impredecibles.
Ten cuidado La implementación de un servicio en múltiples módulos puede conducir a dependencias ocultas que son difíciles de entender e imposibles de desentrañar.Afortunadamente, hay formas de prevenir esto, y las consideraremos a continuación.
Inyección de dependencia en módulo cargado inmediatamente (ansioso)
Como regla general, este caso no tiene sentido y en su lugar podemos usar
provideIn: 'root' . La conexión de un servicio en
EagerModule se puede utilizar para la encapsulación y evitará la implementación sin conectar un módulo, pero en la mayoría de los casos esto no es necesario.
Si realmente necesita limitar el alcance del servicio, es más fácil usar el método anterior de
proveedores: [] , ya que ciertamente no conducirá a dependencias cíclicas.
Si es posible, intente usar provideIn: 'root' en todos los módulos ansiosos.Nota La ventaja de los módulos de carga retardada (perezoso)
Una de las características principales de Angular es la capacidad de dividir fácilmente la aplicación en fragmentos, lo que brinda las siguientes ventajas:
- El pequeño tamaño del paquete principal de la aplicación, debido a que la aplicación se carga y comienza más rápido;
- El módulo de carga retrasada está bien aislado y está conectado a la aplicación una vez en la propiedad loadChildren de la ruta correspondiente.
Gracias a la carga retrasada, se puede quitar o mover un módulo completo con cientos de servicios y componentes a una aplicación o biblioteca separada, con poco o ningún esfuerzo.Otra ventaja del aislamiento del módulo
diferido es que un error cometido en él no afectará al resto de la aplicación. Ahora puedes dormir tranquilo incluso el día del lanzamiento.
Implementación en un módulo con carga retrasada (proporcionado en: LazyModule)

La inyección de dependencia en un módulo específico impide el uso del servicio en otras partes de la aplicación. Esto preserva la estructura de dependencia, que es especialmente útil para aplicaciones grandes donde la inyección de dependencia desordenada puede generar confusión.
Dato interesante: si implementa el servicio diferido en la parte principal de la aplicación, el ensamblado (incluso AOT) fallará sin errores, pero la aplicación se bloqueará con el error "No hay proveedor para LazyService".El problema con la dependencia cíclica.

Puede reproducir el error de la siguiente manera:
- Cree el módulo LazyModule ;
- Creamos el servicio LazyService y nos conectamos usando provideIn: LazyModule ;
- Creamos el componente LazyComponent y lo conectamos al LazyModule ;
- Agregue LazyService al constructor del componente LazyComponent ;
- Obtenemos un error con una dependencia cíclica.
Esquemáticamente, se ve así:
servicio -> módulo -> componente -> servicio .
Puede resolver este problema creando un submódulo
LazyServiceModule , que se conectará a
LazyModule . Conecte los servicios al submódulo.

En este caso, deberá crear un módulo adicional, pero no requerirá mucho esfuerzo y le dará las siguientes ventajas:
- Evitará la introducción del servicio en otros módulos de aplicación;
- Se agregará un servicio al paquete solo si está integrado en un componente u otro servicio utilizado en el módulo.
Incrustar un servicio en un componente (proporcionado en: SomeComponent)
¿Es posible incrustar un servicio en
@Component o
@Directive usando la nueva sintaxis?
¡No por el momento!Para crear una instancia del servicio para cada componente, aún necesita usar
proveedores: [] en los
decoradores @ omponent o
@Directive .

Mejores prácticas para usar la nueva sintaxis en las aplicaciones
Bibliotecas
provideIn: 'root' es bueno para crear bibliotecas. Esta es una forma realmente conveniente de conectar solo la parte directamente utilizada de la funcionalidad a la aplicación principal y reducir el tamaño del ensamblaje final.
Un ejemplo práctico es la biblioteca ngx-model , que se ha reescrito utilizando la nueva sintaxis y ahora se llama @ angular-extensions / model . En la nueva implementación, no hay necesidad de conectar NgxModelModule a la aplicación, solo basta con incrustar ModelFactory en el componente necesario. Los detalles de la implementación se pueden encontrar aquí .Módulos de descarga diferida (diferido)
Utilice el módulo separado
proporcionadoIn: LazyServicesModule para servicios y conéctelo a
LazyModule . Este enfoque encapsula los servicios y evita que se conecten a otros módulos. Esto establecerá límites y ayudará a crear una arquitectura escalable.
En mi experiencia, la introducción accidental en el módulo principal o adicional (usando provideIn: 'root') puede generar confusión y no es la mejor solución.provideIn: 'root' también funcionará correctamente, pero cuando se utiliza
provideIn: LazyServideModule obtenemos un error de
"proveedor faltante" cuando se implementa en otros módulos y podemos corregir la arquitectura.
Mueva el servicio a un lugar más apropiado en la parte principal de la aplicación.¿Cuándo deben usarse los proveedores: []?
En casos donde es necesario configurar el módulo. Por ejemplo, conecte el servicio solo a
SomeModule.forRoot (someConfig) .
Por otro lado, en esta situación, puede usar provideIn: 'root'. Esto garantizará que el servicio se agregará a la aplicación solo una vez.Conclusiones
- Use provideIn: 'root' para registrar el servicio como un singleton, disponible en toda la aplicación.
- Para el módulo incluido en el paquete principal, use provideIn : 'root' , not provideIn: EagerlyImportedModule . En casos excepcionales, use proveedores: [] para encapsulación.
- Cree un submódulo con servicios para limitar su alcance provisto en: LazyServiceModule cuando use carga diferida .
- Conecte el módulo LazyServiceModule en LazyModule para evitar la dependencia circular.
- Use proveedores: [] en los decoradores @ omponent y @Directive para crear una nueva instancia de servicio para cada nueva instancia de componente. Una instancia de servicio también estará disponible en todos los componentes secundarios.
- Siempre limite el alcance de las dependencias para mejorar la arquitectura y evitar confusiones de dependencias.
Referencias
Artículo originalAngular es una comunidad de habla rusa.Meetups de Angular en Rusia