Entendendo o Decorator @ViewChild— Angular

Entendendo o Decorator @ViewChild— Angular

Aprenda a utilizar um dos principais decorators do Angular.

O decorator @ViewChild é um dos principais decorators de propriedade que você provavelmente encontrará ao aprender Angular.

Este decorador tem muitos recursos, alguns deles podem não ser muito conhecidos, mas são extremamente úteis para determinadas situações.

Neste artigo, irei tentar cobrir todos os recursos que temos disponíveis neste decorator, dando exemplos práticos para cada caso de uso ao longo do artigo.

Com o que iremos trabalhar?

Como sabemos, no Angular nós definimos um template HTML para o nosso componente combinando elementos HTML simples com componentes Angular.

No exemplo abaixo nós temos o template HTML do AppComponentque mistura ambos elementos HTML simples, como componentes customizados.

Template inicial do nosso app.component.htmlTemplate inicial do nosso app.component.html

Como podemos ver, este template inclui vários tipos de elementos diferentes, como:

  • Elementos HTML simples como <h2> e <div>;

  • Componentes Angular customizados como o <color-sample>;

  • Componentes de bibliotecas de terceiros como a diretiva colorPicker;

  • Componentes do Angular Material como <mat-label> e <mat-form-field>;

E esse é o resultado atual do template do nosso AppComponent renderizado:

Template renderizado do AppComponentTemplate renderizado do AppComponent

Vamos basear todos os nossos exemplos neste template inicial. O componente <color-sample> é a pequena paleta verde quadrada, e próximo a ele, temos um input comum do HTML que está vinculado a diretiva colorPickerde nossa biblioteca externa, que é responsável por exibir o pop-up.

Quando utilizar o decorator @ViewChild?

Muitas vezes nós conseguimos manipular esses vários componentes e também os elementos do DOM diretamente no próprio template, sem precisar da mediação do AppComponent, utilizando as template variables que o Angular nos fornece, como as que estão sendo utilizadas nesse exemplo #primaryColorSample e #primaryInput.

Template Reference Variables utilizadas em nosso templateTemplate Reference Variables utilizadas em nosso template

Mas nem sempre é assim! Às vezes, dentro do nosso arquivo .ts podemos precisar de referências aos vários elementos que ele contém dentro de seu próprio template, a fim de mediar sua interação.

Se for esse o caso, podemos obter referências a esses elementos do template e injetá-los no AppComponent realizando consultas em seu próprio template, é para isso que serve o @ViewChild

O que ele faz é basicamente executar uma query em seu próprio template, retornando o primeiro elemento que atender ao seletor que o foi passado, e injetando esse elemento em uma propriedade da classe do seu componente, semelhante ao o bom e velho document.querySelector do javascript puro.

Quais são os seletores suportados pelo decorator @ViewChild?

Usando o @ViewChild para Recuperar a Referência de um componente.

Digamos que no nosso arquivo .ts do AppComponent quiséssemos a referência ao componente <color-sample> que ele usa dentro de seu template, para chamar um método diretamente nele.

Nesse caso, podemos injetar uma referência à instância do componente <color-sample> passando o nome da classe do nosso componente como argumento (nesse caso o nome da classe do componente <color-sample>) para o nosso decorator @ViewChild:

Recuperando a referência de um componente

Usando decorator @ViewChild a variável colorSampleComponent será preenchida pelo Angular com uma instância do ColorSampleComponent.

Esta instância do componente ColorSampleComponent injetada é a mesma que está vinculada ao componente <color-sample> renderizado no template.

Quando as variáveis ​​injetadas via @ViewChild estão disponíveis?

Você provavelmente, assim como eu, já deve ter passado pela situação de utilizar o @ViewChild e quando foi tentar usar a propriedade o seu valor está undefined.

Exibindo no console o valor da nossa propriedade usando o OnInitExibindo no console o valor da nossa propriedade usando o OnInit

Resultado do nosso console.logResultado do nosso console.log

Como podem ver a nossa variável não foi preenchida, e tem uma explicação para isso.

O valor injetado pelo @ViewChild não está imediatamente disponível no momento da construção do componente.

O Angular irá preencher essa propriedade automaticamente, porém apenas após o LifeCycle Hook AfterViewInit ser executado.

Aqui está um exemplo de como usar este lifecycle hook:

Se agora executarmos a nossa aplicação, essa será a saída que teremos no console:

Como podemos ver, o Angular preencheu automaticamente a nossa variável colorSampleComponent com a referência do nosso componente.

