Transclusão, Injeção e Procrastinação

A poucos meses atrás, Kapunahele Wong e eu estávamos fazendo um brainstorming de idéias para propostas de palestras que poderíamos fazer juntos. Foi por volta da altura em que decidi explorar a Ivy, por isso pensei que isso poderia ser uma boa opção para uma conversa. Kapunahele, entretanto, teve uma idéia diferente:

Twitter, onde as coisas divertidas estão acontecendo!

Esta foi a primeira vez que ouvi falar sobre Injector Trees, então fiquei intrigado. Kapunahele partilhou comigo algumas das suas pesquisas preliminares, e aprendi que o algoritmo de resolução de dependência do Injector Angular não era tão simples como eu pensava antes.

De facto, uma vez que os NgModules foram introduzidos no 5º candidato de lançamento do Angular 2.0.0, eu só estava prestando serviços no nível de módulo, usando a propriedade familiar dos provedores:

Or desde Angular 6, usando a propriedade providedIn do decorador Injectable:

De qualquer forma, eu sempre declarei meus serviços no nível de módulo, e nunca prestei muita atenção às outras possibilidades.

Enter Injector Trees 🌴

Kapunahele e eu decidi submeter o nosso Injector Trees falar com Angular Connect. Vários meses depois, a seguinte mensagem chegou na minha caixa de entrada:

Fomos bombeados, nossa palestra foi aceita para a conferência! 😊

Passamos as semanas seguintes explorando todos os cantos ocultos de como a injeção de dependência funciona em Angular. Neste post do blog, vou compartilhar alguns deles com vocês.

Starting Simple

Comecemos com uma simples aplicação Angular. Este aplicativo tem um serviço “Kingdom” e um único componente que injeta Kingdom e exibe o nome do Kingdom:

Decidi ir com um Dragon para o exemplo, como eu adoro usá-los em vez de ponto-e-vírgula no meu código.

Para tornar as coisas um pouco mais interessantes, vamos temperar nosso aplicativo com um componente Unicorn. Este componente imprime o nome do Reino onde ele vive:

Então temos um componente de aplicação, e um unicórnio dentro dele. Ótimo!

Agora, o que acontece quando mudamos a definição do nosso AppComponent para fornecer um valor diferente para o KingdomService?

Podemos fazer isso adicionando a seguinte linha à declaração do componente:

providers: 

Como isso afetará a nossa aplicação? Vamos tentar e ver:

Como você pode ver, o valor que definimos para KingdomService no nosso AppComponent teve precedência sobre o serviço definido no nosso AppModule (não foi definido diretamente lá, mas usando providedIn, mas o resultado é o mesmo).

Element Tree, Module Tree

A razão pela qual vemos zumbis é a forma como a resolução de dependência funciona em Angular. Ele primeiro procura na árvore de componentes, e só depois na árvore de módulos. Vamos considerar UnicornComponent. Ele injeta uma instância de KingdomService dentro do seu construtor:

constructor(public kingdom: KingdomService) {}

Quando Angular cria este componente, ele primeiro procura se há algum provedor definido no mesmo elemento que o componente. Esses provedores poderiam ter sido registrados no próprio componente, ou usando uma diretiva. Neste caso, não fornecemos nenhum valor para KingdomService dentro do UnicornComponent, nem temos nenhuma diretiva sobre o elemento <app-unicorn>.

A busca então continua até a árvore de elementos, indo até AppComponent. Aqui Angular descobre que nós temos um valor para KingdomService, então ele injeta esse valor e pára de procurar lá. Então neste caso, Angular nem sequer olhou para a árvore de módulos.

Angular é Lazy!

Apenas como nós programadores, Angular também é um procrastinador. Ele não cria instâncias de serviços a menos que realmente precise. Você pode confirmar isso adicionando uma declaração console.log ao construtor de KingdomService (você também pode adicionar uma declaração alert('I love marquees') se você se sentir nostálgico hoje).

Você verá que a declaração console.log nunca é executada – já que Angular não cria o serviço. Se você remover a declaração providers: da declaração AppComponent (ou movê-la para UnicornComponent, então ela só se aplica ao unicórnio e seus elementos filhos), você deve começar a ver a mensagem de log no seu console.

Agora Angular não tem escolha – ele não encontra o KingdomService quando procura na Árvore de Elementos. Então, primeiro ele vai até a Árvore Injetora de Módulos, depois vê que nós fornecemos o serviço lá, e finalmente cria uma instância dele. Assim, o código dentro do construtor roda, e você poderá ver a impressão de debug que você colocou lá.

Invasão Diretiva!

