Diretivas estruturais de Angular e sua micro sintaxe
Diretivas estruturais de Angular e sua micro sintaxe
Você já se perguntou o que é aquele prefixo de asterisco antes de ngIf
e ngFor
? Isso é chamado diretiva estrutural.
Neste artigo eu vou te mostrar o que é, pra que serve e como funciona.
Também farei uma parte 2, mostrando como criar suas próprias diretivas estruturais.
Templates são a estrutura
Vamos começar definindo o que é.
Uma diretiva estrutural é uma diretiva com uma estrutura. A estrutura é um ng-template.
Quando você escreve <div><p>Text</p></div>
, você está dizendo ao Angular para: “_Declarar a estrutura de uma div tag, com uma paragraph tag, com a string 'Text', e renderizá-la_”.
Mas quando você envolve o código em um <ng-template><div><p>Text</p></div></ng-template>
, você está dizendo ao Angular para “_declarar a estrutura de uma tag 'div', com uma tag 'p', com a string 'Text'_”. Observe que agora não estamos dizendo ao Angular para renderizá-la.
Agora, coloque uma diretiva no <ng-template>
e você terá uma diretiva estrutural:
<ng-template [ngIf]=“condition”><div><p>Text</p></div></ng-template>
Synthetic sugar
É assim que o ngIf funciona. O Angular analisa o <ng-template>
, gerando uma TemplateRef, que então é injetada na diretiva NgIf. Se a condição passada em ngIf for verdadeira, o template é renderizado.
Mas seria muito chato criar um ng-template sempre que quisermos usar NgIf ou qualquer outra diretiva que requeira um ng-template. Então a equipe do Angular criou um açúcar sintático. É como um atalho.
Quando você prefixa sua diretiva com um asterisco, Angular envolve tudo em um &ng-template e aplica a diretiva ao ng-template. Portanto, <div *ngIf=“condition”>Abc</div>
, torna-se um <ng-template [ngIf]=“condition”><div>Abc</div></ng-template>
É apenas açúcar sintático. Você pode escrever o seu aplicativo inteiro sem usar o asterisco se você quisesse.
Apenas um permitido
Sabendo como funciona, agora você vai entender porque só podemos usar uma diretiva estrutural por elemento. Se você usasse *ngIf
e *ngFor
no mesmo elemento como o Angular interpretaria isso? ngIf primeiro e depois ngFor? O contrário? Ambos no mesmo template?
Micro sintaxe
Falando sobre ngFor, ele parece muito mais complicado do que ngIf, certo? Eu já vi algumas expressões bem complexas com ngFor, como passar uma função trackBy, passar um observable de array por um pipe, pegar o índice e verificar se é o último elemento.
<div *ngFor="let item of list$ | async; trackBy: trackByFn; let itemIndex = index; let isLast = last">{{ item }}</div>
TypeScript<div *ngFor="let item of list$ | async; trackBy: trackByFn; let itemIndex = index; let isLast = last">{{ item }}</div>
Inicialmente, pensei que fosse um jargão específico do ngFor, mas não é. É uma sintaxe totalmente documentada que funciona para quaisquer diretivas estruturais, até mesmo diretivas que você mesmo crie. É chamada "micro sintaxe da diretiva estrutural". (meio óbvio)
A micro sintaxe da diretiva estrutural divide as expressões por ponto e vírgula (;). Em nosso exemplo com NgFor, teríamos 4 expressões:
- let item of list$ | async
- trackBy: trackByFn
- let itemIndex = index
- let isLast = last
Declarations
Expressões que começam com "let" são declarações de variáveis. Você declara o nome da variável logo após o "let" e usa o sinal de igual (=) para apontar para o nome da variável no contexto da diretiva exportada.
Eu sei, foi muita informação.
O que quero dizer é que, quando renderizamos um ng-template
, podemos opcionalmente passar um objeto de contexto. E as propriedades deste objeto de contexto são passadas para o template. O objeto de contexto pode ter várias variáveis explícitas e uma única variável implícita.
<!-- Rendering an <ng-template> with a context object -->
<ng-container *ngTemplateOutlet="templateExample; context: { $implicit: 'test', index: 1 }"></ng-container>
<!-- Using the context properties in the <ng-template> -->
<ng-template #templateExample let-itemIndex="index" let-item>
<p>#{{ itemIndex }} - {{ item }}</p>
</ng-template>
TypeScript<!-- Rendering an <ng-template> with a context object -->
<ng-container *ngTemplateOutlet="templateExample; context: { $implicit: 'test', index: 1 }"></ng-container>
<!-- Using the context properties in the <ng-template> -->
<ng-template #templateExample let-itemIndex="index" let-item>
<p>#{{ itemIndex }} - {{ item }}</p>
</ng-template>
É como uma função em JavaScript, nós temos os parâmetros, que nós mesmo declaramos e que, portanto, são muito explícitos, e temos também this
, que é uma variável implícita que existe mesmo que não a tenhamos declarado.
function example(itemIndex, isLast) {
// Explicit
console.log(itemIndex, isLast);
// Implicit
console.log(this);
}
TypeScriptfunction example(itemIndex, isLast) {
// Explicit
console.log(itemIndex, isLast);
// Implicit
console.log(this);
}
Em uma função, você pode ter quantos parâmetros quiser, mas apenas um this
. Da mesma forma, em um ng-template
, você pode ter quantas variáveis explícitas quiser, mas apenas uma variável implícita.
A variável implícita é o que você obtém quando não aponta para nenhuma variável exportada. let item
, por exemplo, obtém a variável implícita. Mas let isLast = last
obtém a variável explícita last
e let itemIndex = index
obtém a variável explícita index
.
Após interpretar os açúcares sintáticos, é isso que obtemos:
<ng-template let-item let-itemIndex="index" let-isLast="last">
<p>#{{ itemIndex }} - {{ item }}</p>
<p *ngIf="isLast">The end</p>
</ng-template>
TypeScript<ng-template let-item let-itemIndex="index" let-isLast="last">
<p>#{{ itemIndex }} - {{ item }}</p>
<p *ngIf="isLast">The end</p>
</ng-template>
Expressões-chave
Expressões com dois argumentos e opcionalmente dois-pontos (:) entre eles são expressões-chave. A expressão (à direita) é atribuída à chave (à esquerda) com um prefixo antes.
Vejamos alguns exemplos.
Em \*ngIf="condition; else otherTemplate
, na expressão else otherTemplate
:
- ngIf é o prefixo
- else é a chave
- otherTemplate é a expressão.
Isso é interpretado como <ng-template [ngIfElse]="otherTemplate"></ng-template>
Em *ngFor="let item of list; trackBy: trackByFn
, na expressão trackBy: trackByFn
:
- ngFor é o prefixo
- trackBy é a chave
- trackByFn é a expressão.
Isso é interpretado como <ng-template [ngForTrackBy]="trackByFn"></ng-template>
Além disso, nesse exemplo com NgFor, of list
em let item of list
também é uma expressão chave.
- ngFor é o prefixo
- of é a chave
- list é a expressão.
Isso é interpretado como <ng-template [ngForOf]="list"></ng-template>
Local bindings
A última coisa a mencionar é a palavra-chave opcional as
no final da expressão. Ela declara uma variável do template e mapeia o resultado da expressão para ela.
*ngIf="condition as value"
é interpretado como <ng-template [ngIf]="condition" let-value="ngIf">
Conclusão
É isso. Agora você entende como as diretivas estruturais funcionam e como analisar sua micro sintaxe.
Farei outro artigo sobre como criar uma diretiva estrutural do zero e como fazer com que o compilador do Angular verifique o contexto dela.
Até lá, tenha um ótimo dia e nos vemos em breve!