Então só é possível ter a variável preenchida durante o ngAfterViewInit?

Não, para que possamos preenchê-la durante o ngOnInit é necessário passar para o @ViewChild mais uma argumento, um objeto com o seguinte formato {static : true } (por padrão essa opção vem como false) isso fará com que o componente injetado pelo Angular esteja disponível antes mesmo do ngAfterViewInit ser chamado e esteja disponível para ser usado no ngOnInit.

Passando um segundo argumento para o @ViewChildPassando um segundo argumento para o @ViewChild

Resultado no console.logResultado no console.log

Qual é o escopo alcançado pelas queries do @ViewChild ?

Com o @ViewChild podemos injetar qualquer componente, diretiva, elemento DOM ou providers definido dentro da árvore do componente, providers definido através de um Token e também um TemplateRef.

Mas até onde podemos consultar componentes na árvore de componentes? Vamos tentar usar @ViewChild para consultar um componente que está mais profundo na árvore de componentes, ou seja um componente dentro de outro componente.

Vamos dar uma olhada no componente <color-sample>:

Template html do componente <color-sample>Template html do componente <color-sample>

Como podemos ver, o componente <color-sample>internamente usa outro componente que é o <mat-icon> (componente do Angular Material), para exibir o pequeno ícone da paleta.

Vamos agora ver se podemos recuperar esse componente <mat-icon> e injetá-lo diretamente no AppComponent:

Definindo a classe do MatIcon como seletor para o @ViewChildDefinindo a classe do MatIcon como seletor para o @ViewChild

Se tentarmos executar isso, obteremos no console:

Como podemos ver no console, o decorador @ViewChild não pode ver além dos limites do componente <color-sample>

Escopo de visibilidade das queries do @ViewChild

Isso significa que as consultas feitas usando o decorator @ViewChild só podem ver os elementos dentro do template do próprio componente. É importante perceber que o@ViewChild não pode ser usado para injetar:

  • Nada que esteja dentro do template dos componentes filhos.

  • Nada que esteja no template do componente pai também.

Resumindo : o decorator @ViewChild é um mecanismo de consulta no template local do próprio componente.

Com isso, cobrimos o caso de uso mais comum do @ViewChild, mas ainda há muito mais, vamos ver mais alguns casos de uso.

Usando o @ViewChild para Recuperar a Referência de um elemento DOM.

Em vez de injetar um componente filho direto, podemos querer interagir diretamente com um elemento HTML simples do nosso template, como, por exemplo, a tag de título <h2> dentro de AppComponent.

Tag h2 sendo referenciada por uma template variableTag h2 sendo referenciada por uma template variable

Para isso, atribuímos uma template variable #title a tag <h2>. Agora podemos ter o elemento <h2> injetado diretamente em uma variável em nosso app.component.ts da seguinte maneira:

definindo a template variable title como seletor do @ViewChilddefinindo a template variable title como seletor do @ViewChild

Como podemos ver, estamos passando a string 'title' sem o # para o decorador @ViewChild que corresponde ao nome da template variable aplicada à tag <h2>.

resultado do console.logresultado do console.log

Como já deve ser de conhecimento da maioria o Angular envolve os elementos DOM nativos com a classe ElementRef e para podemos recuperá-lo usamos a propriedade nativeElement .

Usando a propriedade nativeElement, podemos agora aplicar qualquer operação DOM nativa à tag de título <h2>, como por exemplo addEventListener(), click() etc...

Essa é a forma que podemos usar @ViewChildpara interagir com elementos HTML simples no nosso template, mas isso nos leva à questão:

O que fazer se precisarmos do elemento DOM que está associado a um componente Angular?

Afinal, a tag HTML <color-sample> ainda é um elemento DOM, embora tenha uma instância de colorSampleComponent anexada a ela.

Usando @ViewChild para injetar a referência do elemento DOM de um componente

Vamos dar um exemplo para este novo caso de uso. o componente <color-sample> dentro do AppComponent:

componente <color-sample>componente <color-sample>

O componente <color-sample> tem a template variable #primaryColorSample atribuída a ele.

Vamos ver o que acontece se tentarmos usar esta template variable para injetar o elemento DOM <color-sample> como fizemos com a tag <h2>:

Definindo uma template variable de um componente como seletor para o @ViewChildDefinindo uma template variable de um componente como seletor para o @ViewChild

Se executarmos a aplicação , podemos nos surpreender ao descobrir que, desta vez, não estamos recuperando o elemento DOM nativo:

