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

Top comments (0)

We want your help! Become a Tag Moderator.
Check out this survey and help us moderate our community by becoming a tag moderator here at DEV.