En el desarrollo de proyectos Angular, generalmente usamos el enlace de propiedades de entrada y el enlace de eventos de salida para la comunicación de componentes. Sin embargo, la entrada y la salida solo se pueden usar en padre-hijo. componentes transmiten información. Los componentes forman un árbol de componentes basado en la relación de llamada. Si solo hay enlaces de propiedad y enlaces de eventos, entonces dos componentes de relación no directa deben comunicarse a través de cada punto de conexión. El intermediario debe procesar y pasar continuamente algunas cosas que hace. No es necesario saber información (Figura 1 a la izquierda). El servicio inyectable proporcionado en Angular se puede proporcionar en módulos, componentes o instrucciones y, combinado con la inyección en el constructor, puede resolver este problema (en la Figura 1). [Tutoriales relacionados recomendados: "tutorial angular"]
Figura 1 Modelo de comunicación de componentes
La imagen de la izquierda solo transmite información a través de los componentes padre-hijo. El nodo a y el nodo b necesitan pasar a través de muchos nodos para comunicarse; si el nodo c quiere controlar el nodo b a través de alguna configuración, los nodos entre ellos también deben configurarse; atributos o eventos adicionales para transmitir de forma transparente la información correspondiente. El nodo c del modo de inyección de dependencia en la imagen de la derecha puede proporcionar un servicio para que los nodos a y b se comuniquen. El nodo a se comunica directamente con el servicio proporcionado por el nodo c, y el nodo b también se comunica directamente con el servicio proporcionado por el nodo c. Finalmente, la comunicación se simplifica y el nodo medio no está acoplado a esta parte del contenido y no tiene una conciencia obvia de la comunicación que ocurre entre los componentes superior e inferior.
La inyección de dependencia (DI) no es exclusiva de Angular. Es un medio para implementar el patrón de diseño de inversión de control (IOC). La aparición de la inyección de dependencia resuelve el problema del sobreacoplamiento manual. La creación de instancias y todos los recursos no pueden ser utilizados por ambas partes, en lugar de ser proporcionada por centros de recursos o terceros que no utilizan recursos, puede traer muchos beneficios. En primer lugar, la gestión centralizada de recursos hace que los recursos sean configurables y fáciles de gestionar. En segundo lugar, reduce el grado de dependencia entre las dos partes que utilizan recursos, que es lo que llamamos acoplamiento.
Una analogía con el mundo real es que cuando compramos un producto como un lápiz, sólo necesitamos encontrar una tienda para comprar un producto tipo lápiz. No nos importa dónde se produce el lápiz ni cómo se fabrican la madera y la mina. están unidos. Solo lo necesitamos para completar la función de escritura de un lápiz. No tendremos ningún contacto con el fabricante o la fábrica de lápices específicos. En cuanto a la tienda, puede adquirir lápices en los canales adecuados y realizar la configurabilidad de los recursos.
Combinado con el escenario de codificación, más específicamente, los usuarios no necesitan crear instancias explícitamente (nuevas operaciones) para inyectar y usar instancias. La creación de instancias está determinada por los proveedores. La gestión de recursos se realiza a través de tokens. Dado que no le importa el proveedor ni la creación de instancias, el usuario puede utilizar algunos métodos de inyección local (configuración secundaria de tokens) para finalmente lograr el reemplazo de instancias y el modo de inyección de dependencias. Aplicaciones y programación de aspectos (AOP). ) se complementan.
La inyección de dependencia es uno de los módulos centrales más importantes del marco Angular. Angular no solo proporciona inyección de tipo de servicio, sino que el árbol de componentes en sí es un árbol de dependencia de inyección, y también se pueden inyectar funciones y valores. Es decir, en el marco Angular, los componentes secundarios pueden inyectar instancias del componente principal a través del token del componente principal (generalmente el nombre de la clase). En el desarrollo de bibliotecas de componentes, hay una gran cantidad de casos en los que la interacción y la comunicación se logran mediante la inyección de componentes principales, incluido el montaje de parámetros, el intercambio de estados e incluso la obtención del DOM del nodo donde se encuentra el componente principal, etc.
Para utilizar la inyección angular, primero debe comprender su proceso de resolución de inyección. De manera similar al proceso de análisis de node_modules, cuando no se encuentran dependencias, las dependencias siempre subirán a la capa principal para encontrar dependencias. La versión anterior de Angular (anterior a v6) divide el proceso de análisis de inyección en inyectores de módulos multinivel, inyectores de componentes multinivel e inyectores de elementos. La nueva versión (posterior a v9) se simplifica a un modelo de dos niveles. La primera cadena de consulta es el inyector de elementos de nivel DOM estático, el inyector de componentes, etc., denominados colectivamente inyectores de elementos, y la otra cadena de consulta es el inyector de módulo. El orden de análisis y el valor predeterminado después de un error de análisis se explican más claramente en el documento de comentarios del código oficial (provider_flag).
Figura 2 El proceso de búsqueda de dependencias del inyector de dos niveles (fuente de imagen)
significa que los componentes/instrucciones y el contenido inyectado en el nivel de componente/instrucción primero buscarán dependencias en los elementos en la vista de componentes hasta el elemento raíz. Si no se encuentra, en el elemento El módulo actual, se busca la referencia (incluida la referencia del módulo y la referencia de carga diferida de enrutamiento) del módulo principal del módulo hasta el módulo raíz y el módulo de plataforma.
Tenga en cuenta que el inyector aquí tiene herencia. El inyector de elemento puede crear y heredar la función de búsqueda del inyector del elemento principal, y el inyector de módulo es similar. Después de una herencia continua, se vuelve un poco como la cadena prototipo de objetos js.
comprenden el orden de prioridad de la resolución de dependencias y podemos proporcionar contenido en el nivel apropiado. Ya sabemos que viene en dos tipos: inyección de módulo e inyección de elemento.
Inyector de módulo: los proveedores se pueden configurar en el atributo de metadatos de @NgModule, y también puede usar la declaración @Injectable proporcionada después de que v6 provideIn se declare como el nombre del módulo, 'raíz', etc. (En realidad, hay dos inyectores encima del módulo raíz, Platform y Null. No se discutirán aquí).
Inyector de elementos: proveedores, viewProviders se pueden configurar en el atributo de metadatos del componente @Component o en el @ de la directiva. proveedores en metadatos de la directiva
Además, de hecho, además de utilizar el inyector de módulo declarado, el decorador @Injectable también se puede declarar como un inyector de elementos. Más a menudo se declarará como se proporciona en la raíz para implementar un singleton. Integra metadatos a través de la propia clase para evitar que los módulos o componentes declaren directamente explícitamente al proveedor. De esta manera, si la clase no tiene ningún servicio de directiva de componentes y otras clases para inyectarlo, no habrá código vinculado a la declaración de tipo. el compilador puede ignorarlo, logrando así Agitar el árbol.
Otra forma de proporcionarlo es dar directamente el valor al declarar InkToken.
Aquí están las plantillas abreviadas para estos métodos:
@NgModule({ proveedores: [ //inyector de módulo] }) clase de exportación MyModule {}
@Component({ proveedores: [ // elemento inyector - componente], verProveedores: [ //Inyector de elementos - Vista de componentes] }) clase de exportación MyComponent {}
@Directive({ proveedores: [ // elemento inyector - directiva] }) clase de exportación MyDirective {}
@Injectable({ proporcionado en: 'raíz' }) clase de exportación MyService {}
export const MY_INJECT_TOKEN = new injectionToken<MyClass>('my-inject-token', { proporcionado en: 'raíz', fábrica: () => { devolver nueva MiClase(); } });
Las diferentes opciones para proporcionar ubicaciones de dependencia traerán algunas diferencias, que en última instancia afectarán el tamaño del paquete, el alcance en el que se pueden inyectar las dependencias y el ciclo de vida de las dependencias. Existen diferentes soluciones aplicables para diferentes escenarios, como singleton (raíz), aislamiento de servicios (módulo), múltiples ventanas de edición (componente), etc. Debe elegir una ubicación razonable para evitar información compartida inapropiada o empaquetado de código redundante.
solo proporcionan inyección de instancia, no mostrará la flexibilidad de la inyección de dependencia del marco Angular. Angular proporciona muchas herramientas de inyección flexibles. useClass crea automáticamente nuevas instancias, useValue usa valores estáticos, useExisting puede reutilizar instancias existentes y useFactory se construye a través de funciones, con departamentos específicos y parámetros de constructor específicos. Estas combinaciones pueden ser muy versátiles. Puede cortar el token de una clase y reemplazarlo con otra instancia que haya preparado. Puede crear un token para guardar el valor o la instancia primero y luego reemplazarlo nuevamente cuando necesite usarlo más adelante. la función de fábrica para devolverla. La información local de la instancia se asigna a otro objeto o valor de atributo. La jugabilidad aquí se explicará a través de los siguientes casos, por lo que no entraré en detalles aquí. El sitio web oficial también tiene muchos ejemplos como referencia.
La inyección en Angular se puede inyectar dentro del constructor, o puede hacer que el inyector obtenga elementos inyectados existentes a través del método get.
Angular admite agregar decoradores para marcar al inyectar,
, hay un artículo "¿@Self o @Optional @Host? La guía visual para los decoradores Angular DI". que muestra muy vívidamente que si se utilizan diferentes decoradores entre los componentes principal y secundario, los ejemplos que eventualmente se verán afectados son: diferencia.
Figura 3 Resultados de filtrado de diferentes decoradores inyectados
entre la vista de host y los decoradores @Host, @Host puede ser el más difícil de entender. Aquí hay algunas instrucciones específicas para @Host. La explicación oficial del decorador @Host es
... recuperar una dependencia de cualquier inyector hasta llegar al elemento host.
Aquí Host significa host. El decorador @Host limitará el alcance de la consulta dentro del elemento host. ¿Qué es un elemento anfitrión? Si el componente B es un componente utilizado por la plantilla del componente A, entonces la instancia del componente A es el elemento anfitrión de la instancia del componente B. El contenido generado por la plantilla del componente se denomina Vista. La misma Vista puede tener diferentes vistas para diferentes componentes. Si el componente A usa el componente B dentro del alcance de su propia plantilla (ver Figura 4), la vista (parte del cuadro rojo) formada por el contenido de la plantilla de A es la vista incrustada del componente A, y el componente B está dentro de esta vista, por lo que para B , esta vista es la vista del host de B. El decorador @Host limita el alcance de la búsqueda a la vista del host. Si no se encuentra, no aparecerá.
Figura 4 Vista integrada y vista de host
Usemos casos reales para ver cómo funciona la inyección de dependencia, cómo solucionar errores y cómo jugar.
El componente de ventana modal de la biblioteca de componentes DevUI proporciona un servicio ModalService, que puede abrir un cuadro modal y configurarse como un componente personalizado. Los estudiantes de negocios a menudo informan errores al usar este componente, diciendo que el paquete no puede encontrar el componente personalizado.
Por ejemplo, se informa el siguiente error:
Figura 5 Al usar ModalService, hay un error al crear un componente que hace referencia a EditorX. No se puede encontrar el proveedor de servicios correspondiente.
Analice cómo ModalService crea componentes personalizados. Líneas 52 y 95 de la función Abrir del código fuente de ModalService. Como puede ver, si no se pasa componentFactoryResolver
, se utiliza componentFactoryResolver
inyectado por ModalService. En la mayoría de los casos, la empresa introducirá DevUIModule una vez en el módulo raíz, pero no introducirá ModalModule en el módulo actual. Es decir, la situación actual en la Figura 6 es así. Según la Figura 6, no hay EditorXModuleService en el inyector de ModalService.
Figura 6 Diagrama de relación de provisión de servicios del módulo
Según la herencia del inyector, hay cuatro soluciones:
colocar EditorXModule donde se declara ModalModule, para que el inyector pueda encontrar el EditorModuleService proporcionado por EditorXModule; esta es la peor solución, en sí misma La carga diferida implementada mediante loadChildren es para reducir la carga del módulo de la página de inicio. El resultado es que el contenido que debe usarse en la subpágina se coloca en AppModule. El módulo de texto enriquecido grande se carga en la primera carga, lo que agrava el problema. FMP (Primera pintura significativa).
Introduzca ModalService en el módulo que presenta EditorXModule y utiliza ModalService; es recomendable. Solo hay una situación que no es aconsejable, es decir, llamar a ModalService es otro servicio público de nivel superior, que aún coloca módulos innecesarios en la capa superior para cargar.
Al activar el componente usando ModalService, inyecte componentFactoryResolver
del módulo actual y páselo al parámetro de función abierta de ModalService; es recomendable introducir EditorXModule donde realmente se usa.
En el módulo utilizado, es recomendable proporcionar manualmente un ModalService, que soluciona el problema de inyectar búsqueda.
En realidad, los cuatro métodos resuelven el problema de EditorXModuleService en la cadena interna del inyector de componentFactoryResolver
utilizado por ModalService. Al garantizar que la cadena de búsqueda esté en dos niveles, este problema se puede resolver.
Resumen de puntos de conocimiento : herencia del inyector de módulo y alcance de búsqueda.
Por lo general, cuando usamos la misma plantilla en varios lugares, extraemos la parte común a través de la plantilla. Cuando se desarrolló anteriormente el componente DevUI Select, el desarrollador quería extraer la parte común e informó una. error.
Figura 7 Error de inyección y movimiento de código no encontrado
Esto se debe a que la instrucción CdkVirtualScrollFor necesita inyectar un CdkVirtualScrollViewport. Sin embargo, el sistema de herencia del inyector de inyección de elementos hereda el DOM de la relación AST estática y, por lo tanto, no es posible. Se produce el siguiente comportamiento de consulta y la búsqueda falla.
Figura 8 Rango de búsqueda de la cadena de consulta del inyector de elementos
Solución final: 1) Mantenga la posición del código original sin cambios o 2) Debe incrustar la plantilla completa para encontrarlo.
Figura 9 La incorporación del módulo completo permite a CdkVitualScrollFo encontrar CdkVirtualScrollViewport (Solución 2)
Resumen de puntos de conocimiento : la cadena de consulta del inyector de elementos es el ancestro del elemento DOM de la plantilla estática.
Este caso proviene de este blog "Angular: formulario basado en plantilla anidado".
También encontramos el mismo problema al utilizar la validación de formularios. Como se muestra en la Figura 10, por algunas razones encapsulamos las direcciones de los tres campos en un componente para su reutilización.
Figura 10: Encapsule los tres campos de dirección del formulario en un subcomponente.
En este momento, encontraremos que se informa un error. ngModelGroup
requiere un ControlContainer
dentro del host, que es el contenido proporcionado por la directiva ngForm.
Figura 11 ngModelGroup no puede encontrar ControlContainer.
Al observar el código de ngModelGroup, puede ver que solo agrega la restricción del decorador de host.
La Figura 12 ng_model_group.ts limita el alcance de la inyección de ControlContainer.
Aquí puede usar viewProvider con usingExisting para agregar el proveedor de ControlContainer a la vista de host de AddressComponent.
Figura 13 Uso de viewProviders para proporcionar
puntos de conocimiento de proveedores externos para componentes anidados Resumen de puntos de conocimiento: el maravilloso uso de viewProvider y usingExisting.
la carga diferida, lo que resulta en la imposibilidad de arrastrar y soltar entre sí. La plataforma empresarial interna implica arrastrar y soltar en varios módulos. Al cargar loadChildren, cada módulo El DragDropModule de la biblioteca de componentes DevUI está empaquetado por separado y este módulo proporciona un DragDropService. Las instrucciones de arrastrar y soltar se dividen en instrucciones arrastrables e instrucciones desplegables. Las dos instrucciones se comunican a través de DragDropService. Originalmente, era posible comunicarse introduciendo el mismo módulo y utilizando el servicio proporcionado por el módulo. Sin embargo, después de una carga diferida, el módulo DragDropModule se empaquetó dos veces, lo que también resultó en dos instancias aisladas. En este momento, la instrucción Draggable en un módulo con carga diferida no puede comunicarse con la instrucción Droppable en otro módulo con carga diferida, porque DragDropService no es la misma instancia en este momento.
Figura 14 La carga diferida de módulos hace que los servicios no sean la misma instancia/caso único.
Es obvio que nuestro requisito aquí es que necesitamos un singleton, y el método de singleton suele ser providerIn: 'root'
. ¿El DragDropService de la biblioteca de componentes? Es bueno proporcionar el dominio raíz directamente a nivel de módulo. Pero si lo piensas detenidamente, aquí hay otros problemas. La biblioteca de componentes en sí se proporciona para que la utilicen una variedad de empresas. Si algunas empresas tienen dos grupos de arrastrar y soltar correspondientes en dos lugares de la página, no desean estar vinculadas. En este momento, el singleton destruye el aislamiento natural basado en el módulo.
Entonces sería más razonable implementar el reemplazo singleton por parte del lado comercial. Recuerde la cadena de consulta de dependencia que mencionamos anteriormente. Primero se busca el inyector de elementos. Si no se encuentra, se inicia el inyector de módulos. Entonces, la idea de reemplazo es que podamos proporcionar proveedores a nivel de elemento.
Figura 15 Utilice el método de extensión para obtener un nuevo DragDropService y márquelo como proporcionado en el nivel raíz
Figura
16 Puede usar el mismo selector para superponer instrucciones repetidas, superponer una instrucción adicional en la instrucción Draggable y Droppable de la biblioteca de componentes y reemplazar el token de DragDropService con DragDropGlobalService que ha proporcionado un singleton en la raíz.
En las Figuras 15 y 16, inyectamos elementos. El controlador superpone instrucciones y reemplaza el token DragDropService con una instancia de nuestro propio singleton global. En este momento, donde necesitamos usar el DragDropService singleton global, solo necesitamos introducir el módulo que declara y exporta estas dos instrucciones adicionales para permitir que la instrucción arrastrable Instrucción desplegable de la biblioteca de componentes se comunique entre módulos cargados de forma diferida.
Resumen de puntos de conocimiento : los inyectores de elementos tienen mayor prioridad que los inyectores de módulos.
La temática de la biblioteca de componentes DevUI utiliza atributos personalizados CSS (variables CSS) para declarar el valor de la variable CSS de la raíz para lograr el cambio de tema. . Si queremos mostrar vistas previas de diferentes temas al mismo tiempo en una interfaz, podemos volver a declarar las variables CSS localmente en el elemento DOM para lograr la función de los temas locales. Cuando estaba creando un generador de tramado de temas, utilicé este método para aplicar un tema localmente.
Figura 17 Función de tema local
, pero no es suficiente aplicar valores de variables CSS localmente. Hay algunas capas emergentes desplegables que están adjuntas a la parte posterior del cuerpo de forma predeterminada, lo que significa que su capa adjunta está afuera. las variables locales, lo que conducirá a una situación muy embarazosa. El cuadro desplegable del componente del tema local muestra el estilo del tema externo.
Figura 18 El componente del tema local está adjunto al cuadro desplegable de superposición externa.
¿Qué debo hacer si el tema es incorrecto? Deberíamos mover el punto de conexión nuevamente dentro del dominio temático local.
Se sabe que la superposición del componente DatePickerPro de la biblioteca de componentes DevUI utiliza la superposición de Angular CDK. Después de una ronda de análisis, la reemplazamos con inyección de la siguiente manera:
1) Primero, heredamos OverlayContainer e implementamos nuestro propio ElementOverlayContainer como se muestra. abajo.
Figura 19 Personalice ElementOverlayContainer y reemplace la lógica _createContainer
2) Luego proporcione directamente nuestro nuevo ElementOverlayContainer en el lado del componente de la vista previa y proporcione una nueva superposición para que la nueva superposición pueda usar nuestro OverlayContainer. Originalmente, Overlay y OverlayContainer se proporcionan en la raíz, aquí debemos cubrir estos dos.
Figura 20 Reemplace OverlayContainer con un ElementOverlayContainer personalizado y proporcione una nueva superposición.
Ahora vaya a la vista previa del sitio web y el DOM de la capa emergente se adjuntará correctamente al elemento de vista previa del componente.
Figura 21 El contenedor Overlay de cdk está adjunto al dom especificado. La vista previa parcial del tema es exitosa.
También hay un OverlayContainerRef personalizado en la biblioteca de componentes de DevUI para algunos componentes y bancos de cajones de cajas modales, que también deben reemplazarse en consecuencia. Finalmente, se pueden implementar capas emergentes y otras capas emergentes para que admitan perfectamente los temas locales.
Resumen de puntos de conocimiento : Un buen patrón de abstracción puede hacer que los módulos sean reemplazables y lograr una programación de aspecto elegante.
con el último caso, me gustaría hablar sobre un enfoque menos formal. Para facilitar la comprensión de todos sobre la naturaleza del proveedor, configurar el proveedor significa esencialmente dejar que le ayude a crear una instancia o asignarla a una instancia existente.
Sabemos que si se usa cdkOverlay, si queremos que el cuadro emergente siga la barra de desplazamiento y se suspenda en la posición correcta, debemos agregar la instrucción cdkScrollable a la barra de desplazamiento.
Sigue siendo el mismo escenario que el ejemplo anterior. Toda nuestra página se carga mediante enrutamiento. Para simplificar, escribí la barra de desplazamiento en el host del componente.
Figura 22 La barra de desplazamiento de desbordamiento de contenido escribe overflow:auto en el componente:host.
De esta manera, encontramos un problema más difícil: el módulo está especificado por la definición del enrutador, es decir, <app-theme-picker-customize></app-theme-picker-customize>
, entonces, ¿cómo agregar la instrucción cdkScrollable? La solución es la siguiente. Parte del código está oculto aquí y solo queda el código principal.
Figura 23 Cree una instancia mediante inyección y llame manualmente al ciclo de vida.
Aquí, se genera una instancia de cdkScrollable mediante inyección y el ciclo de vida se llama sincrónicamente durante la etapa del ciclo de vida del componente.
Esta solución no es un método formal, pero resuelve el problema. Se deja aquí como idea y exploración para que los lectores la prueben.
Resumen de puntos de conocimiento : el proveedor de configuración de inyección de dependencia puede crear instancias, pero tenga en cuenta que las instancias se tratarán como clases de servicio ordinarias y no pueden tener un ciclo de vida completo.
consulte esta publicación de blog: "Representación de aplicaciones Angular en la Terminal".
Figura 24 Reemplace el renderizador RendererFactory2 y otros contenidos para permitir que Angular se ejecute en la terminal.
El autor reemplazó RendererFactory2 y otros renderizadores para que la aplicación Angular pueda ejecutarse en la terminal. Ésta es la flexibilidad del diseño angular. Incluso la plataforma se puede reemplazar. Es potente y flexible. Los detalles detallados del reemplazo se pueden encontrar en el artículo original y no se ampliarán aquí.
Resumen de puntos de conocimiento : El poder de la inyección de dependencia es que el proveedor puede configurarlo usted mismo y finalmente implementar la lógica de reemplazo.
Este artículo presentó el modo de inyección de dependencia de inversión de control y sus beneficios. También presentó cómo la inyección de dependencia en Angular busca dependencias, cómo configurar proveedores, cómo usar decoradores limitados y de filtrado para obtener la instancia deseada, y más allá de N. Los casos analizan cómo combinar los puntos de conocimiento de la inyección de dependencia para resolver problemas encontrados en el desarrollo y la programación.
Al comprender correctamente el proceso de búsqueda de dependencias, podemos configurar el proveedor en la ubicación exacta (Casos 1 y 2), reemplazar otras instancias con singletons (Casos 4 y 5) e incluso conectarnos a través de las restricciones de los paquetes de componentes anidados proporcionados (. Caso 3) o utilice la curva de método proporcionada para implementar la creación de instancias de instrucciones (Caso 6).
El caso 5 parece ser un reemplazo simple, pero poder escribir una estructura de código que pueda reemplazarse requiere una comprensión profunda del modo de inyección y una abstracción mejor y razonable de cada función. Si la abstracción no es apropiada, las dependencias. No se puede utilizar. El efecto máximo de la inyección. El modo de inyección proporciona más espacio posible para que los módulos sean enchufables, enchufables y basados en piezas, lo que reduce el acoplamiento y aumenta la flexibilidad, de modo que los módulos puedan trabajar juntos de manera más elegante y armoniosa.
La poderosa función de inyección de dependencia no solo puede optimizar las rutas de comunicación de los componentes, sino que, lo que es más importante, también puede lograr la inversión de control, exponiendo los componentes encapsulados a más aspectos de la programación y la implementación de cierta lógica específica del negocio también puede volverse más flexible.