I mencionei que as diretivas também podem fornecer valores para injeção de dependência. Vamos experimentar com isso. Vamos definir uma nova diretiva appInvader, que vai mudar o valor do reino para 👾.
Porquê? Porque eles foram tão adoráveis no VR + Angular talk Alex Castillo e eu demos em ng-conf.

Então, vamos adicionar outro elemento <app-unicorn>, e aplicar-lhe a nova diretiva appInvader:

Como esperado, o novo unicórnio vive no reino de 👾. Isto é porque a diretiva forneceu um valor para KingdomService. E como explicado acima, Angular inicia a busca a partir do elemento atual, olhando para o Componente e todas as Diretivas, e somente se ele não conseguir encontrar o valor solicitado lá, ele continua subindo na árvore de elementos (e depois nos módulos).

Vejamos algo um pouco mais complicado:

Adicionando uma Floresta de Projeção de Conteúdo para o App!

Adicionaremos um novo componente florestal ao nosso aplicativo, e colocaremos alguns dos unicórnios dentro desta floresta, porque eram unicórnios ao vivo (um cara qualquer disse isso na quora, então deve ser verdade).

O componente Floresta é simplesmente um recipiente, que usa a Projecção de Conteúdo para mostrar os seus filhos em cima de um fundo verde “foresty”:

Então vemos os elementos do componente AppForest em cima de um fundo gramado, e então, todo o conteúdo projectado em cima de um fundo verde brilhante. E como fornecemos um valor para KingdomService dentro do nosso componente app, tudo dentro o herda (exceto o unicórnio com a diretiva appInvader).

Mas o que acontece se fornecemos um novo valor para KingdomService dentro do componente ForestComponent? O conteúdo projectado (que foi definido no template para AppComponent) também irá obter este novo valor para o reino? Ou será que ainda estará no reino 🧟? Você pode adivinhar?

Tratavam-no de Transclusão. Agora é chamado de “Projeção de Conteúdo”. Foto por ng-conf

The Wizard of The Forest

Adicionaremos uma única linha ao nosso exemplo anterior, fornecendo um reino 🧙 para o reino ForestComponent:

providers: 

E este é o resultado:

Agora isto é interessante – vemos uma mistura de reinos dentro da floresta! O elemento floresta em si vive no reino 🧙, mas o conteúdo projetado parece ter personalidade dividida: os unicórnios também pertencem ao reino 🧙, mas o texto acima deles mostra 🧟 reino?

Definimos tanto estes unicórnios quanto o texto no mesmo lugar, linhas 12-15 do modelo app.component.html. No entanto, o que importa é o local onde o componente em si foi criado no DOM. O texto na linha 12 é na verdade o mesmo que o fazemos na linha 4 – lemos a propriedade kingdom da mesma instância AppComponent. O elemento DOM para este componente é na verdade um antepassado do elemento <app-forest> DOM. Então quando esta AppComponent instância foi criada, ela foi injetada com o elemento 🧟 kingdom.

Os dois elementos <app-unicorn>, estão, entretanto, dentro dos elementos <app-forest> DOM, então quando suas instâncias de UnicornComponents são criadas, angularmente sobe o DOM e vê o valor que fornecemos para os KingdomService dentro dos ForestComponent, e assim estes unicórnios são injetados com o reino 🧙.

Você pode conseguir um comportamento diferente se mudar providers para viewProviders ao definir o ForestComponent. Você pode aprender mais sobre View Providers aqui, e também verificar este exemplo de código, onde eu mudei ForestComponent para usar View Providers, então agora até os unicórnios dentro da floresta são injetados com o reino 🧟. Obrigado Lars Gyrup Brink Nielsen por me apontar isso!

Transportei o Chrome T-Rex neste post de blog

Keep Exploring!

>

Espero que você tenha acabado de aprender algo novo sobre o sistema de Injeção de Dependência Angular. Esta é apenas uma das coisas que Kapunahele e eu exploramos quando preparamos nossa palestra para o AngularConnect. Há muito mais – você está convidado a explorar mais, e nós também vamos compartilhar os slides e o link para o vídeo após a palestra. Ah, e haverá também algum código ao vivo. Vai ser muito divertido!

Se você quiser saber mais sobre as entradas e saídas do Angular Injector, aqui estão alguns artigos que eu achei muito úteis:

  • O que você sempre quis saber sobre Injeção de Dependência Angular
  • Um caso curioso do @Host decorador e Injetores de Elementos em Angular
  • Injeção de Dependência Hierárquica (Angular Docs)

E se você estiver assistindo AngularConnect, você está convidado a vir e dizer oi!

Deixe uma resposta

O seu endereço de email não será publicado.