DEV Community

Cover image for Criando uma *diretiva estrutural de repetição
Felipe Carvalho
Felipe Carvalho

Posted on

Criando uma *diretiva estrutural de repetição

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>
Enter fullscreen mode Exit fullscreen mode

O Angular traduz para:

<ng-template [ngIf]="hero">
  <div class="name">{{hero.name}}</div>
</ng-template>
Enter fullscreen mode Exit fullscreen mode

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) { }
}
Enter fullscreen mode Exit fullscreen mode

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) { }
}
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

E funciona para componentes personalizados também:

  <app-nome *repetir="2; posicao as pos" (nomeEmitido)="nomeRecebido($event, pos)">    
  </app-nome>
Enter fullscreen mode Exit fullscreen mode

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

Oldest comments (0)