Profundizando en los Ámbitos de Proveedores ‘root’ y ‘any’ de Angular
Las ventajas a nivel de ámbito de proveedores de Angular Ivy
A partir de Angular 9, Ivy nos proporciona unas cuantas opciones adicionales a la hora de definir el ámbito de los proveedores (provider scope.) En este artículo vamos a ver por qué el ámbito 'root’
es insuficiente, y cómo nos puede ayudar el nuevo valor 'any'
.
Si habéis seguido el lanzamiento de Angular 9, quizá hayáis oído hablar de que providedIn
tiene propiedades nuevas. El ámbito de módulo está disponible desde la versión 2, el ámbito de módulo tree-shakable apareció en la versión 6 con providedIn: MyServiceModule
y ahora con Angular 9, tenemos 'any'
y 'platform'
.
En este artículo vamos a ver en qué casos podemos utilizar 'any'
, y por qué es tan útil. Dejaremos 'platform'
para el siguiente artículo.
Proveedores tree-shakable
En Angular 6 se añadió la propiedad providedIn
a los proveedores, para hacer que los servicios fuesen tree-shakable. Si eres nuevo en el mundo de Angular, permíteme una breve explicación de lo que queremos decir con tree shaking: es el proceso mediante el cual se elimina el código no utilizado de nuestra aplicación. Por ejemplo, si creamos un servicio, pero no lo llegamos a utilizar en nuestra aplicación, el código correspondiente al servicio no formará parte del compilado de producción final. Para saber más, podéis leer este artículo de Lars.
El por qué del ámbito ‘any’
Ahora sabemos por qué se introdujo 'root'
, la idea era que los servicios fuesen tree-shakable. Para poder entender providedIn: 'any'
tenemos que hablar sobre la implementación de forRoot
y forChild
, y sobre la carga diferida. Si has utilizado Angular Router o NgRx, entonces estarás familiarizado con estos métodos.
El problema de trabajar con módulo de carga diferida es que si utilizamos providedIn: 'root'
, aunque creamos que deberíamos obtener una nueva instancia de un servicio, obtenemos la misma instancia, lo que quizá no sea el comportamiento que esperamos. Cuando trabajamos con un módulo de carga diferida, se debe crear una nueva instancia cuando cargamos el módulo.
Vamos a escribir algo de código para ver el problema anterior, además de ver cómo 'any'
(es muy importante que nos aseguramos de incluir las comillas simples para evitar confusión con el tipo any
de TypeScript) nos ayuda a resolverlo.
Lo que vamos a hacer
- Un servicio config que recibirá los parámetros
apiEndpoint
ytimeout
. - Dos módulos de carga diferida: employee y department, que van a utilizar el config service, pero con valores diferentes.
El problema que supone utilizar el ámbito root
Si no queremos instalar Angular 9 de forma global, podemos crear una aplicación Angular 9 con el siguiente comando:
npx -p @angular/cli ng new providerdemo
Si tienes el Angular CLI 9 instalado de forma global, puedes ahorrarte escribir npx -p @angular/cli
en todos los comandos.
Ahora vamos a crear dos módulos de carga diferida con componentes. Para ello, ejecutamos los siguientes comandos:
npx -p @angular/cli ng g module employee --routing --route employee --module appnpx -p @angular/cli ng g module department --routing --route department --module app
Vamos a crear un nuevo proveedor y una interfaz, que añadiremos a una carpeta shared
, ya que el código se compartirá entre varios módulos:
A continuación, creamos un servicio al que llamaremos ConfigService
, que leerá el valor del token y lo utilizará para llevar a cabo alguna operación. Para crearlo, ejecutamos el siguiente comando:
npx -p @angular/cli ng g service shared/config
Una vez creado, añadimos el siguiente código a nuestro servicio:
Ahora vamos a utilizar este servicio en los componentes EmployeeComponent
y DepartmentComponent
, que hemos creado en sus respectivos módulos. Únicamente vamos a imprimir los valores recibidos de Token
:
Ahora, vamos a intentar proporcionar dos configuraciones diferentes a EmployeeComponent
y DepartmentComponent
, añadiendo el siguiente código a ambos módulos:
Podemos ver la diferencia entre ambos, en los valores de configValue
. Vamos a ejecutar la aplicación para ver qué obtenemos, recordando que el valor de providedIn
sigue siendo 'root'
. Para comprobar el estado actual de la aplicación, vamos a añadir las rutas al app.component.html
:
A continuación, ejecutamos la aplicación con el siguiente comando:
npx -p @angular/cli ng serve -o
Podemos ver que la aplicación funciona. Sin embargo, al hacer click sobre una de las rutas, y boom, “habemus” error. Esperábamos ver los diferentes valores de configValue
, pero nos encontramos con el siguiente error:
Qué es lo que ha ido mal
Lo hemos hecho todo bien, pero en lugar de ver los diferentes valores de config en los componentes Employee y Department, nos hemos topado con un error. Esto es debido a providedIn: 'root'
. El siguiente diagrama nos muestra lo que ha ocurrido:
Cuando proveemos el servicio con providedIn: 'root'
, queda registrado en nuestro AppModule
. En cuanto intentamos activar una de las rutas, el servicio espera el valor config, que no ha sido proporcionado. Así que, vamos a hacer que funcione, haciendo ciertos cambios a nuestro AppModule
.
Añadimos el siguiente código a nuestro app.module.ts
:
Ahora, nuestra aplicación funciona. Al hacer click en las rutas, podremos ver siguientes valores, tanto para el componente Employee como para el componente Department, que es lo que esperábamos según el modelo de la Imagen 1:
apiEndPoint: 'def.com'
timeout: 5000
Lo que queremos conseguir
En realidad, queremos conseguir lo que nos muestra la Imagen 2, donde cada módulo tiene su propia instancia. Sin embargo, con providedIn: 'root'
, no es posible lograrlo. Para resolver esto, la solución que había antes era implementando los métodos estáticos forRoot
y forChild
, para que cada componente pudiera tener su propia instancia.
Otra forma de conseguir el mismo resultado es proveyendo el ConfigService
en todos y cada uno de los módulos. El problema con este enfoque es que el servicio deja de ser tree-shakable:
En su lugar, vamos a cambiar la propiedad providedIn
del ConfigService
de la siguiente manera:
Volvemos a ejecutar la aplicación y nos fijamos en la consola:
¡Bingo! Tenemos instancias independientes, sin tener que implementar los métodos estáticos forRoot
y forChild
, ni conformarnos con proveedores tree shakable.
Tras añadir providedIn: 'any'
, los módulos de carga inmediata compartirán una instancia común, y todos los módulos de carga diferida tendrán su propia instancia de ConfigService
. Podemos verlo en la siguiente imagen:
Conclusión
Anteriormente, asegurarnos de que recibíamos una nueva instancia de un servicio en los módulos de carga diferida suponía todo un reto. Ahora, con el nuevo valor any
, es fácil de conseguir. Podemos proveer cualquier token y cualquier valor a los módulos de carga diferida, y el servicio creará una nueva instancia por cada módulo de carga diferida.
Podéis descargar el código de este proyecto en GitHub.
Nota de la editora: Este artículo ha sido publicado originalmente por Santosh Yadav en indepth.dev: A detailed look at Angular’s ‘root’ and ‘any’ provider scopes
Traducción por Estefanía García Gallardo.