Comprender la organización de las entidades con las que trabaja no es algo que un desarrollador obtiene inmediatamente al escribir sus primeros proyectos en Angular.
Y uno de los problemas que puede surgir es el uso ineficiente de los módulos angulares, en particular el módulo de la aplicación sobrecargada: crearon un nuevo componente, lo incorporaron y el servicio también fue allí. Y todo parece ser genial, todo funciona. Sin embargo, con el tiempo, dicho proyecto será difícil de mantener y optimizar.
Afortunadamente, Angular proporciona a los desarrolladores la capacidad de crear sus propios módulos, y también los llama módulos de funciones.

Módulos de funciones de dominio
El módulo de la aplicación sobrecargada debe dividirse. Por lo tanto, lo primero que debe hacer es seleccionar piezas grandes en la aplicación y colocarlas en módulos separados.
Un enfoque popular es dividir la aplicación en módulos de funciones de dominio. Están diseñados para dividir la interfaz en función de una tarea clave (dominio) que realiza cada parte de ella. Los ejemplos de módulos de características de dominio incluyen una página de edición de perfil, una página de producto, etc. En pocas palabras, todo eso podría estar debajo del elemento del menú.

Todos los anuncios en los cuadros azules, así como el contenido de otros elementos del menú, merecen sus propios módulos de funciones de dominio.
Los módulos de características de dominio pueden usar un número ilimitado de declarables (componentes, directivas, canalizaciones), sin embargo, solo exportan el componente que representa la interfaz de usuario de este módulo. Los módulos de características de dominio se importan, generalmente en un módulo más grande.
Los módulos de funciones de dominio generalmente no declaran servicios dentro de sí mismos. Sin embargo, si anuncian, la vida útil de estos servicios debería limitarse a la vida útil del módulo. Esto se puede lograr utilizando servicios de carga diferida o publicitarios en el componente externo del módulo. Estos métodos se discutirán más adelante en el artículo.
Carga perezosa
Dividir la aplicación en módulos de funciones de dominio le permite utilizar la carga diferida . Por lo tanto, puede eliminar del paquete original lo que el usuario no necesita al abrir la aplicación por primera vez: perfil de usuario, página del producto, página de fotos, etc. Todo esto se puede cargar a pedido.
Servicios e inyectores
La aplicación se divide en piezas grandes: módulos, y algunos de estos módulos se cargan a pedido. Pregunta: ¿dónde deben anunciarse los servicios globales? ¿Y si quisiéramos limitar el alcance del servicio?
Inyectores de módulos con carga lenta
A diferencia de los declarables, cuya existencia debe declararse en cada módulo donde se utilizan, los singletones de servicios declarados una vez en cualquiera de los módulos están disponibles en toda la aplicación.
Resulta que los servicios se pueden declarar en cualquier módulo y no te preocupes? Realmente no es así.
Lo anterior es cierto si la aplicación usa solo un inyector global, pero a menudo todo es algo más interesante. Los módulos con carga lenta tienen su propio inyector (componentes también, pero más sobre eso más adelante). ¿Por qué los módulos con carga lenta incluso crean su propio inyector? La razón radica en cómo funciona la inyección de dependencia en Angular.
El inyector se puede reponer con nuevos proveedores hasta que comience a usarse. Tan pronto como el inyector crea el primer servicio, se cierra para agregar nuevos proveedores.
Cuando se inicia la aplicación, Angular primero configura el inyector raíz, fijando en él los proveedores que se declararon en el módulo de la aplicación y en los módulos importados en él. Esto ocurre incluso antes de crear los primeros componentes y antes de proporcionarles dependencias.
En una situación en la que el módulo se carga lentamente, el inyector global se ha configurado durante mucho tiempo y ha entrado en funcionamiento. El módulo cargado no tiene más remedio que crear su propio inyector. Este inyector será hijo del inyector utilizado en el módulo que inició la carga. Esto lleva a un comportamiento familiar para los desarrolladores de javascript en la cadena de prototipos: si el servicio no se encontró en el inyector de un módulo cargado con pereza, el marco DI lo buscará en el inyector principal, etc.
Por lo tanto, los módulos con carga lenta le permiten declarar servicios que estarán disponibles solo en el marco de este módulo. Los proveedores también se pueden redefinir, nuevamente, al igual que en los prototipos de JavaScript.
Volviendo a los módulos de características de dominio, el comportamiento descrito es una forma de limitar la vida de los proveedores anunciados en ellos.
Módulo central
Aún así, ¿dónde deben anunciarse los servicios globales, como los servicios de autorización, los servicios API, los servicios de usuario, etc.? La respuesta simple está en el módulo de la aplicación. Sin embargo, para restablecer el orden en el módulo de la aplicación (esto es lo que estamos haciendo), debe declarar los servicios globales en un módulo separado, llamado módulo Core, e importarlo SOLO en el módulo de la aplicación. El resultado será el mismo que si los servicios se declararan directamente en el módulo de la aplicación.
A partir de la versión 6, en el angular había una oportunidad de declarar servicios globales sin importarlos a ningún lado. Todo lo que necesita hacer es agregar la opción proporcionada a Injectable y especificar el valor 'raíz' en ella. Los servicios declarados de esta manera están disponibles para toda la aplicación y, por lo tanto, no es necesario declararlos en el módulo.
Además del hecho de que este enfoque mira hacia el brillante futuro del angular sin módulos, también ayuda a truncar el código innecesario.
Prueba Singleton
Pero, ¿qué pasa si alguien en el proyecto quiere importar el módulo Core en otro lugar? ¿Es posible protegerse de esto? Usted puede
Agregue un constructor al módulo Core que le pida que inyecte el módulo Core en él (es correcto, usted mismo) y marque este anuncio con decoradores opcionales y SkipSelf. Si el inyector pone una dependencia en una variable, alguien está tratando de volver a declarar el módulo Core.

