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.
Top comments (0)