Dominando as Diretivas Estruturais no Angular

Andrew Rosário
5 min readJan 13, 2022

--

Se você é um desenvolvedor Angular, certamente já utilizou pelo menos uma diretiva estrutural do Framework. ngIf e ngFor são os exemplos mais clássicos. Elas possuem o objetivo de adicionar e remover elementos do DOM.

Uma característica da diretiva estrutural é a presença do asterisco (*) no início de sua declaração. Mas você sabe por qual motivo? Ele é basicamente um açúcar sintático para que não seja preciso escrever um <ng-template> em seu template HTML.

Como dito anteriormente, utilizamos com frequência as diretivas estruturais nativas do Angular, mas também podemos criar nossas próprias. Antes de analisar um exemplo útil, vamos entender a sua estrutura. Para isso, trago abaixo uma diretiva estrutural que possui o mesmo comportamento do ngIf.

Repare que temos duas dependências no método construtor:

  • TemplateRef: Como sabemos que uma diretiva estrutural cria internamente um <ng-template>, então esta dependência nada mais é do que a referência deste template.
  • ViewContainerRef: Esta dependência representa um Container onde uma ou mais views podem ser inseridas ou removidas do template.

A lógica ocorre dentro do nosso Input setter. É importante ressaltar que o nome do Input precisa ser o mesmo do seletor da diretiva. Isso é necessário, pois toda vez que o Angular detectar uma mudança na condição, o código precisa ser executado.

O código consiste basicamente nesta condição. Se for verdadeira, utilizamos o método createEmbeddedView do ViewContainerRef, passando por parâmetro a referência do nosso template. Este método irá anexar o Container no template HTML. Se a condição for falsa basta chamar o método clear para limpar o template.

Criando uma diretiva estrutural customizada

Em nossa aplicação nós temos um sistema de Lock que impede que dois ou mais usuários editem um mesmo registro simultaneamente. Ele é utilizado em diversos lugares e tem o objetivo de evitar conflitos de dados. Um segundo usuário só consegue editar um determinado registro quando o primeiro usuário terminou de editá-lo.

Este é um comportamento que é replicado em muitas páginas, portanto criamos uma diretiva estrutural para renderizar um template quando um registro já está sendo editado por outro usuário.

Por motivos de facilitação do código, não estou me desinscrevendo do Observable!

Temos um serviço chamado LockService que expõe um stream com o nome do usuário que está editando o registro no momento. Caso não tenha nenhum usuário então null é retornado. Injetamos este serviço em nossa diretiva e nos inscrevemos neste stream para verificar se podemos renderizar o template ou não.

Ótimo! Podemos utilizar a diretiva de forma muito simples. Mas precisamos de mais alguns detalhes. Primeiramente precisamos mostrar qual é o usuário que está editando o registro no momento.

Expondo variáveis de template

Além de anexar elementos do DOM em uma diretiva estrutural, podemos também expor um objeto de contexto que estará disponível para realizar o binding no template. Este objeto de contexto pode ser passado como segundo parâmetro no método viewContainerRef.createEmbeddedView.

Agora estamos expondo o username como uma propriedade dentro do objeto de contexto. Perceba que temos a chave com o nome $implicit. Logo entenderemos o porquê.

No template HTML capturamos o usuário a partir de uma variável de template. Passamos a palavra-chave let que declara esta variável e cria sua referência.

Esta sintaxe soa familiar, não? Você provavelmente já escreveu um código como *ngFor="let item of items". O let do ngFor vem exatamente deste objeto de contexto.

Múltiplas propriedades no objeto de contexto

O próximo passo é implementar a funcionalidade do botão Atualizar. Quando o usuário clicar neste botão, a aplicação precisa fazer uma nova consulta no servidor, verificando se o usuário já terminou de editar o registro.

O serviço LockService já possui um método com esta funcionalidade. Poderíamos simplesmente injetar o serviço no componente e chamar o método no clique do botão. Funcionaria, porém em um cenário onde temos dezenas de componentes que necessitariam injetar esse serviço, não seria a melhor solução em termos de reaproveitamento de código.