Usando el enfoque descrito en BrowserModule .
Este enfoque se puede utilizar con módulos y servicios.
Anuncio de servicio en componente
Ya hemos considerado una forma de limitar el alcance de los proveedores que usan carga diferida, pero aquí hay otra.
Cada instancia de componente tiene su propio inyector, y para configurarlo, al igual que el decorador NgModule, el decorador de componentes tiene la propiedad de proveedores. Y también: una propiedad adicional de viewProviders. Ambos sirven para configurar los componentes del inyector, sin embargo, los proveedores declarados en cada uno de los métodos tienen diferentes ámbitos.
Para comprender la diferencia, necesita un breve resumen.
Un componente consta de vista y contenido.

Ver componentes

Componentes de contenido
Todo lo que hay en el archivo html del componente es su vista, mientras que lo que se pasa entre las etiquetas de los componentes de apertura y cierre es su contenido.
El resultado obtenido:

El resultado
Por lo tanto, los proveedores agregados a los proveedores están disponibles tanto en la vista del componente en el que se declaran como en el contenido que se pasa al componente. Mientras que viewProviders, como su nombre lo indica, hace que los servicios sean visibles solo para verlos y los cierra al contenido.
A pesar de que es una práctica recomendada declarar servicios en el inyector raíz, existen situaciones en las que se puede utilizar el componente inyector:
La primera es cuando cada nueva instancia de componente debe tener su propia instancia de servicio. Por ejemplo, un servicio que almacena datos específicos para cada nueva instancia de un componente.
Para otro escenario, debemos recordar que, aunque los módulos de características de Dominio pueden declarar algunos proveedores que solo necesitan, es aconsejable que estos proveedores mueran con estos módulos. En este caso, declararemos el proveedor en el componente más externo, el que se exporta desde el módulo.
Por ejemplo, el módulo de características de dominio que es responsable del perfil de usuario. Declararemos el servicio necesario solo para esta parte de la aplicación en los proveedores del componente más externo, UserProfileComponent. Ahora todos los declarables que se declaran en el marcado de este componente, así como que se pasan a él en el contenido, recibirán la misma instancia de servicio.
Componentes reutilizables
¿Qué hacer con los componentes que queremos reutilizar? Tampoco hay una respuesta definitiva a esta pregunta, pero hay enfoques probados.
Módulo Compartido
Todos los componentes utilizados en el proyecto se pueden almacenar en un módulo, exportándolos e importándolos a los módulos del proyecto donde estos componentes pueden ser necesarios.
En dicho módulo, puede colocar los componentes de un botón, una lista desplegable, un bloque de texto estilizado, etc., así como directivas y tuberías personalizadas.
Tal módulo generalmente se llama SharedModule.
Es importante tener en cuenta que SharedModule no debe declarar servicios. O declare usando el enfoque forRoot. Hablaremos de él un poco más tarde.
Aunque el enfoque SharedModules funciona, hay un par de puntos:
- No limpiamos la estructura de la aplicación, simplemente cambiamos el desorden de un lugar a otro;
- Este enfoque no mira hacia el brillante futuro de Angular, en el que no habrá módulos.
Un enfoque que carece de estas deficiencias es e implica la creación de un módulo para cada componente.
Módulo por componente o estafa (módulo angular de un solo componente)
Al crear un nuevo componente, debe ponerlo en su propio módulo. Debe importar las dependencias de los componentes en el mismo módulo.

