Formulários dinâmicos com componentes dinâmicos no Angular

Andrew Rosário
5 min readApr 6, 2022

--

Neste artigo vamos explorar a criação de formulários dinâmicos utilizando todo o poder ferramental do Angular com Reactive Forms e a injeção de componentes dinâmicos com ViewContainerRef.

É muito comum uma aplicação precisar ter diversos formulários semelhantes. Podemos usar como exemplo o Google Forms, onde criamos formulários com base nos dados fornecidos. Basta a aplicação receber esses dados (que podem vir de uma API, por exemplo) e montar o formulário com seus devidos labels, inputs e validações.

A própria documentação do Angular tem uma sessão bem interessante sobre este assunto, mas aqui vamos criar uma solução ainda mais robusta e escalável. Você pode conferir o exemplo final pelo StackBlitz.

Criando o DynamicFormModule

O primeiro passo é criar um módulo onde podemos vincular todas as nossas classes que possuem a responsabilidade de criação e manipulação de formulários dinâmicos. Vamos aproveitar e importar o ReactiveFormsModule que é peça central para a construção de formulários reativos.

Criando o modelo de configuração

Vamos utilizar os recursos de tipagem estática do TypeScript para criar nossa model de configuração dos controles dinâmicos.

Cada controle customizável será do tipo DynamicFormConfig, que terá as seguintes propriedades:

  • type: Tipo de controle que será renderizado. Cada tipo refere-se a um componente específico que veremos mais adiante. Neste exemplo teremos somente inputs e selects, mas poderíamos ter vários outros conforme a necessidade. (Exemplo: checkbox, radio button, date picker).
  • label: O texto do controle que será visível para o usuário. Utilizaremos a tag <label> do HTML para ele.
  • name: Este será o nome dado para o Angular manipular o seu FormControl. Iremos atribuí-lo para a diretiva FormControlName.
  • initialValue: Como o próprio nome diz, será o valor inicial do controle. Futuramente se os dados forem salvos, podemos recuperá-los e atualizar este campo.
  • placeholder: Placeholder do controle, caso exista.
  • options: Este campo somente será utilizado quando o controle for do tipo select. Basicamente será um array dos itens disponíveis para seleção.
  • validation: Um array de validações do controle, como por exemplo uma validação se o campo é obrigatório.

Vale mencionar que podemos ter outras propriedades neste modelo caso sejam necessárias. Varia muito das necessidades da implementação.

Criando o DynamicFormComponent

Este será o componente responsável pela criação do formulário dinâmico. Ele receberá por meio do Decorator @Input um array de configurações do tipo DynamicFormConfig.

Percorremos o nosso array de configurações, e para cada configuração criamos um novo FormControl dentro do nosso formulário a partir do método addControl. Cada controle será criado com seu respectivo nome (name), valor inicial (initialValue) e validação (validation).

Agora vamos analisar seu template HTML, mais especificamente este trecho:

<ng-container 
*ngFor="let field of config"
[appDynamicField]="field">
</ng-container>

Ok, faz sentido utilizar o *ngFor para renderizar cada um dos controles. Mas o que seria este appDynamicField? Tenha calma que logo voltaremos a falar sobre isso!

Agora vamos criar os componentes específicos para cada tipo de campo.

Criando o DynamicFormInputComponent

O primeiro componente a ser criado é o de Input. Um campo simples para o usuário digitar um texto.

O template HTML é bem simples. Todos os dados virão de acordo com o nosso objeto de configurações. Já no viewProviders temos um ponto interessante.

viewProviders: [{ 
provide: ControlContainer,
useExisting: FormGroupDirective
}]

Sabemos que para vincular um FormGroup no nosso template, utilizamos a diretiva FormGroupDirective. Exemplo: [formGroup]="form".

Aqui estamos referenciando nossa diretiva pelo ControlContainer, que é uma classe muito poderosa para buscar uma referência de um formulário na árvore de componentes.

Veja este artigo onde explico mais detalhadamente sobre o ControlContainer.

O nosso componente de Input tem um FormControlName, mas sabemos que ele sozinho não resolve nada. Ele precisa de um FormGroup atrelado a ele. O FormGroup que ele irá receber será o do DynamicFormComponent!

Graças a nossa estratégia de definir nosso provider com o ControlContainer, o componente de Input conseguirá buscar a referência do formulário a partir de um componente pai.

Criando o DynamicFormSelectComponent

O segundo componente criado é um select onde o usuário poderá selecionar uma opção dentre as disponíveis.

Este componente segue a mesma lógica do DynamicFormInputComponent. Capturamos o formulário pai pelo ControlContainer.

A grande diferença está no array de options dentro do nosso objeto de configurações. Este é um atributo específico deste componente.

Agora que já temos nossos componentes específicos criados, vamos voltar as atenções para o DynamicFormComponent.

Poderíamos muito bem utilizar vários *ngIf ou o *ngSwitchCase para renderizar nossos componentes específicos com base em nosso array de configurações. Algo mais ou menos dessa forma:

Mas vamos adotar uma solução mais elegante. Você se lembra que foi deixado no ar um misterioso appDynamicField? Pois bem, neste ponto você talvez já tenha adivinhado que esta será uma diretiva! E pelo seu nome, podemos entender que será ela a responsável por renderizar cada campo. Então vamos lá!

Criando a DynamicFieldDirective

Diretivas são extremamente poderosas no Angular, porém, muito subestimadas. Vamos entender a solução da DynamicFieldDirective.

Primeiramente criamos um objeto chamado components que vai receber todos os componentes específicos para cada campo. Aqui utilizamos os Mapped Types do TypeScript para tornar a sua tipagem muito mais concisa.

Na inicialização da diretiva capturamos o componente que devemos renderizar de acordo com o @Input de configurações recebido. Agora basta criar o componente com o método createComponentda classe ViewContainerRef.

Temos uma referência do componente instanciada na variável componentRef. A partir de instance conseguimos ter acesso a todos os atributos e métodos da classe do componente. Atribuímos então o objeto de configurações para o mesmo.

Atenção: Até a versão 12 do Angular, precisávamos também injetar a classe ComponentFactoryResolver para criar componentes dinâmicos. A partir da versão 13 conseguimos esse feito somente com o ViewContainerRef, deixando assim nosso código mais limpo!

Ao criar novos componentes específicos, a única coisa que precisamos fazer é incluir no objeto de componentes. Não precisamos fazer nenhum tipo de condição no template, respeitando vários princípios como o Single Responsability Principle e o Open-Closed Principle.

Nosso módulo de componentes dinâmicos está pronto! Precisamos somente exportar o DynamicFormComponent para utilizá-lo em outros componentes.

Agora chegou o momento mais esperado. Vamos criar nosso formulário dinâmico somente passando nossas configurações! Lembrando que essas configurações podem ser passadas de uma API ou de qualquer outra fonte de dados.

Este é um exemplo bem simples, mas podemos criar muitos outros recursos para deixar nossos formulários dinâmicos bem poderosos. Na aplicação abaixo do StackBlitz há um recurso para mostrar mensagens de erro de acordo com as validações, além de salvar os dados do formulário no LocalStorage.

Conclusão

Neste artigo exploramos uma solução eficiente e robusta para a criação de formulários dinâmicos utilizando o Angular. Utilizamos várias abordagens como os Reactive Forms, diretivas customizadas e a criação de componentes dinâmicos.

--

--

Andrew Rosário
Andrew Rosário

Written by Andrew Rosário

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

Responses (1)