Entendiendo los módulos Angular (NgModule) y sus ámbitos

24/10/2017

angular

NgModule es la estructura básica con la que te encuentras al codificar una aplicación con Angular, pero también es la más sutil y compleja, debido a sus diferentes ámbitos. La documentación de Angular tiene incluso una completa FAQ acerca de NgModules, pero sigue siendo una locura entenderlos.

¿Porqué NgModule?

Si utilizas Angular CLI se hace automáticamente, pero lo primero que tienes que hacer en Angular es cargar el NgModule root (raíz).

platformBrowserDynamic().bootstrapModule(AppModule);

El propósito de NgModule es declarar cada cosa que creas en Angular. Existen dos tipos de estructuras principales:

  • declarations: se utiliza para las cosas que usas en tus plantillas, principalmente componentes (~ vistas: las clases para mostrar datos), pero también directivas y pipes
  • providers: se utiliza para los servicios (~ modelos: las clases para obtener y manejar datos)

import { NgModule } from '@angular/core';

import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';
import { SomeService } from './shared/some.service';

@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
providers: [SomeService]
})
export class SomeModule {}

¿Porqué tenemos que registrar todo?

NgModule es una estructura introducida tardíamente en la fase RC de Angular 2. Inicialmente, parecía una complejidad innecesaria, ya que parece redundante con los imports de ES6. Pero, ahora que estamos acostumbrados a ella, nos alegramos: nos permite tener Ahead of Time (AoT) compilation (compilación anticipada), lo que es genial para el rendimiento. Y puede parecer duro al principio, pero realmente nos evita muchas líneas de imports: en las versiones beta de Angular 2, tenías que importar tus componentes y directivas cada vez que los usabas (buena suerte si utilizas módulos de UI como Material).

NgModule y ambitos/visibilidad

La confusión comienza con el hecho que declarations y providers no tienen el mismo ámbito/visibilidad:

  • declarations: los componentes están en el ámbito local (visibilidad privada)
  • providers: los servicios están en el ámbito global (visibilidad pública)

Esto quiere decir que los componentes que has declarado solamente se pueden utilizar en el módulo actual. Si necesitas utilizarlos fuera, en otros módulos, los tendrás que exportar:

import { NgModule } from '@angular/core';

import { SomeComponent } from './some.component';
import { SomeDirective } from './some.directive';
import { SomePipe } from './some.pipe';

@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
exports: [SomeComponent, SomeDirective, SomePipe]
})
export class SomeModule {}

Por el contrario, los servicios que proporcionas estarán disponibles/inyectables en cualquier parte de tu aplicación, en todos los módulos.

Cuándo importar un NgModule

La diferencia de ámbitos entre componentes y servicios es un punto importante a conocer. Las cosas se complican porque, por supuesto, como en cualquier framework y aplicación, no tendrás un solo módulo, sino varios. El propio Angular está subdividido en diferentes módulos (core, common, http y demás).

Así que otra cosa importante que haces en un módulo Angular es importar otros NgModules que necesites:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';

import { FeatureModule } from '../feature/feature.module';

@NgModule({
imports: [CommonModule, HttpClientModule, FeatureModule]
})
export class SomeModule {}

El problema es que: necesitas saber porqué importas estos módulos.

  • ¿Es para usar componentes (u otras cosas relacionadas con las plantillas, como directivas y pipes)?
  • ¿O es para utilizar servicios?

¿Porqué? Porque dada la diferencia de ámbito entre componentes y servicios:

  • Si el módulo se importa por sus componentes, tendrás que importarlo en cada módulo que lo necesite
  • Si el módulo se importa por sus servicios, solamente lo tendrás que importar una vez, en el primer módulo de aplicación.

Si no entiendes esto, tendrás errores en componentes no disponibles, porque olvidaste importar su módulo de nuevo.

O si importas un módulo por sus servicios más de una vez, crearás varias instancias del mismo en tu aplicación, lo que te puede llevar a errores, pero más importante, es malo para el rendimiento.

Cuándo importar módulos principales de Angular

Es necesario tener un buen conocimiento de los módulos de Angular, para saber cuántas veces los tienes que importar. A continuación tienes un pequeño resumen.

