Criando uma *diretiva estrutural de repetição
Tags: angular
Published: 2019-07-09 14:57:17

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:

```html
<div *ngIf="hero" class="name">{{hero.name}}</div>
```

O Angular traduz para:
```html
<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á:
```typescript
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.
```typescript
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:
```typescript
@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:
```html
<p *repetir="4; posicao as pos">
	Parágrafo {{pos}}
</p>
```

E funciona para componentes personalizados também:
```html
  <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](https://stackblitz.com/edit/angular-repetir-directive) para visualizar em outra aba.

{% stackblitz angular-repetir-directive %}

----

###Referências
* [Structural Directives](https://angular.io/guide/structural-directives)