Cada vez que se necesita un determinado componente en cualquier lugar de la aplicación, todo lo que hay que hacer es importar el módulo de este componente.
En inglés, este enfoque se llama módulo por componente o SCAM - módulo angular de un solo componente. Aunque el nombre contiene el componente de palabra, este enfoque también se aplica a las canalizaciones y directivas (SPAM, SDAM).
Probablemente la ventaja más significativa de este enfoque es la facilitación de las pruebas de componentes. Dado que el módulo creado para el componente lo exporta, y también contiene todas las dependencias que necesita, para configurar TestBed, simplemente ponga este módulo en importaciones.
Este enfoque contribuye al orden y la estructura en el código del proyecto, y también nos prepara para el futuro sin módulos, donde usar un componente en el diseño de otro, solo necesita declarar dependencias en la directiva Componente. Puedes mirar hacia el futuro un poco a través de este artículo .
Interfaz ModuleWithProviders
Si se inicia un módulo que contiene una declaración de servicios XYZ en el proyecto, y sucede que con el tiempo este módulo comenzó a usarse en todas partes, cada importación de este módulo intentará agregar servicios XYZ al inyector correspondiente, lo que inevitablemente provocará colisiones. Angular tiene un conjunto de reglas para este caso, que pueden no corresponder a lo que el desarrollador espera. Esto es especialmente cierto para el módulo inyector con carga lenta.
Para evitar problemas de colisión, Angular proporciona la interfaz ModuleWithProviders , que le permite conectar proveedores al módulo, mientras que los proveedores del módulo en sí no se ven afectados. Y esto es exactamente lo que se necesita en el caso descrito anteriormente.
Estrategias paraRoot (), forChild ()
Para que los servicios se reparen con precisión en el inyector global, el módulo con proveedores solo se importa en el AppModule. Del lado del módulo importado, solo necesita crear un método estático que devuelva ModuleWithProviders, que históricamente recibió el nombre de raíz.

Los métodos que devuelven ModuleWithProviders pueden ser cualquier número y pueden nombrarse como desee. forRoot es una convención más conveniente que un requisito.
Por ejemplo, RouterModule tiene un método estático forChild , que se utiliza para configurar el enrutamiento en módulos con carga lenta.
Conclusión
- Separe la interfaz de usuario por tareas clave y cree su propio módulo para cada parte seleccionada: además de una manera más conveniente de comprender la estructura del código del proyecto, tenga la oportunidad de cargar perezosamente partes de la interfaz
- Use inyectores de módulos y componentes con carga lenta si la arquitectura de la aplicación lo requiere
- Publique anuncios de servicio global en un módulo separado, módulo Core, e impórtelo solo en el módulo de la aplicación. Esto ayudará a limpiar el módulo de la aplicación.
- Mejor, use la opción proporcionadaIn con el valor raíz del decorador inyectable.
- Use hack con decoradores opcionales y SkipSelf para evitar la reimportación de módulos y servicios
- Almacene componentes, directivas y tuberías reutilizables en el módulo Compartido
- Sin embargo, el mejor enfoque, que también mira hacia el futuro y facilita las pruebas, es crear un módulo para cada componente (directivas y tuberías, también)
- Use la interfaz ModuleWithProviders si desea evitar conflictos de proveedores. Un enfoque popular es implementar el método forRoot para agregar proveedores al módulo raíz