Entendendo o Decorator @ViewChild— 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 AppComponent
que mistura ambos elementos HTML simples, como componentes customizados.
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:
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 colorPicker
de 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 doAppComponent
, utilizando as template variables
que o Angular nos fornece, como as que estão sendo utilizadas nesse exemplo #primaryColorSample
e #primaryInput
.
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?
- O nome de qualquer classe que possua os decorators @Component e @Directive.
- O nome de uma template variable entre aspas sem o #.
- Qualquer serviço definido na árvore do componente filho do componente atual.
- Qualquer provider definido por um injection token
- Um template ref
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:
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
.
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.
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>
:
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
:
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
.
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:
Como podemos ver, estamos passando a string 'title'
sem o #
para o decorador @ViewChild
que corresponde ao nome da template variable aplicada à tag <h2>
.
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 @ViewChild
para 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
:
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>
:
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
Como podemos ver, estamos passando um segundo argumento contendo um objeto de configuração com a propriedade read
definida para ElementRef
.
Esta propriedade read
irá especificar exatamente o que estamos tentando injetar, caso haja vários injetáveis possíveis disponíveis.
Nesse caso, estamos usando a propriedade read
para 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:
Vamos agora ver outro caso de uso comum em que a propriedade read do @ViewChild
pode 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 read
cada 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:
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 colorPicker
e 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:
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 ElementRef
nã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
:
Da mesma forma que utilizamos o segundo parâmetro do @ViewChild
para recuperar o ElementRef
também podemos utilizá-lo para recuperar uma diretiva especifica.
Então, no nosso app.component.ts
, teremos a diretiva colorPicker
injetada da seguinte maneira:
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:
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>
Agora no ngOnInit
do nosso <color-sample>
. Irei alterar o prefix
do nosso serviço para ver se no AppComponent
iremos recuperar essa mesma instância com o prefix
alterado.
Agora iremos ver se conseguimos ter acesso a instância desse serviço utilizando o nosso decorator @ViewChild
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>
.
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 InjectionToken
com o @ViewChild
.
Para isso criarei um token simples que apenas possui o valor da url
de uma api com o valor padrão de 'http://production.com/api'
.
E no nosso componente <color-sample>
irei configurá-lo com outro valor para ter certeza que de que estamos recuperando a instância correta.
Agora iremos ver se conseguimos ter acesso a instância desse token utilizando o nosso decorator @ViewChild
.
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>
.
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
.
No AppComponent
iremos implementar os métodos show
e hide
utilizando o ViewContainerRef
que será injetado no construtor do AppComponent
.
Agora iremos ver se conseguimos ter acesso a instância do TemplateRef utilizando o @ViewChild
.
E com este último exemplo, agora cobrimos todos os recursos do decorator @ViewChild
e 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 @ViewChild
nos 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 @ViewChild
e 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.