Como criar um formulário dinâmico avançado com Angular?
Precisando de um guia avançado para criar um formulário dinâmico com Angular?
Esse é o tutorial passo a passo que você está precisando.
Tabela de Conteúdo 👇
- Uma história comum
- Ciando modelo de dados
- Criando o serviço de dados
- Crie o componente de entrada do formulário
- Crie o componente do formulário
- Preenchendo nosso formulário
- Conclusão
- Leitura Adicional
Uma história comum
Porque essa situação é muito recorrente?
O projeto pelo qual você está suando está finalmente se tornando um sucesso...
...e você está quase acabando...
Mas na última hora, aquele formulário precisa de uma mudança, e essa mudança precisa acontecer AGORA!
Suspiro.
Esse tipo de situação é muito comum. Então o que você acha de um método proativo que nos permita renderizar dinamicamente um formulário Angular? E fazer com que nosso formulário lide com uma série de situações difíceis?
Que tal criar um formulário dinâmico com Angular para que nós possamos encaminhar o blob do JSON ou outra coisa que ele possa renderizar?
Boa ideia, não é?
Mas... Espera um pouco. O que nós precisamos para renderizar dinamicamente? Temos várias coisas para resolver, por exemplo:
- Texto de entrada
- Área de texto
- Botões
- Caixas de check
- Validação do formulário
- E muito mais!
Caramba! Existe alguma forma de fazer isso de sofisticadamente?
Nesse artigo eu vou guiar você passo a passo para construir um formulário dinâmico como a imagem mostra a seguir. Esse exemplo utiliza CSS Bootstrap para o estilo do formulário, mas você pode personalizar da forma que você desejar.
Esse exemplo utiliza CSS Bootstrap para o estilo do formulário, mas você pode personalizar da forma que você desejar.
Gostaria de saber o código todo agora? Esse é o link.
Criando modelo de dados
O primeiro passo é criar um modelo para os campos de entrada. Vamos criar um arquivo chamado form-field.ts e exportar nosso modelo, assim:
export class FormField<T> {
value: T;
key: string;
label: string;
required: boolean;
validator: string;
order: number;
controlType: string;
type: string;
options: { key: string; value: string }[];
constructor(
options: {
value?: T;
key?: string;
label?: string;
required?: boolean;
validator?: string;
order?: number;
controlType?: string;
type?: string;
options?: { key: string; value: string }[];
} = {}
) {
this.value = options.value;
this.key = options.key || "";
this.label = options.label || "";
this.required = !!options.required;
this.validator = options.validator || "";
this.order = options.order === undefined ? 1 : options.order;
this.controlType = options.controlType || "";
this.type = options.type || "";
this.options = options.options || [];
}
}
Criando o serviço de dados
O próximo passo é criar um serviço que vai pegar nossos dados e retornar para o grupo de formulário.
Vamos pegar o Angular CLI e gerar o serviço usando o seguinte comando:
Depois inserir alguns poderes do formulário dinâmico com o seguinte código:
import { Injectable } from '@angular/core';
import { FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { of } from 'rxjs';
import { FormField } from './form-field';
@Injectable({
providedIn: 'root'
})
export class FormfieldControlService {
constructor() { }
toFormGroup(inputs: FormField<string>[]): FormGroup {
const group: any = {};
inputs.forEach(input => {
let validator: ValidatorFn[] = input.required ? [Validators.required] : [];
switch (input.validator) {
case "email":
validator.push(Validators.email);
break;
default:
break;
}
group[input.key] = validator.length > 0 ? new FormControl(input.value || '', validator)
: new FormControl(input.value || '');
});
return new FormGroup(group);
}
getFormFields() {
const inputs: FormField<string>[] = [
new FormField<string>({
controlType: "textbox",
key: 'name',
label: 'Name',
required: true,
order: 1
}),
new FormField<string>({
controlType: "textbox",
key: 'email',
label: 'Email',
type: 'email',
required: true,
validator: "email",
order: 2
}),
new FormField<string>({
controlType: "dropdown",
key: 'country',
label: 'Country',
options: [
{key: 'usa', value: 'United States of America'},
{key: 'br', value: 'Brazil'},
{key: 'other', value: 'Other'}
],
order: 3
}),
new FormField<string>({
controlType: "checkbox",
key: 'agree',
label: 'I accept terms and services',
type: 'checkbox',
required: true,
order: 4
}),
new FormField<string>({
controlType: "radio",
key: 'sex',
label: 'Sex',
type: 'radio',
options: [
{key: 'male', value: 'Male'},
{key: 'female', value: 'Female'}
],
order: 5
}),
new FormField<string>({
controlType: "textarea",
key: 'message',
label: 'Mesage',
type: 'textarea',
order: 6
})
];
return of(inputs.sort((a, b) => a.order - b.order));
}
}
Muito difícil? Complicado?
Ótimo. Isso quer dizer que você está aprendendo coisas novas!
Note que nesse serviço nós temos duas funções. Uma delas pega o grupo de FormField e devolve para o FormGroup. A segunda função devolve o grupo de FormField. É na segunda opção que você pode usar sua imaginação para pegar os dados do formulário para qualquer objetivo que você queira.
Crie o componente de entrada do formulário
O próximo passo é criar os componentes que vão renderizar seu formulário dinâmico.
Vamos criar o primeiro e intitular como dynamic-form-input. Esse é o comando Angular CLI:
ng generate component dynamic-form-input
Vamos editar o dynamic-form-input.component.ts
para tomar o FormGroup
e FormField<string>
com um Input.
import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { FormField } from '../form-field';
@Component({
selector: 'app-dynamic-form-input',
templateUrl: './dynamic-form-input.component.html',
styleUrls: ['./dynamic-form-input.component.css']
})
export class DynamicFormInputComponent {
@Input() input: FormField<string>;
@Input() form: FormGroup;
get isValid() { return this.form.controls[this.input.key].valid; }
}
E por final editar o dynamic-form-input.component.html para renderizar o campo de formulário.
<div [formGroup]="form" class="form-group">
<div [ngSwitch]="input.controlType">
<div *ngSwitchCase="'textbox'">
<label [attr.for]="input.key">{{input.label}}</label>
<input class="form-control" [formControlName]="input.key" [id]="input.key" [type]="input.type">
</div>
<div *ngSwitchCase="'dropdown'">
<label [attr.for]="input.key">{{input.label}}</label>
<select class="custom-select" [id]="input.key" [formControlName]="input.key">
<option *ngFor="let opt of input.options" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
<div *ngSwitchCase="'checkbox'">
<div class="form-check">
<input class="form-check-input" type="checkbox" [formControlName]="input.key" [id]="input.key">
<label class="form-check-label" [attr.for]="input.key">{{input.label}}</label>
</div>
</div>
<div *ngSwitchCase="'radio'">
<div class="form-check form-check-inline" *ngFor="let opt of input.options">
<input class="form-check-input" type="radio" [formControlName]="input.key" [id]="input.key" [value]="opt.value">
<label class="form-check-label" [attr.for]="opt.key"> {{ opt.value }} </label>
</div>
</div>
<div *ngSwitchCase="'textarea'">
<label [attr.for]="input.key">{{input.label}}</label>
<textarea class="form-control" [formControlName]="input.key" [id]="input.key" rows="5"></textarea>
</div>
</div>
</div>
Crie o componente do formulário
E, por fim, vamos gerar nosso último componente.
ng generate component dynamic-form
Esse é o código para o arquivo .ts
.
import { FormGroup } from '@angular/forms';
import { FormField } from '../form-field';
import { FormfieldControlService } from '../formfield-control.service';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.css']
})
export class DynamicFormComponent implements OnInit {
@Input() formFields: FormField<string>[] = [];
form: FormGroup;
payLoad = ' ';
constructor(private formfieldService: FormfieldControlService) { }
ngOnInit(): void {
this.form = this.formfieldService.toFormGroup(this.formFields);
}
onSubmit() {
this.payLoad = JSON.stringify(this.form.getRawValue());
}
}
E o HTML.
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let formField of formFields" class="form-group">
<app-dynamic-form-input [input]="formField" [form]="form"></app-dynamic-form-input>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" [disabled]="form.invalid">Save</button>
</div>
</form>
<div *ngIf="payLoad" class="form-group">
<strong>Here's the payload</strong><br>{{payLoad}}
</div>
</div>
Preenchendo nosso formulário
Agora temos tudo prontinho. Tudo o que precisamos fazer é "ligar".
Dentro do nosso arquivo app.component.ts vamos pegar os dados para o nosso formulário dinâmico.
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { FormField } from './form-field';
import { FormfieldControlService } from './formfield-control.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [FormfieldControlService]
})
export class AppComponent {
title = 'AngularDynamicForms';
formFields: Observable<FormField<any>[]>;
constructor(service: FormfieldControlService) {
this.formFields = service.getFormFields();
}
}
Então em app.component.html vamos fazer o template do formulário dinâmico e passar para os dados dinâmicos.
<app-dynamic-form [formFields]="formFields | async "></app-dynamic-form>
Está feito!!! É isso!
Conclusão
Esse é um exemplo avançado e bem explicado de como um formulário dinâmico com Angular pode ser criado.
Nós vimos um modelo para moldar diferentes campos de formulário e então criamos um serviço para os campos de formulário e por fim, a renderização do formulário dinâmico em um componente.
Muito trabalhoso? Esse é um a solução complexa para um simples formulário de contato, mas quando são muitos requerimentos, vale a pena pensar em utilizar o formulário dinâmico com Angular.
Dúvidas ou comentários? Esqueci de mencionar alguma coisa? Não deixe de entrar em contato.