Podemos passar para o objeto de contexto da nossa diretiva várias propriedades, e essas propriedades podem ser qualquer coisa. Não somente valores mas métodos também. Então a ideia aqui é expor o método check do LockService dentro do objeto de contexto da diretiva!

Temos agora duas propriedades expostas: o usuário e o método de verificação de lock. Perceba que foi necessário utilizar o método bind para indicar qual é o contexto de this (Neste caso é o LockService).

Simples e extremamente poderoso! declaramos mais uma variável de template chamada check e chamamos ela como uma função no botão Atualizar.

Neste ponto temos um questionamento. para utilizá-la precisamos escrever o código let check = check. Se declararmos somente como let check o código não funcionaria. Mas por qual motivo?

A resposta está na chave $implicit que atribuímos para o username. Quando passamos uma propriedade implícita, o Angular não obriga que passemos o nome da chave no template. Isso significa que se atribuirmos qualquer nome, a variável será referenciada ao username. (Exemplo: let qualquerCoisa. A variável qualquerCoisa terá o username).

Sabendo disso, podemos sempre ter uma propriedade implícita dentro do objeto de contexto. Então é interessante deixar a propriedade mais importante da sua diretiva como implícita para facilitar o binding no template.

Como a propriedade check não é implícita, precisamos declará-la atribuindo o mesmo nome que foi informado na diretiva. Exemplo: let checkQualquerCoisa = check. O nome da esquerda pode ser qualquer um, porém o nome da direita deve obrigatoriamente ser o mesmo informado no objeto de contexto.

Criando Inputs para a diretiva

A última implementação do nosso sistema de lock consiste em fornecer um template alternativo (um placeholder) para caso não tenha nenhum usuário editando um determinado registro. Neste caso devemos renderizar os botões de Salvar e Desfazer alterações.

Já sabemos como criar um Input para uma diretiva estrutural. Declaramos o Input com o mesmo nome do seletor da diretiva. Vimos este exemplo no início do artigo onde criamos uma diretiva semelhante ao ngIf.

No nosso caso precisamos criar um Input que receba um template a ser renderizado (Os botões de ações). Se você já utilizou o else do ngIf saiba que a implementação será basicamente a mesma.

Queremos esse mesmo resultado em nossa diretiva, porém em vez de escrevermos else, um Input com o nome unlock seria mais apropriado para esta situação.

Para utilizar demais Inputs em uma diretiva estrutural, o Angular oferece uma sintaxe própria:

Se o seletor da diretiva é ngIf e queremos utilizar a nomenclatura else, então nomeamos o Input como ngIfElse. Concatenamos o seletor da diretiva com o nome do Input.

@Input('ngIfElse') placeholder: TemplateRef<any>;

Um outro exemplo é do próprio *ngFor="let item of items". O Input of é declarado desta mesma forma:

@Input('ngForOf') items: Array<any>;

Vamos seguir esse mesmo padrão para criar o Input unlock na nossa diretiva:

@Input('lockUnlock') placeholder: TemplateRef<any>;

O código final da diretiva ficou assim:

Se não houver usuário editando o registro então realizamos duas ações: primeiro limpamos o Container de lock com o método clear, e logo em seguida criamos um Container passando o nosso placeholder que veio como Input.

Agora sim temos nossa implementação completa!

Conclusão

Neste artigo vimos como criar nossas próprias diretivas estruturais no Angular, além de entender o conceito de renderização de um objeto de contexto para expormos variáveis de template, assim como seus Inputs personalizados.

O código final da implementação pode ser conferido pelo StackBlitz:

--

--

Andrew Rosário
Andrew Rosário

Written by Andrew Rosário

Desenvolvedor Front-end, mentor e palestrante. Apaixonado por tecnologia e por compartilhar conhecimento.

No responses yet