No desenvolvimento de projetos Angular, geralmente usamos a ligação de propriedades de entrada e a ligação de eventos de saída para comunicação de componentes. componentes. Os componentes formam uma árvore de componentes com base no relacionamento de chamada. Se houver apenas ligações de propriedades e ligações de eventos, então dois componentes de relacionamento não diretos precisam se comunicar através de cada ponto de conexão. O intermediário precisa processar e passar continuamente algumas coisas que ele faz. não precisa saber informações (Figura 1 à esquerda). O Serviço Injetável fornecido em Angular pode ser fornecido em módulos, componentes ou instruções, e combinado com injeção no construtor, pode resolver este problema (logo na Figura 1). [Tutoriais relacionados recomendados: "tutorial angular"]
Figura 1 Modelo de comunicação de componentes
A imagem à esquerda apenas transmite informações por meio de componentes pai-filho. O nó a e o nó b precisam passar por muitos nós para se comunicarem; se o nó c quiser controlar o nó b por meio de alguma configuração, os nós entre eles também devem ser definidos; atributos ou eventos adicionais para transmitir de forma transparente as informações correspondentes. O nó c do modo de injeção de dependência na imagem à direita pode fornecer um serviço para os nós a e b se comunicarem. O nó a se comunica diretamente com o serviço fornecido pelo nó c, e o nó b também se comunica diretamente com o serviço fornecido pelo nó c. Finalmente, a comunicação é simplificada e o nó do meio não está acoplado a esta parte do conteúdo e não tem consciência óbvia da comunicação que ocorre entre os componentes superiores e inferiores.
A injeção de dependência (DI) não é exclusiva do Angular. É um meio de implementar o padrão de design de inversão de controle (IOC). O surgimento da injeção de dependência resolve o problema de sobreacoplamento manual. instanciação, e todos os recursos não podem ser usados. O gerenciamento de recursos por ambas as partes, em vez de ser fornecido por centros de recursos ou terceiros que não utilizam recursos, pode trazer muitos benefícios. Primeiro, o gerenciamento centralizado de recursos torna os recursos configuráveis e fáceis de gerenciar. Em segundo lugar, reduz o grau de dependência entre as duas partes que utilizam os recursos, o que chamamos de acoplamento.
Uma analogia com o mundo real é que quando compramos um produto como um lápis, só precisamos encontrar uma loja para comprar um produto do tipo lápis. Não nos importamos onde o lápis é produzido ou como a madeira e a grafite do lápis. estão colados. Só precisamos dele para completar a função de escrita de um lápis. Não teremos nenhum contato com o fabricante ou fábrica específico do lápis. Já a loja pode adquirir lápis nos canais adequados e perceber a configurabilidade dos recursos.
Combinado com o cenário de codificação, mais especificamente, os usuários não precisam criar explicitamente instâncias (novas operações) para injetar e usar instâncias. A criação de instâncias é determinada pelos provedores. O gerenciamento de recursos é feito por meio de tokens. Como não se preocupa com o provedor ou com a criação de instâncias, o usuário pode usar alguns métodos de injeção local (configuração secundária de tokens) para finalmente obter substituição de instância e modo de injeção de dependência. ) se complementam.
A injeção de dependência é um dos módulos principais mais importantes da estrutura Angular e não apenas fornece injeção de tipo de serviço, mas a própria árvore de componentes é uma árvore de dependência de injeção, e funções e valores também podem ser injetados. Ou seja, na estrutura Angular, os componentes filhos podem injetar instâncias do componente pai por meio do token do componente pai (geralmente o nome da classe). No desenvolvimento de bibliotecas de componentes, há um grande número de casos em que a interação e a comunicação são alcançadas pela injeção de componentes pais, incluindo montagem de parâmetros, compartilhamento de estado e até mesmo obtenção do DOM do nó onde o componente pai está localizado, etc.
Para usar injeção Angular, você deve primeiro entender seu processo de resolução de injeção. Semelhante ao processo de análise de node_modules, quando nenhuma dependência é encontrada, as dependências sempre irão para a camada pai para encontrar dependências. A versão antiga do Angular (anterior à v6) divide o processo de análise de injeção em injetores de módulo multinível, injetores de componentes multinível e injetores de elemento. A nova versão (após v9) é simplificada para um modelo de dois níveis. A primeira cadeia de consulta é o injetor de elemento de nível DOM estático, injetor de componente, etc., chamados coletivamente de injetores de elemento, e a outra cadeia de consulta é o injetor de módulo. A ordem de análise e o valor padrão após a falha na análise são explicados mais claramente no documento oficial de comentários do código (provider_flag).
Figura 2 O processo de pesquisa de dependência do injetor de dois níveis (fonte da imagem)
significa que os componentes/instruções e o fornecimento de conteúdo injetado no nível do componente/instrução procurarão primeiro por dependências nos elementos na visualização do componente até o elemento raiz . Se não for encontrado, no elemento O módulo atual, a referência (incluindo referência de módulo e referência de carregamento lento de roteamento) do módulo pai do módulo é pesquisada até o módulo raiz e o módulo de plataforma.
Observe que o injetor aqui tem herança. O injetor de elemento pode criar e herdar a função de pesquisa do injetor do elemento pai, e o injetor de módulo é semelhante. Após a herança contínua, torna-se um pouco como a cadeia de protótipos de objetos js.
entendem a ordem de prioridade da resolução de dependências e podem fornecer conteúdo no nível apropriado. Já sabemos que se trata de dois tipos: injeção de módulo e injeção de elemento.
Injetor de módulo: os provedores podem ser configurados no atributo de metadados de @NgModule e você também pode usar a instrução @Injectable fornecida após a v6 fornecerIn ser declarada como o nome do módulo, 'root', etc. (Na verdade, existem dois injetores acima do módulo raiz, Platform e Null. Eles não serão discutidos aqui.)
Elemento injetor: Provedores, viewProviders podem ser configurados no atributo de metadados do componente @Component, ou no @ da diretiva provedores nos metadados da diretiva
Além disso, além de usar o injetor de módulo declarado, o decorador @Injectable também pode ser declarado como um injetor de elemento. Mais frequentemente, será declarado como fornecido na raiz para implementar um singleton. Ele integra metadados através da própria classe para evitar que módulos ou componentes declarem explicitamente o provedor. Desta forma, se a classe não tiver nenhum serviço de diretiva de componente e outras classes para injetá-la, não haverá código vinculado à declaração de tipo e. ele pode ser ignorado pelo compilador, conseguindo assim Shake the tree.
Outra forma de fornecê-lo é fornecer o valor diretamente ao declarar o InjectionToken.
Aqui estão os modelos abreviados para esses métodos:
@NgModule({ provedores: [ //Módulo injetor] }) exportar classe MeuModule {}
@Component({provedores: [ // injetor de elemento - componente], visualizarProvedores: [ //Injetor de Elemento - Visualização de Componente] }) exportar classe MyComponent {}
@Directive({provedores: [ // injetor de elemento - diretiva] }) exportar classe MyDirective {}
@Injectable({ fornecidoIn: 'root' }) exportar classe MyService {}
export const MY_INJECT_TOKEN = new InjectionToken<MyClass>('my-inject-token', { fornecidoIn: 'root', fábrica: () => { return new MinhaClasse(); } });
Diferentes opções para fornecer locais de dependência trarão algumas diferenças, que em última análise afetarão o tamanho do pacote, o escopo no qual as dependências podem ser injetadas e o ciclo de vida das dependências. Existem diferentes soluções aplicáveis para diferentes cenários, como singleton (raiz), isolamento de serviço (módulo), múltiplas janelas de edição (componente), etc. Você deve escolher um local razoável para evitar informações compartilhadas inadequadas ou empacotamento de código redundante.
fornecerem apenas injeção de instância, isso não mostrará a flexibilidade da injeção de dependência da estrutura Angular. Angular fornece muitas ferramentas de injeção flexíveis. useClass cria automaticamente novas instâncias, useValue usa valores estáticos, useExisting pode reutilizar instâncias existentes e useFactory é construído por meio de funções, com deps especificados e parâmetros de construtor especificados. Você pode cortar o token de uma classe e substituí-lo por outra instância que você preparou. Você pode criar um token para salvar o valor ou instância primeiro e, em seguida, substituí-lo novamente quando precisar usá-lo mais tarde. a função de fábrica para retorná-la A informação local da instância é mapeada para outro objeto ou valor de atributo. A jogabilidade aqui será explicada através dos seguintes casos, então não vou entrar nisso aqui. O site oficial também traz muitos exemplos para referência.
A injeção em Angular pode ser injetada dentro do construtor, ou você pode fazer com que o injetor obtenha os elementos injetados existentes através do método get.
Angular suporta a adição de decoradores para marcar ao injetar,
, há um artigo "@Self ou @Optional @Host? O guia visual para decoradores Angular DI que mostra vividamente que se diferentes decoradores forem usados entre componentes pai e filho, os exemplos que eventualmente serão atingidos são: Que tipo de decoradores são usados? diferença.
Figura 3 Filtrando resultados de diferentes decoradores injetados
Entre a visualização Host e os decoradores @Host, @Host pode ser o mais difícil de entender. Aqui estão algumas instruções específicas para @Host. A explicação oficial do decorador @Host é
...recuperar uma dependência de qualquer injetor até atingir o elemento Host
aqui significa que o decorador @Host limitará o escopo da consulta ao elemento Host. O que é um elemento host? Se o componente B for um componente usado pelo modelo do componente A, então a instância do componente A será o elemento host da instância do componente B. O conteúdo gerado pelo modelo de componente é chamado de Visualização. A mesma Visualização pode ser visualizações diferentes para componentes diferentes. Se o componente A usa o componente B dentro de seu próprio escopo de modelo (veja a Figura 4), a visualização (parte da caixa vermelha) formada pelo conteúdo do modelo de A é a visualização incorporada do componente A, e o componente B está dentro desta visualização, então Para B , esta visão é a visão do host de B. O decorador @Host limita o escopo da pesquisa à visualização do host. Se não for encontrado, não aparecerá.
Figura 4 Visualização incorporada e visualização do host
Vamos usar casos reais para ver como funciona a injeção de dependência, como solucionar erros e como jogar.
O componente da janela modal da biblioteca de componentes DevUI fornece um serviço ModalService, que pode abrir uma caixa modal e pode ser configurado como um componente personalizado. Estudantes de administração geralmente relatam erros ao usar esse componente, dizendo que o pacote não consegue encontrar o componente personalizado.
Por exemplo, o seguinte erro é relatado:
Figura 5 Ao usar ModalService, ocorre um erro ao criar um componente que faz referência ao EditorX O provedor de serviço correspondente não pode ser encontrado.
Analise como ModalService cria componentes customizados. Como você pode ver, se componentFactoryResolver
não for passado, componentFactoryResolver
injetado por ModalService será usado. Na maioria dos casos, a empresa introduzirá o DevUIModule uma vez no módulo raiz, mas não introduzirá o ModalModule no módulo atual. Ou seja, a situação atual na Figura 6 é assim. Conforme Figura 6, não existe EditorXModuleService no injetor do ModalService.
Figura 6 Diagrama de relacionamento de prestação de serviço do módulo
De acordo com a herança do injetor, existem quatro soluções:
colocar EditorXModule onde ModalModule é declarado, para que o injetor possa encontrar o EditorModuleService fornecido pelo EditorXModule - esta é a pior solução, em si O carregamento lento implementado por loadChildren é reduzir o carregamento do módulo da página inicial. O resultado é que o conteúdo que precisa ser usado na subpágina é colocado no AppModule. O grande módulo rich text é carregado no primeiro carregamento, o que agrava o problema. FMP (Primeira Pintura Significativa) Não deve ser tomada.
Introduzir ModalService no módulo que apresenta o EditorXModule e usa ModalService - é aconselhável. Só existe uma situação que não é aconselhável, ou seja, chamar ModalService é outro serviço público de nível superior, que ainda coloca módulos desnecessários na camada superior para carregamento.
Ao acionar o componente usando ModalService, injete componentFactoryResolver
do módulo atual e passe-o para o parâmetro de função open do ModalService - é aconselhável introduzir o EditorXModule onde ele realmente é usado.
No módulo utilizado, é aconselhável fornecer manualmente um ModalService, que resolve o problema de injeção de pesquisa.
Os quatro métodos estão na verdade resolvendo o problema do EditorXModuleService na cadeia interna do injetor do componentFactoryResolver
usado pelo ModalService. Garantindo que a cadeia de pesquisa esteja em dois níveis, este problema pode ser resolvido.
Resumo dos pontos de conhecimento : herança do injetor de módulo e escopo de pesquisa.
Normalmente, quando usamos o mesmo modelo em vários locais, extrairemos a parte comum por meio do modelo. Quando o componente DevUI Select foi desenvolvido antes, o desenvolvedor queria extrair a parte comum e relatou um. erro.
Figura 7 Erro de movimentação e injeção de código não encontrado
Isso ocorre porque a instrução CdkVirtualScrollFor precisa injetar um CdkVirtualScrollViewport. Porém, o sistema de herança do injetor de injeção de elemento herda o DOM do relacionamento AST estático, e o dinâmico não é possível. ocorre o seguinte comportamento de consulta e a pesquisa falha.
Figura 8 Solução final do intervalo de pesquisa da cadeia de consulta do injetor de elementos
: 1) Mantenha a posição do código original inalterada ou 2) Você precisa incorporar o modelo inteiro para encontrá-lo.
Figura 9 Incorporar todo o módulo permite que CdkVitualScrollFo encontre CdkVirtualScrollViewport (Solução 2)
Resumo dos pontos de conhecimento : A cadeia de consulta do injetor de elemento é o elemento DOM ancestral do modelo estático.
Este caso vem deste blog "Angular: Formulário controlado por modelo aninhado".
Também encontramos o mesmo problema ao usar a validação de formulário. Conforme mostrado na Figura 10, por alguns motivos encapsulamos os endereços dos três campos em um componente para reutilização.
Figura 10: Encapsular os três campos de endereço do formulário em um subcomponente
Neste momento, descobriremos que um erro é relatado. ngModelGroup
requer um ControlContainer
dentro do host, que é o conteúdo fornecido pela diretiva ngForm.
Figura 11 ngModelGroup não consegue encontrar ControlContainer
Observando o código ngModelGroup, você pode ver que ele apenas adiciona a restrição do decorador de host.
Figura 12 ng_model_group.ts limita o escopo de injeção de ControlContainer
Aqui você pode usar viewProvider com usingExisting para adicionar o Provider de ControlContainer à visualização de host de AddressComponent.
Figura 13 Usando viewProviders para fornecer
pontos de conhecimento de provedores externos para componentes aninhados Resumo de pontos de conhecimento: o maravilhoso uso de viewProvider e usingExisting.
carregamento lento, resultando na incapacidade de arrastar e soltar uns aos outros. A plataforma de negócios interna envolve arrastar e soltar em vários módulos. Ao carregar loadChildren, cada módulo será O DragDropModule da biblioteca de componentes DevUI é empacotado separadamente e este módulo fornece um DragDropService. As instruções de arrastar e soltar são divididas em instruções arrastáveis e instruções que podem ser soltas. As duas instruções se comunicam por meio do DragDropService. Originalmente, era possível comunicar-se introduzindo o mesmo módulo e utilizando o serviço fornecido pelo módulo. Porém, após o carregamento lento, o módulo DragDropModule foi empacotado duas vezes, o que também resultou em duas instâncias isoladas. Neste momento, a instrução Draggable em um módulo de carregamento lento não pode se comunicar com a instrução Droppable em outro módulo de carregamento lento, porque o DragDropService não é a mesma instância no momento.
Figura 14 O carregamento lento de módulos faz com que os serviços não sejam a mesma instância/caso único.
É óbvio que nosso requisito aqui é que precisamos de um singleton, e o método de singleton geralmente é providerIn: 'root'
Então não devemos usar. o DragDropService da biblioteca de componentes? É bom fornecer o domínio raiz diretamente no nível do módulo. Mas se você pensar bem, há outros problemas aqui. A biblioteca de componentes em si é fornecida para uso por diversas empresas. Se algumas empresas tiverem dois grupos de arrastar e soltar correspondentes em dois locais da página, elas não desejarão ser vinculadas. Neste momento, o singleton destrói o isolamento natural baseado no módulo.
Então seria mais razoável implementar a substituição singleton pelo lado comercial. Lembre-se da cadeia de consulta de dependência que mencionamos anteriormente. O injetor de elemento é pesquisado primeiro. Portanto, a ideia de substituição é que possamos fornecer provedores em nível de elemento.
Figura 15 Use o método de extensão para obter um novo DragDropService e marcá-lo como fornecido no nível raiz
Figura 16 Você pode usar o mesmo seletor para sobrepor instruções repetidas, sobrepor uma instrução adicional na instrução Draggable e na instrução Droppable da biblioteca de componentes e substituir o token de DragDropService pelo DragDropGlobalService que forneceu um singleton na raiz
. Nas Figuras 15 e 16, injetamos elementos through. O manipulador sobrepõe instruções e substitui o token DragDropService por uma instância de nosso próprio singleton global. Neste momento, onde precisamos usar o DragDropService singleton global, precisamos apenas introduzir o módulo que declara e exporta essas duas instruções extras para permitir que a instrução Draggable Droppable da biblioteca de componentes se comunique através de módulos carregados lentamente.
Resumo dos pontos de conhecimento : Os injetores de elemento têm prioridade mais alta que os injetores de módulo.
O tema da biblioteca de componentes DevUI usa atributos personalizados CSS (variáveis css) para declarar o valor da variável css de root para obter a troca de tema. . Se quisermos exibir visualizações de diferentes temas ao mesmo tempo em uma interface, podemos declarar novamente variáveis css localmente no elemento DOM para alcançar a função de temas locais. Antes, quando eu estava criando um gerador de pontilhamento de tema, usei esse método para aplicar um tema localmente.
Figura 17 Função de tema local
, mas não é suficiente aplicar valores de variáveis CSS localmente. Existem algumas camadas pop-up suspensas que são anexadas à parte traseira do corpo por padrão, o que significa que sua camada de anexo está externa. as variáveis locais, o que levará a uma situação muito embaraçosa. A caixa suspensa do componente do tema local exibe o estilo do tema externo.
Figura 18 O componente no tema local está anexado à caixa suspensa de sobreposição externa.
O que devo fazer se o tema estiver incorreto? Devemos mover o ponto de anexação de volta para dentro do tema local.
Sabe-se que o Overlay do componente DatePickerPro da biblioteca de componentes DevUI usa o Overlay do Angular CDK. Após uma rodada de análise, substituímos por injeção da seguinte forma:
1) Primeiro, herdamos OverlayContainer e implementamos nosso próprio ElementOverlayContainer conforme mostrado. abaixo.
Figura 19 Personalize o ElementOverlayContainer e substitua a lógica _createContainer
2) Em seguida, forneça diretamente nosso novo ElementOverlayContainer no lado do componente da visualização e forneça um novo Overlay para que o novo Overlay possa usar nosso OverlayContainer. Originalmente, Overlay e OverlayContainer são fornecidos no root, aqui precisamos cobrir esses dois.
Figura 20 Substitua OverlayContainer por um ElementOverlayContainer personalizado e forneça um novo Overlay
Agora vá para visualizar o site e o DOM da camada pop-up será anexado com sucesso ao elemento de visualização do componente.
Figura 21 O contêiner Overlay do cdk está anexado ao dom especificado. A visualização parcial do tema foi bem-sucedida.
Também há um OverlayContainerRef personalizado na biblioteca de componentes DevUI para alguns componentes e bancos de gaveta de caixa modal, que também precisam ser substituídos de acordo. Finalmente, camadas pop-up e outras camadas pop-up podem ser realizadas para suportar perfeitamente temas locais.
Resumo dos pontos de conhecimento : Um bom padrão de abstração pode tornar os módulos substituíveis e obter uma programação de aspecto elegante.
com o último caso, gostaria de falar sobre uma abordagem menos formal. Para facilitar a compreensão de todos sobre a natureza do provedor, configurar o provedor significa essencialmente permitir que ele ajude você a instanciar ou mapear para uma instância existente.
Sabemos que se cdkOverlay for usado, se quisermos que a caixa pop-up siga a barra de rolagem e seja suspensa na posição correta, precisamos adicionar a instrução cdkScrollable à barra de rolagem.
Ainda é o mesmo cenário do exemplo anterior. Nossa página inteira é carregada por meio de roteamento. Para simplificar, escrevi a barra de rolagem no host do componente.
Figura 22 A barra de rolagem de estouro de conteúdo escreve overflow:auto no componente:host
Dessa forma, encontramos um problema mais difícil. O módulo é especificado pela definição do roteador, ou seja, <app-theme-picker-customize></app-theme-picker-customize>
, então como adicionar a instrução cdkScrollable? A solução é a seguinte. Parte do código está oculta aqui e apenas o código principal permanece.
Figura 23 Crie uma instância por injeção e chame manualmente o ciclo de vida
Aqui, uma instância de cdkScrollable é gerada por injeção e o ciclo de vida é chamado de forma síncrona durante o estágio do ciclo de vida do componente.
Esta solução não é um método formal, mas resolve o problema. Fica aqui como uma ideia e exploração para o gosto dos leitores.
Resumo dos pontos de conhecimento : O provedor de configuração de injeção de dependência pode criar instâncias, mas observe que as instâncias serão tratadas como classes de serviço comuns e não podem ter um ciclo de vida completo.
consulte esta postagem do blog: "Renderizando aplicativos Angular no Terminal"
Figura 24 Substitua o renderizador RendererFactory2 e outros conteúdos para permitir que o Angular seja executado no terminal.
O autor substituiu o RendererFactory2 e outros renderizadores para que o aplicativo Angular possa ser executado no terminal. Esta é a flexibilidade do design Angular. Até a plataforma pode ser substituída. Detalhes detalhados de substituição podem ser encontrados no artigo original e não serão expandidos aqui.
Resumo dos pontos de conhecimento : O poder da injeção de dependência é que o provedor pode configurá-la sozinho e finalmente implementar a lógica de substituição.
Este artigo introduziu o modo de injeção de dependência de inversão de controle e seus benefícios. Ele também apresentou como a injeção de dependência em Angular procura dependências, como configurar provedores, como usar decoradores limitados e de filtragem para obter a instância desejada e, posteriormente, por meio de N. os casos analisam como combinar os pontos de conhecimento da injeção de dependência para resolver problemas encontrados no desenvolvimento e na programação.
Ao compreender corretamente o processo de busca de dependências, podemos configurar o provedor no local exato (Casos 1 e 2), substituir outras instâncias por singletons (Casos 4 e 5) e até mesmo conectar-se através das restrições dos pacotes de componentes aninhados (. Caso 3) ou use a curva de método fornecida para implementar a instanciação de instruções (Caso 6).
O caso 5 parece ser uma substituição simples, mas ser capaz de escrever uma estrutura de código que possa ser substituída requer um entendimento profundo do modo de injeção e uma abstração melhor e razoável de cada função. Se a abstração não for apropriada, as dependências. não pode ser usado. O efeito máximo da injeção. O modo de injeção oferece mais espaço possível para os módulos serem conectáveis, plugáveis e baseados em peças, reduzindo o acoplamento e aumentando a flexibilidade, para que os módulos possam trabalhar juntos de maneira mais elegante e harmoniosa.
A poderosa função de injeção de dependência pode não apenas otimizar os caminhos de comunicação dos componentes, mas, mais importante, também pode obter inversão de controle, expondo os componentes encapsulados a mais aspectos da programação, e a implementação de algumas lógicas específicas de negócios também pode se tornar mais flexível.