Pois o que nos foi retornado é a referência para a instância da classe do componente ColorSampleComponent, e não é isso que queremos.

Comportamento padrão da injeção do @ViewChild para template variables

O comportamento padrão do @ViewChild ao realizar a consulta utilizando uma template variable como seletor é:

  • Ao injetar a referência da classe de componente, obtemos a instância do componente

  • Ao injetar uma referência a um elemento HTML simples, obtemos o elemento DOM encapsulado correspondente (ElementRef)

A propriedade read do segundo parâmetro do decorator @ViewChild

Voltando ao caso do nosso componente <color-sample>, gostaríamos de obter o elemento DOM que está vinculado ao componente. Isso ainda é possível, usando a propriedade read do objeto do segundo parâmetro do @ViewChild

Passando um objeto com a propriedade read como argumento para o @ViewChildPassando um objeto com a propriedade read como argumento para o @ViewChild

Como podemos ver, estamos passando um segundo argumento contendo um objeto de configuração com a propriedade readdefinida para ElementRef.

Esta propriedade readirá especificar exatamente o que estamos tentando injetar, caso haja vários injetáveis ​​possíveis disponíveis.

Nesse caso, estamos usando a propriedade readpara especificar que queremos obter o elemento DOM (encapsulado por ElementRef) que se remete ao nosso componente, e não a instância do componente em si.

Se agora executarmos nossa aplicação, isso é realmente o que obteremos no console:

resultado do console.logresultado do console.log

Vamos agora ver outro caso de uso comum em que a propriedade read do @ViewChildpode ser útil.

Usando o decorator @ViewChild para injetar a referência de uma diretiva

Com o crescimento da nossa aplicação, o uso de diretivas e/ou bibliotecas será bastante comum, provavelmente precisaremos da propriedade readcada vez mais.

Por exemplo, voltando ao nosso exemplo do seletor de cores, vamos agora tentar fazer algo simples, como abrir o color picker quando o componente <color-sample> for clicado:

Definindo um event-binding para o componente color-sampleDefinindo um event-binding para o componente color-sample

Neste exemplo, estamos tentando integrar os nossos componentes usando apenas template variables.

Estamos detectando o clique no componente <color-sample> e, quando isso ocorrer, estamos tentando usar a template variable #primaryInput para acessar a diretiva colorPickere abrir a caixa de diálogo.

Usar template variables é uma boa abordagem que funcionará em muitos casos, mas não aqui!

Nesse caso, a template variable #primaryInput aponta para o elemento DOM <input>, e não para a diretiva colorPicker aplicada a esse mesmo elemento.

Se executarmos nossa aplicação, obteremos o seguinte erro:

Erro occorido durante o evento click do componente color-sampleErro occorido durante o evento click do componente color-sample

Este erro ocorre porque, por padrão, a template variable #primaryInput aponta para o ElementRef que encapsula o elemento DOM e não para a diretiva colorPicker, e no ElementRefnão existe nenhum método chamado openDialog.

Como podemos ver, essa não é a maneira de obter a referência de uma diretiva, especialmente em uma situação em que várias diretivas são aplicadas ao mesmo elemento HTML simples ou componente Angular.

Para resolver isso, vamos primeiro reescrever nosso template para que a manipulação do evento click agora seja delegada para o app.component.ts:

Vinculando o evento click a um método do app.component.tsVinculando o evento click a um método do app.component.ts

Da mesma forma que utilizamos o segundo parâmetro do @ViewChild para recuperar o ElementReftambém podemos utilizá-lo para recuperar uma diretiva especifica.

Então, no nosso app.component.ts, teremos a diretiva colorPickerinjetada da seguinte maneira:

Passando um objeto com a propriedade read como argumento para o @ViewChildPassando um objeto com a propriedade read como argumento para o @ViewChild

E com isso, agora temos uma referência correta da diretiva colorPicker!

Se agora clicarmos no ícone da pequena paleta, o seletor de cores será aberto conforme o esperado:

resultado do click no componente <color-sample>resultado do click no componente <color-sample>

Usando o decorator @ViewChild para injetar a referência de um serviço

Agora iremos tentar recuperar a referência para um serviço que foi definido na propriedade providers do nosso componente <color-sample>.

Para isso criarei um serviço simples que apenas possui uma propriedade prefix com o valor padrão de 'service'e um método para fazer log desse prefix, e irei configurado em nosso componente <color-sample>

