Usando forRoot() y forChild() para configurar módulos en Angular

U

Los módulos en Angular son bastante complejos y muchas veces cuesta entenderlos. Aunque la documentación preparada por Angular sea detallada, sus múltiples ámbitos la hacen compleja. Sebastián Puentes, ingeniero en informática, nos detalla en este artículo como inyectar configuraciones con forRoot() y forChild() en Angular.

Esencialmente, forRoot y forChild nos permiten tener control de los providers (nuestros injectables) dependiendo del tipo de carga que queramos darle. Esto puede permitirnos tener diferentes configuraciones para distintos casos de la carga.

¿Cuándo se usa forRoot en Angular?

Se usa forRoot cuando un módulo es “eager”, esto es, no es lazy-loaded (lo cargamos al iniciar la aplicación). Angular crea un factory para todos los módulos, excepto los módulos lazy, que al cargarse bajo demanda, tienen su propio factory. Cuando nosotros usamos forRoot(), lo que estamos haciendo es cargar un provider que va a ser inyectado hacia la “raíz” de los módulos gracias a que usa el mismo factory que nuestro módulo principal.

En términos simples, esto significa que usar forRoot nos permite acceder a nuestros providers desde cualquier punto de la aplicación que no sea lazy loaded. Entonces, si nosotros implementamos un ejemplo como:

export class ExampleModule{

 static forRoot(): ModuleWithProviders {

 return {

   ngModule: ExampleModule,

   providers:[

     {

       provide: MyService,

       useClass: MyEagerService

     }

   ]

 };

} }

Lo que estamos haciendo es decir que ese módulo, y los providers, va a cargarse “globalmente” para la app. En este caso, “MyService” sera la clase “MyEagerService” para cualquier modulo que no sea lazy-loaded de nuestra app.

¿Cuándo se usa forChild en Angular?

Se usa forChild al reves: cuando queremos entregar un provider que es visible solamente para los módulos “hijos” de nuestro modulo, en el caso de que sean lazy loaded. Como cada modulo lazy se carga bajo demanda, tiene su propio inyector.

Siguiendo con el ejemplo de arriba, podemos decir:

export class ExampleModule {

 static forChild(): ModuleWithProviders {

 return {

   ngModule: ExampleModule,

   providers:[

     {

       provide: MyService,

       useClass: MyLazyService

     }

   ]

 };

} }

En este caso, estamos diciendo que cuando el módulo sea lazy-loaded, nuestro provider “MyService” va a ser la clase MyLazyService.

Podemos incluso definir no solo distintas clases para un mismo provider usando forRoot() y forChild(), si no que incluso, distintos providers dependiendo de como lo instanciamos.

export class ExampleModule {

 static forRoot(): ModuleWithProviders {

 return {

   ngModule: AModule,

   providers:[MyService]

 };

static forChild(): ModuleWithProviders {

 return {

   ngModule: AModule,

   providers:[MyLazyService]

 };

} }

Aca tenemos un mismo modulo que cargará dos providers distintos dependiendo si es llamado como “forRoot”, o “forChild”.

¡Únete a nuestra comunidad de expertos IT!  » Crea tu perfil gratis

forRoot y forChild para inyectar configuraciones a módulos

Más allá de la utilidad de controlar que instanciamos al importar nuestros módulos, forRoot y forChild nos ofrecen algo mucho más útil: Poder inyectar configuraciones a nuestros módulos.

export interface CoreModuleConfig {

 environment: string;

}

export class ExampleModule {

 static forRoot(conf: ModuleConfig): ModuleWithProviders {

 return {

   ngModule: AModule,

   providers:[{provide:CONFIG, useClass: conf}]

 };

} }

Aquí, he definido una interfaz llamada ModuleConfig con un parámetro para mi modulo (environment). Esta interfaz se entrega como parametro al metodo forRoot(), el que toma ese objeto entrante definiéndolo como el provider CONFIG.

¿Qué quiere decir esto? Que al usar forRoot, el valor del provider CONFIG será el que entregaremos como parametro a forRoot cuando lo llamemos.

En términos simples, al importar ExampleModule entregandole el objeto a forRoot, como en este ejemplo…

@NgModule({

 declarations: [

   AppComponent,

      …

 ],

 imports: [

   ExampleModule.forRoot({

     environment: “testing”

  }),

…estamos diciéndole a ExampleModule que use los valores definidos como parámetros como su provider de nombre CONFIG. Si llamamos a CONFIG.environment, su valor será “testing”.

forChild podemos usarlo de la misma forma, sin embargo, lo usaremos cuando queramos configurar un módulo lazy loaded.

Esto es especialmente util para configurar módulos que después serán exportados de forma independiente, por ejemplo.

Una advertencia sobre usar lógica en los métodos forRoot y forChild

Los métodos forRoot y forChild, si bien son funciones,es recomendable que solo tengan “return”, sin implementar más lógica. Esto, debido a que por defecto angular compila las aplicaciones de producción, usando la compilación AoT (Ahead of Time). Lamentablemente, si bien la logica que implementemos dentro de forRoot antes del return funcionara en una compilación JiT, en AoT arrojará el siguiente error:

Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function

Este código, por ejemplo, no funcionará en AoT.

function init(){

 console.log(“hola!”);

}

export class ExampleModule {

 static forRoot(): ModuleWithProviders {

      init();

 return {

   ngModule: AModule,

   providers:[MyService]

 };

Pero como solución, podemos usar un inicializador enganchandose al inicializador de Angular. Podemos definir un provider con el tag APP_INITIALIZER, y la función que le entreguemos, se iniciará cuando parta nuestra app, siempre que nuestra función sea una función exportada.

export function init(){

 console.log(“hola!”);

}

export class ExampleModule {

 static forRoot(): ModuleWithProviders {

      init();

 return {

   ngModule: AModule,

   providers:[MyService, {provide:APP_INITIALIZER,useClass:init]

 };

Este código si podrá compilar en AoT, y el método init() se ejecutará al cargar nuestra aplicación.

Esos son algunos usos interesantes para los metodos forRoot y forChild de los modulos Angular. Bien usados, pueden ser un gran apoyo en crear aplicaciones simples, limpias y escalables.

Sebastian Puentes
Por Sebastian Puentes

Entradas recientes