Criando uma *diretiva estrutural de repetição
Felipe Carvalho
Posted on July 9, 2019
Diretivas estruturais são "aquelas com asterisco", responsáveis por manipular o DOM, adicionando, removendo ou alterando elementos, tal como o *ngIf ou o *ngFor.
Há algum tempo tive a necessidade criar um componente onde eu precisaria repetir um mesmo elemento um certo número de vezes. Com as diretivas nativas, a única maneira de fazê-lo seria criando um array vazio e percorre-lo com *ngFor, mas essa solução não me agradou e então surgiu a oportunidade de criar uma diretiva estrutural para receber apenas o número de vezes que ela deva repetir o elemento.
Mas o que é o asterisco?
Na verdade, o asterisco antes da diretiva estrutural pode ser entendido como
uma abstração da implementação de um ng-template. Então, quando usamos uma diretiva estrutural:
<div *ngIf="hero" class="name">{{hero.name}}</div>
O Angular traduz para:
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
</ng-template>
Resumo
A diretiva terá um atributo Input com o mesmo nome de seu seletor definido como set para que, ao setá-lo com o número de repetições, executemos a lógica para adicionar elementos ao TemplateRef recebido pelo construtor, utilizando o método createEmbeddedView() do ViewContainerRef.
Construção
Como vimos anteriormente, nossa diretiva estrutural é traduzida para um template e podemos receber esse template pelo construtor da nossa diretiva repetir, assim como o container onde nosso elemento está:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[repetir]'
})
export class RepetirDirective {
constructor(private templateRef: TemplateRef<any>,
private containerRef: ViewContainerRef) { }
}
Defini então um Input com o mesmo nome da diretiva, o que torna possível usá-la passando o valor (*repetir="2", por exemplo).
Defini então o set deste Input para executar uma lógica assim que o valor for recebido pela diretiva. Esta lógica consiste em criar cópias do meu template (elemento onde apliquei a diretiva) dentro do seu próprio container.
Como segundo argumento do createEmbeddedView() defini uma propriedade chamada posicao que poderá ser acessada pelo template do componente que usar a diretiva.
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[repetir]'
})
export class RepetirDirective {
@Input() set repetir(vezes: number) {
for (let i = 0; i < vezes; i++) {
this.containerRef.createEmbeddedView(this.templateRef, { posicao: i + 1 });
}
}
constructor(private templateRef: TemplateRef<any>,
private containerRef: ViewContainerRef) { }
}
Não podemos nos esquecer de colocar a diretiva nas declarations do nosso módulo:
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, RepetirDirective, NomeComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Agora, basta usar em algum template de algum componente. Repare que defini posicao as pos e exibi qual a posição daquela cópia:
<p *repetir="4; posicao as pos">
Parágrafo {{pos}}
</p>
E funciona para componentes personalizados também:
<app-nome *repetir="2; posicao as pos" (nomeEmitido)="nomeRecebido($event, pos)">
</app-nome>
Nada melhor que ver funcionando, certo?
O exemplo construído se encontra logo abaixo, mas se você não estiver conseguindo visualizar, ou caso prefira, pode clicar aqui para visualizar em outra aba.
Referências
Posted on July 9, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.