Atualmente são apenas duas páginas, mas ao longo dos tutoriais outras serão desenvolvidas.
Inicializando a aplicação
$ ng-serve
Após carregar acesse http://localhost:4200/ leia atentamente o conteúdo da página Reactive Forms, em seguida navegue para a página Formulário Simples, leia e realize testes no formulário.
Os passos acima informados são extremamente necessários para o completo entendimento deste tutorial.
Arquitetura
Leia os comentários de cada método dos arquivos presentes neste projeto, isso irá ajudar muito na compreensão do que foi feito.
Levando em consideração que o ".html" seja o ponto de partida para visualizarmos uma página, quero propor a seguinte abordagem aqui, que será mostrar do arquivo mais interno ao mais externo. Ou seja, vamos começar pelo serviço, passando para o componente até chegar na página ".html".
abstract-form-builder.ts
É esta classe que irá fazer toda a "magia" na criação do objeto FormGroup. Em resumo sua função é criar o FormGroup do tipo <T> do qual será passado pela classe que for sua herdeira. Seu método principal é o construirFormGroup() além de criar o FormGroup ele cria uma referência local através do método atualizarReferenciaFormulario() capturando os FormControls e passando para a variável local formControls do tipo <T>. Além disso são inicializadas as validações padrão dos FormControls criados, quando houverem.
abstract-form-builder.ts
import {Injectable} from"@angular/core";import {AbstractControl, FormBuilder, FormGroup} from"@angular/forms";@Injectable()exportabstractclassAbstractFormBuilder<T> { formControls:T;constructor(private formBuilder:FormBuilder ) { }/** * Quem implementar será obrigado a informar se tem alguma validação * padrão a ser feita no form */abstractinicializarValidacoesDefault();/** * Aqui Passamos um objeto com os atributos que queremos para o * nosso formulário, neste método será criado um FromGroup * * @param objetoReferencia Objeto que dará inicio a magia * @return: FormGroup */construirFormGroup(objetoReferencia:T):FormGroup{constform=this.formBuilder.group(objetoReferencia);this.atualizarReferenciaFormulario(form, objetoReferencia);this.inicializarValidacoesDefault();return form; }/** * Captura os controls criados e passam para um objeto como tipo T, * desta forma evita-se o comum uso de "this.formGroup.get('nomeAtributo')" * e passa-se a usar um obejeto com os atributos necessários * * @param form * @param objetoReferencia */atualizarReferenciaFormulario(form:FormGroup, objetoReferencia:T){Object.entries(objetoReferencia).forEach(field => {constlabel= field[0]; objetoReferencia[label] =form.controls[label]; });this.formControls = objetoReferencia; }/** * Ao receber um Control seus validators serão substituidos pelos informados * * @param control * @param novoValidador */atualizarValidadores(control:AbstractControl, novoValidador:any|any[]){control.setValidators(novoValidador);control.updateValueAndValidity(); }}
formulario-simples-builder-service.ts
formulario-simples-builder-service.ts
import {Injectable} from"@angular/core";import {AbstractFormBuilder} from"src/app/shared/form-builer/abstract-form-builer";import {Validators} from'@angular/forms';/** * Model, DTO, VM... Tanto faz! * Independente da sigla que for usar a idea é ter um objeto que pode * ser usado como coringa. * Vamos entender melhor no artigo */exportclassPessoaVM {constructor(public nome?:any,public apelido?:any,public altura?:any ) { }}/** * A ideia é armazenar nesse serviço todas as regras de validações do formulário */@Injectable()exportclassFormularioSimplesBuilderServiceextendsAbstractFormBuilder<PessoaVM> {/** Podem ser um ou vários validator como para o campo default */private alturaValidatorDefault =Validators.required;/** * Regra de negócio 1: os campos nome e altura são de preenchimento obrigatório */inicializarValidacoesDefault() {this.atualizarValidadores(this.formControls.nome,Validators.required);this.atualizarValidadores(this.formControls.altura,this.alturaValidatorDefault); }/** * Regra de negócio 2: Quando o apelido for preenchido então a altura passa * a ter uma validação mínima de 1.20 e máxima de 2.20. * Caso contrário então passa o validator default * * obs: repare no this.formControls.apelido.value "apeli passou a ter um novo * atributo o '.value' isso porque no objeto 'formControls' ele tem uma * referência de um FormControl". */atualizarComportamentoControlAltura() {constalturaValidators=this.formControls.apelido?.value ? [this.alturaValidatorDefault,Validators.min(1.20),Validators.max(2.20) ] :this.alturaValidatorDefault;this.atualizarValidadores(this.formControls.altura, alturaValidators); }}
O Serviço FormularioSimplesBuilderService é filho de AbstractFormBuilder, ou seja, seus comportamentos serão herdados e é nele que devemos aplicar todas as regras de validação do formulário. Isso o torna um serviço especializado em regras de negócio especificas do formulário que estamos desenvolvendo o que irá tornar o código mais legível e com baixo acoplamento.
campo-minado-component.ts
/** * Imagina só sua classe component cheia desses métodos get('altura') pra cá * get('altura') pra lá e de repente o atributo muda de "altura" para * "alturaDaPessoa" você vai precisar de uma boa IDE para te ajudar arrumar isso. */modificarComportamentoFormulario() {this.pessoaFormGroup.get('altura').setValidators(this.pessoaFormGroup.get('apelido').value ? [Validators.required,Validators.min(1.20),Validators.max(2.20) ] :Validators.required ); }
Vamos usar um objeto como referência que é melhor né!? Usamos o this.formControls objeto que foi herdado de AbstractFormBuilder dai colocamos this.formControls.altura se mudar já vai aparecer como erro de compilação, entretanto, será mais difícil isso acontecer pois a maioria das IDE's são eficientes no seu Refactor > Renamee conseguem atualizar todas as referências.
formulario-simples.component.ts
Nesta classe iremos injetar o FormularioSiplesBuilderService, usar o método construirFormGroup() e armazenar numa variável local do tipo FormGroup. Além disso a classe não tem mais preocupação nenhuma em como serão feitas as validações do formulário, mas apenas em quando serão feitas.
formulario-simples.component.ts
import {Component, OnInit} from'@angular/core';import {FormGroup} from"@angular/forms";import {FormularioSimplesBuilderService, PessoaVM} from"./formulario-simples-builder-service";@Component({ selector:'app-formulario-simples', templateUrl:'./formulario-simples.component.html', styleUrls: ['./formulario-simples.component.css'], providers: [FormularioSimplesBuilderService]})exportclassFormularioSimplesComponentimplementsOnInit { pessoaFormGroup:FormGroup; exibeTextoSubmissao:boolean=false;constructor(private formularioSimplesBuilderService:FormularioSimplesBuilderService ) { }ngOnInit():void {this.inicializarFormulario(); }/** * Dica: sempre bom separar bem os métodos e suas responsabilidades, alguns * programadores costumam criar métodos gigantes que fazem milhares de * coisas ao mesmo tempo. Não seja essa pessoa! */privateinicializarFormulario() {this.pessoaFormGroup =this.formularioSimplesBuilderService.construirFormGroup(newPessoaVM()); }/** * Retornar os valores do formulário no formato Json */verificarValoresFormulario() {returnJSON.stringify(this.pessoaFormGroup?.value,null,2); }/** * Este método esta sendo chamado pelo html toda vez que é digitado alguma * iformação no campo apelido.As regras de como os campos devem se comportar * estão no FormulárioSimplesBuilderService */modificarComportamentoFormulario() {this.formularioSimplesBuilderService.atualizarComportamentoControlAltura(); }/** * Nesse método poderiamos chamar métodos que irão tratar as informações * adquiridas no formulário e em seguinda utilizar um serviço para enviar * para o back-end (Quem sabe a gente não faz isso um dia) */submeterFormulario() {this.exibeTextoSubmissao =true; }}
formulario-simples.component.html
Finalmente o "html" aqui não temos nada novo, o formulário foi apresentado assim como qualquer exemplo que podemos encontrar no site do Angular.
formulario-simples.component.html
<!-- FORMULÁRIO - estão sendo utilizadas classes de estilização do Bootstrap
e campos do Angular Material -->
<div class="container-fluid d-flex justify-content-center">
<div class="card bg-light">
<div class="card-header text-center">
<h2 class="card-title">Cadastrar Pessoa</h2>
</div>
<div class="card-body">
<form class="form-group" [formGroup]="pessoaFormGroup"
(ngSubmit)="submeterFormulario()">
<div class="row">
<mat-form-field class="col">
<mat-label>Nome</mat-label>
<input matInput type="text" formControlName="nome">
</mat-form-field>
</div>
<div class="row">
<mat-form-field class="col-9">
<mat-label>Apelido</mat-label>
<input (keyup)="modificarComportamentoFormulario()"
formControlName="apelido" matInput type="text">
</mat-form-field>
<mat-form-field class="col-3">
<mat-label>Altura</mat-label>
<input matInput type="number" formControlName="altura">
</mat-form-field>
</div>
<div class="text-right mt-2">
<button mat-raised-button class="btn btn-success" type="submit"
[disabled]="pessoaFormGroup.invalid">Salvar
</button>
</div>
</form>
</div>
<div class="alert alert-info" role="alert">
<div class="row d-flex justify-content-center">
<strong>Valores (JSON)</strong>
</div>
<div class="row d-flex justify-content-center">
<pre>{{verificarValoresFormulario()}}</pre>
</div>
</div>
</div>
</div>
<div class="container-fluid mt-2">
<div *ngIf="exibeTextoSubmissao" class="alert alert-danger">
<button mat-mini-fab color="warn" class="float-right"
(click)="exibeTextoSubmissao = !exibeTextoSubmissao">
<mat-icon>close</mat-icon>
</button>
<h4 class="alert-heading">
<span class="material-icons">device_unknown</span>️
Vish!! Acho que tá faltando alguma coisa
</h4>
<hr>
<p>
Vi que você tentou salvar, só que eu não fiz essa parte. <span class="material-icons">pest_control</span>
<br>
Caso queira avançar sugiro que crie um serviço para enviar as informações do front-end
para o back-end através de uma requisição http.
</p>
<p>Ps: se valer de alguma ajuda eu já fiz um método chamado "submeterFormulario" e acho você pode usá-lo para
salvar.
<br>
Boa sorte! <span class="material-icons">directions_run</span></p>
</div>
<div class="alert alert-warning" role="alert">
<h4>Regras de negócio aplicadas:</h4>
<ul>
<li>O Campos nome e altura são de preenchimento obrigatório</li>
<li>Ao preencher o apelido o campo altura passa a ter um valor mínimo de 1.20 e máximo de 2.20</li>
</ul>
<hr>
<h4><span class="material-icons">contact_support</span> Dúvidas</h4>
<h5 class="alert-heading"> - Quem vai pedir um cadastro com uma regra tão estranha assim?</h5>
<p>
De fato a regra da altura é estranha, mas acredite quando eu digo que seu cliente poderá
ter requisitos bem peculiares. <span class="material-icons">sentiment_satisfied</span>
</p>
<h5 class="alert-heading"> - Onde foram parar os valores digitados?</h5>
<p>
Estão no objeto do tipo FormGroup e também estão no objeto formControls da classe FormularioSimplesBuilderService
que criamos para facilitar nossa vida. Vamos entender melhor sobre isso durante o tutorial.
</p>
</div>
</div>
Reparem que criei uma classe chamada PessoaVM (é ela que será o tipo <T>) no mesmo arquivo, mas se preferir pode colocar dentro de um arquivo próprio. Sua função é ser um modelo para nosso formulário, lembre-se que um dos problemas apontados no artigo anterior foi justamente o fato dos atributos serem criados em tempo de execução. Agora não mais!
A beleza de guardarmos uma referência dos FormControls dentro do objeto formControls é que não precisaremos usar o método get() do FormGroup passando uma string e correndo o risco de ter que replicar esse código diversas vezes. Dependendo da quantidade de regras de validação de um determinado campo esse get() irá sair caro pra você e é nesse ponto que está o campo minado.
O método modificarComportamentoFormulário() será executado quando o campo apelido for modificado então ele irá chamar o método atualizarComportamentoControlAltura(), pois esse sim sabe como manipular as validações. Olha que maravilha, o componente não precisa se preocupar em como fazer ele só delega e nosso Serviço especializado se encarrega de mostrar para o que veio.
Reitero a importância de você abrir o projeto, executar e ler seu conteúdo, para adquirir um completo entendimento acerca deste artigo. Se não o fez, volte e faça agora, eu espero.
Caso tenha gostado da solução e deseje implementá-la no seu projeto sugiro você verificar se os objetos recebidos nos métodos são nulos. É importante, mas no exemplo eu não fiz.
Obrigado por chegar até aqui! Aguardem para mais artigos.