Módulos a importar cada vez que los necesitas

  • CommonModule (todo lo básico del sistema de plantillas de Angular: bindings, *nfIf, *ngFor…), excepto en el primer módulo de aplicación, debido a que ya forma parte de BrowserModule
  • FormsModule / ReactiveFormsModule
  • BrowserAnimationsModule
  • FlexLayoutModule
  • MaterialModule y módulos UI (como PrimeNg)
  • cualquier otro módulo que te de componentes, directivas o pipes

Módulos a importar una sola vez

  • HttpClientModule / HttpModule
  • cualquier otro módulo que te de solamente servicios.

Buenas prácticas de SharedModule

Es el motivo por el que Angular CLI, importa automáticamente CommonModule cuando creas un módulo.

Si utilizas otros módulos relacionados con componentes como animaciones, distribución flexible o Material, puedes acabar cansándote de importar sus módulos cada vez. Por eso es una buena práctica crear un SharedModule para factorizarlo.

Pero cuidado, no funcionará si solamente importas los módulos:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatCardModule } from '@angular/material';

@NgModule({
imports: [CommonModule, FlexLayoutModule, MatCardModule]
})
export class SharedModule {}

¿Porqué? Por un problema de ámbito: los componentes estarán disponibles en el propio SharedModule. Pero no es ahí donde los utilizarás, es en el resto de módules que incluyan a este SharedModule. Por tanto debes exportarlos:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatCardModule } from '@angular/material';

@NgModule({
imports: [CommonModule, FlexLayoutModule, MatCardModule],
exports: [CommonModule, FlexLayoutModule, MatCardModule]
})
export class SharedModule {}

Si tu SharedModule incluye otras cosas compartidas (como el menú de la aplicación), usa el código anterior. Pero si tu SharedModule solamente se utliza para factorizar y din incluye nada más, podemos simplificarlo elminando la propiedad imports:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatCardModule } from '@angular/material';

@NgModule({
exports: [CommonModule, FlexLayoutModule, MatCardModule]
})
export class SharedModule {}

NgModules mixtos

Puede ser más caótico: ¿cómo manejamos módulos con componentes y servicios al mismo tiempo?

Seguramente ya conoces uno de ellos: el RouterModule. Te da un componente (<router-outlet>) y una directiva (routerLink), pero también te da servicios (ActivatedRoute para obtener parámetros de la URL, Router para navegar…).

Afortunadamente, el caos es gestionado por el propio módulo. Los ficheros de ruta son generados automáticamente por Angular CLI, pero te puedes haber fijado que existe una sutil diferencia entre el enrutado del primer módulo de tu aplicación y el enrutado de los submódulos.

Para el AppModule:

RouterModule.forRoot(routes)

Para los submódulos:

RouterModule.forChild(routes)

¿Porqué? Porque la primera vez, en el módulo de aplicación, forRoot() proporcionará los componentes y los servicios de enrutado. Pero el resto de veces, en los submódulos, forChild solamente proporcionará los componentes de enrutado (sin proporcionar de nuevo los servicios, lo que sería malo).

Carga perezosa de módulos

Una última complicación: la carga perezosa de un módulo, que ahora es sencillo de hacer con Angular CLI.

const routes: Routes = [
{ path: 'admin', loadChildren: './admin/admin.module#AdminModule' }
];

Al ser un empaquetado y módulo diferente, cargado por defecto únicamente bajo demanda, no se incluye en el ámbito global de tu aplicación.

Para componentes, esto no cambia nada: necesitaa importar de nuevo el CommonModule o tu SharedModule, como en los submódulos.

Para servicios, hay una diferencia:

  • sigues teniendo acceso a servicios ya disponibles en la aplicación (como Http y tus propios servicios)
  • pero los servicios proporcinados por tus módulos cargados perezosamente solamente estarán disponibles en el módulo cargado perezosamente, no en cualquier parte de tu aplicación

Conclusión: ¿Porqué?

Ahora que ya sabes todo acerca de los módulos Angular te puedes preguntar: ¿porqué esta locura?
Bueno, puede ser un podo difícil para los principiantes, pero existe una buena razón:

  • los servicios son principalmente simples clases ES6: se importan/exportan, en sus namespaces, así que no hay riesgo de colisión!
  • los componentes crean… componentes, esto es, nuevas etiquetas HTML: si fueren globales, la carga de dos librerías que creasen componentes con el mismo nombre crearía conflictos

vía

Anuncios

One Response to “Entendiendo los módulos Angular (NgModule) y sus ámbitos”

  1. carmoreno Says:

    Buen artículo, muchas gracias


Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

A %d blogueros les gusta esto: