Angular ¿qué son los módulos y cómo se refactoriza una aplicación?

Yone Moreno Jiménez
16 min readJan 2, 2018

--

Siguiendo el curso de Deborah Kurata, en este artículo quiero explicar lo básico que he aprendido:

En la parte 1 vimos lo básico de Angular

En la parte 2 vimos como añadir lógica al html

En la parte 3 vimos el data binding y las pipes

En la parte 4 vimos interfaces, pipes, el ciclo de vida.

En la parte 5 vimos como anidar Component.

Para aprender como compartir datos entre Component es la 6.

Para la parte 7 descubrimos cómo hacer peticiones http.

Mediante la parte 8 aprendimos lo básico del enrutamiento.

Ya en la parte 9 aprendimos cómo pasar datos mediante URLs.

De la manera en la que hemos creado la aplicación de ejemplo, hemos ubicado todos los componentes en un mismo módulo, de forma que si quisiéramos añadir cualquier funcionalidad tendríamos un interesante problema: todo está desorganizado. La idea es usar los módulos que ofrece angular para separar las responsabilidades:

De forma que tuviéramos un AppModule para la portada y el inicio de la página. Un ProductModule para lo relacionado con los productos. Un SharedModule para poner todo lo común/compartido, por ejemplo el componente de las estrellas que representan la puntuación.

En este artículo veremos más sobre los módulos de angular y sus usos.

Índice(Drive)



Qué es un módulo angular 2

Bootstrap array (array de inicio) 15

Declaration array (array de declaraciones) 18

Exports array (array de exportaciones) 22

Imports array (array de importaciones) 24

Providers array (array de proveedores / servicios) 27

Refactorizar la aplicación 29

Código para extraer ProductModule: 35

Shared Module 49

Código 52

Resumen de lo aprendido 62

Qué es un módulo angular

En cuanto a código es una clase con un decorador llamado @NgModule.

Su propósito es: organizar las partes de nuestra aplicación. Ordenando la misma en bloques. Permitiendo extender nuestra aplicación con funcionalidades de librerías externas. Permite a angular saber las importaciones / exportaciones necesarias para que cierto componente funcione.

En concreto un módulo angular:

Declara los componentes, directivas y pipes. E inicia (bootstraps) de forma visual (el html/css) esos componentes para que sean visibles.

Además:

Exporta otros módulos, ya sean nuestros, de angular o de librerías de terceros, al igual que puede exportar componentes, directivas o pipes.

También:

Importa módulos.

Como añadido:

Los módulos angular proveen de servicios, lo cual significa que hacen disponibles los servicios para todos los componentes, directivas y pipes que estén declaradas en ese módulo.

Podemos pensar que un módulo angular es una caja, donde primero metemos todos los componentes:

Nuestro AppComponent usa el Router:

Por lo tanto hay que definir en el módulo, el Router:

Luego el productList usa ngModel para filtrar la lista de productos:

Por tanto necesita el FormsModule:

Además el productList también usa *ngFor y *ngIf para recorrer la lista de productos filtrados:

Por lo que necesita el BrowserModule:

Por si fuera poco el productList también usa pipes, para convertir los guiones en espacios en el código del producto:

Así que necesita nuestra pipe propia:

E incluso el productList emplea el starRating, nuestro componente para representar las puntuaciones mediante estrellas:

Por lo tanto lo necesitamos:

Luego, los módulos angular definen un template resolution environment, de forma que los componentes declarados en ese módulo sólo pueden usar módulos, componentes, directivas y pipes declarados en el mismo:

Por ejemplo la productList emplea [(ngForm)] para trabajar con el cuadro de texto mediante el cual se filtra la lista de productos. Por tanto necesitamos importar el módulo que dispone de esa directiva:

Además la productList también usa las estrellas para la puntuación, por tanto hay que declarar en este módulo el StarComponent:

Como StarComponent es un componente que hemos creado nosotros, podemos usarlo de dos formas. Incluyéndolo directamente junto a la ProductList, o asociándolo a otro módulo y exportando el mismo al actual:

Actualmente tenemos todo en un módulo:

En la imagen superior se observa todo lo que se importa, como el Router.

También declara todos los componentes:

Provee del servicio que permite validar la url a la que se pretende navegar:

E inicia la aplicación:

La idea es refactorizar la aplicación para separar sus partes.

Bootstrap array (array de inicio)

Como hemos visto toda aplicación angular tiene un módulo raíz:

Y como mínimo tiene un componente:

El módulo inicia al componente, de forma que hace que sea visible su html /css:

Este es el componente que se carga cada vez que se lanza la aplicación:

Por tanto hay unas reglas:

Cada aplicación debe iniciar al menos un componente, el componente raíz de la aplicación.

Segunda:

El array de inicio sólo debería ser usado en el módulo raíz de la aplicación, por defecto llamado AppModule.

Declaration array (array de declaraciones)

Cada componente, directiva o pipe esta declarada en un módulo, para ello usamos el array declarations:

Hay también unas reglas:

Cada componente, directiva o pipe que creamos debe pertencer a uno y sólo un módulo angular.

Segunda:

Sólo declara componentes, directiva y pipes. Las clases, servicios u otros módulos NO SE declaran.

La tercera:

Nunca redeclares componentes, directivas o pipes que pertenezcan a otro módulo. Esto tiene relación con la primera, declarar sólo una vez.

Para que algo esté disponible en varios módulos, se exporta el módulo en sí:

Cuarta regla:

Todos los componentes declarados, directivas y pipes son privadas por defecto. Quiere decir que sólo son accesibles para otros componentes, directivas y pipes declaradas en el mismo módulo.

Por ejemplo:

Si el StarComponent está declarado en Module B, sólo esta disponible para otros componentes también declarados en Module B.

Para compartir el StarComponent, se exporta el módulo en el cual se ha declarado:

Nuestra quinta regla:

El módulo angular da el entorno de resolución de plantillas (html css) para las plantillas de sus componentes. Es decir, al escribir una directiva como <pm-star></pm-star> en un html, angular buscará la misma en los componentes que estén declarados en ese módulo, y si la hay en la etiqueta selector, entonces revisará y cargará los template y styles correspondientes.

En nuestro ejemplo:

El StarComponent debe ser declarado en el mismo módulo que el productList component, porque el segundo necesita del html y css del primero.

O se puede probar este enfoque más modular:

Sólo hay que tener en cuenta elegir una opción o la otra no ambas a la vez.

Exports array (array de exportaciones)

Nos permite hacer que módulos, componentes, directivas y pipes, estén disponibles para otros elementos de la aplicación:

Como primera regla tenemos:

Hay que exportar cualquier componente, directiva o pipe siempre que otro componente lo necesite.

Un módulo puede reexportar, es decir, hacer disponibles a otros algo que él mismo ha tenido que importar de forma previa. Esto es útil para encapsular en un mismo sitio todo lo común en nuestra aplicación, como el módulo de formularios, el router.

La tercera es curiosa:

Un módulo angular puede reexportar algo, sin haberlo importado. El sentido que tiene, es que sólo hay que importar elementos si estos van a ser usados por los componentes declarados en el mismo módulo.

Por ejemplo:

Nuestro SharedModule puede exportar el FormsModule, el cual hace disponibles [(ngForm)] y otras directivas, sin tener que importarlo para sí.

Bien, la cuarta:

Nunca exportes un servicio.

La idea es que:

El servicio se escribe en el ‘providers’ del módulo raíz AppModule. El motivo es que el inyector de dependencias de angular hará disponible el servicio para todos los componentes, por eso es un servicio, y por eso se trata de forma distinta, es decir, se comparte por defecto con todas las demás elementos de la aplicación.

Imports array (array de importaciones)

Nos permite extender las funcionalidades de un módulo, incluyendo otros:

También hay unas reglas a tener en cuenta:

Importar un módulo hace que cualquier componente, directiva y pipe del importado estén disponibles en el importador.

En nuestro ejemplo:

Importamos FormsModule porque queremos tener disponible [(ngModel)] para cualquier elemento de AppModule, para trabajar con los formularios.

La siguiente regla es de sentido común:

Importa sólo lo que necesite este módulo.

La tercera es interesante:

Importar un módulo no da acceso a sus módulos importados.

Miremos el ejemplo:

Si AppModule importa SharedModule, tendrá el primero disponible el componente del segundo: StarComponent.

Luego si SharedModule importa FormsModule, significa que las directivas de ngModel están disponibles para SharedModule.

Sin embargo:

Lo de FormsModule no esta disponible para AppModule, por tanto nuestra ProductList no usará ngModel.

Es decir hemos visto que las importaciones no son heredadas, no existe propiedad transitiva.

Para solventar la situación podemos hacer que SharedModule reexporte a FormsModule:

En definitiva que los módulos son como cajas y no como árboles.

Providers array (array de proveedores / servicios)

Permite registrar servicios, tanto a nivel de módulos como a nivel de componentes:

Existen unas reglas importantes:

Cualquier proveedor de servicios añadido al array de providers es registrado en la raíz de la aplicación.

Es decir:

Si tenemos nuestro servicio ProductService en el array providers de ProductModule, estaríamos haciendo disponible el servicio a toda la aplicación.

La segunda regla es interesante:

No añadas servicios al array providers de un módulo compartido. La razón es que un servicio se pretende que sea una sola instancia accesible desde toda la aplicación , es decir un singleton. Mientras que un módulo compartido su idea es proveer de múltiples instancias, lo cual choca con el propósito de un servicio.

La autora nos recomienda:

Crear un CoreModule donde aglutinar los servicios e importarlo una sola vez en AppModule.

Además:

Los guardas de enrutamiento, es decir, los servicios que validan la navegación, deben ser añadidos al array providers de un módulo angular. Esto es así porque necesitamos que estén disponibles en el nivel más alto de nuestra aplicación: a nivel de módulo, es decir en toda la aplicación.

Refactorizar la aplicación

Hasta el momento nuestra aplicación es un mazacote:

Donde estamos mezclando las responsabilidades.

La idea es reorganizarla:

El primer paso es crear un módulo de funcionalidades, y como la que tenemos es la lista de productos pues:

Y añadiríamos los componentes productList y productDetails:

Además el productList usa el pipe para convertir guiones en espacios:

Y también emplean el starComponent para las puntuaciones:

Además necesitamos el enrutamiento:

También el productList usa el [(ngModel)] para el filtrado:

Luego necesitamos *ngIf, *ngFor y pensaríamos que hace falta importar BrowserModule que es donde están. Sin embargo, resulta que BrowserModule es especial porque sólo debería ser importado en el módulo raíz de la aplicación, AppModule, ya que su misión es dar los recursos para ejecutar la aplicación en el servidor web.

La idea entonces es importar CommonModule, donde también se incluyen directivas como *ngIf, *ngFor:

Además necesitamos integrar los servicios:

Por último es imprescindible conectar ProductModule con la raíz de la aplicación, AppModule:

Código para extraer ProductModule:

La idea es crear nuestro nuevo módulo y para ello usamos la terminal:

Mediante la sentencia:

‘ ng g m products/product — flat -m app.module ‘

Expresamos que queremos usar angular CLI, para ello ng; generamos por eso la g; un módulo, de ahí la m; en la carpeta products con el nombre product; de forma que queremos que NO nos cree otra carpeta products (ya tenemos una en el proyecto) por eso ponemos — flat; y -m app.module expresa que queremos vincular el módulo recién creado con AppModule.

Al ejecutarlo:

Vemos que se crea y enlaza.

Como imports en AppModule:

Si observamos el módulo creado:

Vemos que le ha puesto el decorador @NgModule, y ha importado lo que ha necesitado.

Luego declaramos los componentes asociados al módulo en declarations:

Después borramos los componentes del AppModule:

Y las importaciones asociadas:

Volviendo al ProductModule:

Vemos que ya importó de forma automática CommonModule, y nosotros añadimos FormsModule y RouterModule.

Además borramos FormsModule del AppModule y su importación:

A continuación hay algo interesante:

En AppModule declaramos las rutas mediante forRoot([]) pero no va a ser así en ProductModule.

La idea es coger las rutas que involucran productos desde el AppModule y emplazarlas en ProductModule

Pegándolas en ProductModule:

Con la diferencia de que en ProductModule se emplea forChild para configurar las rutas. El motivo, es que este método no hace otra instancia del router, sino que preserva el del padre y nos permite extenderlo añadiendo más rutas.

forRoot()

Registra el servicio, y como sólo se tiene que registrar un servicio una vez:

Usamos forChild()

Además hay que registrar los servicios en ProductModule en providers:

Por tanto en AppModule se borra el servicio ProductGuardService:

Y el ProductService que estaba en AppComponent:

Como vemos la aplicación se ejecuta:

Bien:

Una cuestión, la idea es hacer este proceso con cada funcionalidad, pero en realidad podemos simplificarlo todo, si extraemos lo común, lo que se usaría en las demás funcionalidad a su propio módulo:

Por ejemplo, el *ngIf y *ngFor de CommonModule, el [(ngForm)] de FormsModule y las estrellas para las puntuaciones son comunes, por tanto nos convendría sacarlos en otro módulo.

Shared Module

La idea es crear un módulo que albergue todo lo compartido por la mayoría de elementos de la aplicación, para tener que escribirlo una sola vez, y poder reutilizarlo en toda la aplicación:

En nuestro ejemplo, como componente sólo necesitamos el StarComponent:

Luego importamos lo que hace falta para que funcione el StarComponent:

Después exportamos el StarComponent, de forma que esté disponible para todo aquel otro módulo que importe SharedModule.

Luego reexportamos CommonModule y FormModule para que los módulos que importen a SharedModule los tengan a su disposición:

Por último exportamos todo:

Código

La idea es usar la terminal:

Mediante:

‘ ng g m shared/shared — flat -m products/product.module ‘

Nos creamos el módulo nuevo y lo enlazamos al anterior.

De forma que ya nos lo enlaza con ProductModule:

El SharedModule creado queda así:

Como primer paso declaramos StarComponent:

Para hacerlo disponible, lo exportamos:

Además también exportamos los otros dos:

Luego volviendo al ProductModel borramos el StarComponent

Y los otros dos:

Ya que acabamos de hacerlos disponibles mediante SharedModule

Y la aplicación sigue funcionando.

Por tanto hemos logrado separar las responsabilidades, de forma que en ProductModule sólo hay productos:

Y SharedModule incluye lo compartido por toda la aplicación:

Resumen de lo aprendido

Hemos pasado de un mazacote:

A una arquitectura más sensata:

Repositorio de la autora para los archivos de inicio

https://github.com/DeborahK/Angular-GettingStarted

Repositorio propio

https://github.com/YoneMoreno/LearningAngular

--

--