Criação do ColorSampleServiceCriação do ColorSampleService

Agora no ngOnInitdo nosso <color-sample>. Irei alterar o prefixdo nosso serviço para ver se no AppComponent iremos recuperar essa mesma instância com o prefixalterado.

Alteração da propriedade prefix dentro do ColorSampleComponentAlteração da propriedade prefix dentro do ColorSampleComponent

Agora iremos ver se conseguimos ter acesso a instância desse serviço utilizando o nosso decorator @ViewChild

Passando o ColorSampleService como seletor para o @ViewChild e Invocando o método logPassando o ColorSampleService como seletor para o @ViewChild e Invocando o método log

Se executarmos nossa aplicação e verificarmos o console, podemos ver que conseguimos ter acesso a mesma instância que teve o prefixo alterado no nosso componente <color-sample>.

resultado do console.logresultado do console.log

Usando o decorator @ViewChild para injetar a referência de um InjectionToken

Da mesma forma que fizemos para recuperar a referência de um serviço, podemos também recuperar um InjectionTokencom o @ViewChild.

Para isso criarei um token simples que apenas possui o valor da urlde uma api com o valor padrão de 'http://production.com/api'.

Criação do InjectionTokenCriação do InjectionToken

E no nosso componente <color-sample>irei configurá-lo com outro valor para ter certeza que de que estamos recuperando a instância correta.

Definindo outro valor para o InjectionToken no componente <color-sample>Definindo outro valor para o InjectionToken no componente <color-sample>

Agora iremos ver se conseguimos ter acesso a instância desse token utilizando o nosso decorator @ViewChild.

Passando o Token como seletor para o @ViewChild e fazendo console do valorPassando o Token como seletor para o @ViewChild e fazendo console do valor

Se executarmos nossa aplicação e verificarmos o console, podemos ver que conseguimos ter acesso a mesma instância que teve o valor do token alterado no nosso componente <color-sample>.

resultado do console.logresultado do console.log

Usando o decorator @ViewChild para injetar a referência de um TemplateRef

Para este exemplo , iremos criar um div contendo um ng-template e dois botões, um para exibir e outro para ocultar, e iremos manipulá-lo pelo nosso app.component.ts.

Criação do ng-template que será manipulado no app.component.tsCriação do ng-template que será manipulado no app.component.ts

No AppComponent iremos implementar os métodos show e hide utilizando o ViewContainerRef que será injetado no construtor do AppComponent.

Injetando o ViewContainerRef para manipular o ng-templateInjetando o ViewContainerRef para manipular o ng-template

Agora iremos ver se conseguimos ter acesso a instância do TemplateRef utilizando o @ViewChild.

Definindo o TemplateRef como seletor para o @ViewChildDefinindo o TemplateRef como seletor para o @ViewChild

Exibindo ou ocultando o ng-template recuperado com o @ViewChildExibindo ou ocultando o ng-template recuperado com o @ViewChild

E com este último exemplo, agora cobrimos todos os recursos do decorator @ViewChilde alguns de seus casos de uso pretendidos. Vamos agora resumir o que aprendemos.

Nem sempre iremos precisar do decorator @ViewChild

Também vamos lembrar que há muitos casos de uso em que esse decorador pode não ser realmente necessário, porque muitas interações simples podem ser codificadas diretamente no template do componente usando apenas as template variables, sem a necessidade de usar a classe do componente.

Conclusão

O decorador @ViewChildnos permite injetar em uma propriedade de um componente referências a elementos usados ​​dentro de seu próprio template, é para isso que devemos usá-lo.

Podemos facilmente injetar componentes, diretivas,elementos DOM simples, providers e templates ref. Podemos até sobrescrever o comportamento padrão do @ViewChilde especificar exatamente o que precisamos injetar, caso várias opções estejam disponíveis.

O @ViewChild é um mecanismo de consulta no template local de um componente, que não pode ver o interior de seus componentes filhos.

Ao injetar referências diretamente na propriedade de um componente, podemos facilmente escrever qualquer lógica de manipulação que envolva vários elementos do template.

Espero que este artigo o ajude a entender melhor o funcionamento do decorator @ViewChild e que você tenha gostado!

Se você tiver alguma dúvida ou comentário, ou caso tenha faltado algo que não está neste artigo por favor me avise nos comentários abaixo para que possamos ajudar uns aos outros.

Referências:

Espero que isso seja útil para você e até a próxima.

Todos os códigos utilizados para esses exemplos podem ser encontrados neste repositório do GitHub.