Variáveis de Template e ViewChild em Angular
As variáveis de template permitem definir algumas variáveis dentro do template de um componente e, em seguida, referenciar essas variáveis dentro do mesmo template. Para definir essas variáveis, utiliza-se o símbolo de cerquilha (#
). Por exemplo, vamos definir uma variável de template chamada userName
em um componente:
import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
@Component({
selector: "my-app",
standalone: true,
imports: [FormsModule],
template: ` <p #userName>{{ name }}</p>
<p>{{ userName.textContent }}</p>
<input type="text" [(ngModel)]="name" />`,
})
export class AppComponent {
name = "Tom";
}
A definição de template tem a seguinte estrutura:
<p #userName>{{ name }}</p>
A definição da variável userName
no elemento <p>
indica que ela representará este parágrafo, ou seja, o elemento <p>
na marcação HTML. Posteriormente, podemos referenciar este parágrafo por meio da variável. Por exemplo, usando a propriedade userName.textContent
, podemos obter o conteúdo textual do parágrafo. Se o valor da variável name
, que está vinculado ao parágrafo, for alterado, o valor de userName.textContent
será atualizado automaticamente:
Interação entre Componentes Pai e Filho
O uso de variáveis de template oferece uma maneira adicional de interação entre o componente pai e o componente filho. Por exemplo, vamos definir o seguinte componente filho ChildComponent
:
import { Component } from "@angular/core";
@Component({
selector: "child-comp",
standalone: true,
template: `<p>{{ counter }}</p>`,
})
export class ChildComponent {
counter = 0;
increment() {
this.counter++;
}
decrement() {
this.counter--;
}
}
Este componente define uma variável counter
que pode ser incrementada ou decrementada por meio dos métodos increment
e decrement
.
No código do componente principal, vamos incluir o componente filho:
import { Component } from "@angular/core";
import { ChildComponent } from "./child.component";
@Component({
selector: "my-app",
standalone: true,
imports: [ChildComponent],
template: `<child-comp #counter></child-comp>
<button (click)="counter.increment()">+</button>
<button (click)="counter.decrement()">-</button>`,
})
export class AppComponent {}
Neste caso, a variável de template counter
, definida dentro da tag <child-comp>
, representará o componente ChildComponent
. Assim, podemos referenciar o ChildComponent
através dessa variável, vinculando os métodos increment
e decrement
aos eventos dos botões. Como resultado, ao clicar nos botões no componente principal, os métodos do componente filho serão chamados:
ViewChild
Entretanto, as variáveis de template têm suas limitações: elas não podem ser usadas fora do template, nem mesmo no código da classe do componente. Por exemplo, não podemos escrever o seguinte:
import { Component } from "@angular/core";
import { ChildComponent } from "./child.component";
@Component({
selector: "my-app",
standalone: true,
imports: [ChildComponent],
template: `<child-comp #counter></child-comp>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>`,
})
export class AppComponent {
increment() {
this.counter++;
}
decrement() {
this.counter--;
}
}
Aqui, a propriedade this.counter
não existe na classe AppComponent
, ela existe apenas dentro do template.
Para permitir o acesso a métodos e outras funcionalidades do componente filho, podemos utilizar o decorador ViewChild
. Esse decorador é aplicado a uma propriedade e recebe um seletor de elemento DOM que precisa ser monitorado. Se o elemento rastreado pelo seletor for alterado, ViewChild
atualizará o estado da propriedade. Vamos modificar o componente principal da seguinte forma:
import { Component, ViewChild } from "@angular/core";
import { ChildComponent } from "./child.component";
@Component({
selector: "my-app",
standalone: true,
imports: [ChildComponent],
template: `<child-comp></child-comp>
<button (click)="increment()">+</button>
<button (click)="decrement()">-</button>`,
})
export class AppComponent {
@ViewChild(ChildComponent, { static: false })
private counterComponent: ChildComponent | undefined;
increment() {
this.counterComponent?.increment();
}
decrement() {
this.counterComponent?.decrement();
}
}
O primeiro parâmetro do decorador ViewChild
indica o seletor do elemento que será monitorado. Esse seletor pode ser uma classe decorada com @Component
, como a classe do componente ChildComponent
. O segundo parâmetro, static
, indica como será feito o rastreamento das alterações.
Vinculando ViewChild a Variáveis de Template
Embora no exemplo anterior não tenhamos utilizado variáveis, é possível vincular uma propriedade a uma variável de template usando o decorador ViewChild
. Vamos modificar o código do componente principal:
import { Component, ViewChild, ElementRef } from "@angular/core";
@Component({
selector: "my-app",
standalone: true,
imports: [],
template: `<p #nameText>{{ name }}</p>
<p>{{ nameText.textContent }}</p>
<button (click)="change()">Change</button>`,
})
export class AppComponent {
@ViewChild("nameText", { static: false })
nameParagraph: ElementRef | undefined;
name = "Tom";
change() {
if (this.nameParagraph !== undefined) {
console.log(this.nameParagraph.nativeElement.textContent);
this.nameParagraph.nativeElement.textContent = "hello";
}
}
}
Aqui, no template, definimos a variável nameText
, que representa o parágrafo. No decorador ViewChild
, passamos o nome dessa variável. Assim, a propriedade nameParagraph
, à qual o decorador é aplicado, apontará para essa variável nameText
. A propriedade nameParagraph
é um objeto do tipo ElementRef | undefined
, onde ElementRef
é usado para referenciar elementos HTML.
Ao clicar no botão, o conteúdo textual dessa variável é exibido no console e alterado.
ContentChild
Além do ViewChild
, podemos usar outro decorador chamado ContentChild
para se conectar a variáveis de template, funcionando de maneira semelhante. Em que situação ele pode ser necessário? Suponha que no componente pai esteja definido o seguinte código:
import { Component } from "@angular/core";
import { ChildComponent } from "./child.component";
@Component({
selector: "my-app",
standalone: true,
imports: [ChildComponent],
template: `<child-comp>
<h3 #headerContent>Welcome {{ name }}!</h3>
</child-comp>`,
})
export class AppComponent {
name = "Tom";
}
Aqui, definimos a variável #headerContent
, que aponta para o elemento de título <h3>
.
Como os dados do componente pai são passados diretamente para o componente filho, utilizaremos o elemento ng-content
para receber esses dados no componente filho:
import { Component, ContentChild, ElementRef } from "@angular/core";
@Component({
selector: "child-comp",
standalone: true,
template: `<ng-content></ng-content> <button (click)="change()">Change</button>`,
})
export class ChildComponent {
@ContentChild("headerContent", { static: false })
header: ElementRef | undefined;
change() {
if (this.header !== undefined) {
console.log(this.header);
this.header.nativeElement.textContent = "Hello to the world!";
}
}
}
Para acessar as variáveis passadas pelo ng-content
, o componente filho usa o decorador ContentChild
. Nesse decorador, também passamos o nome da variável. A propriedade associada ao decorador será um objeto do tipo ElementRef | undefined
, permitindo manipulações com esse objeto.