3
respostas

[Dúvida] Validação Formulário Customizado

Boa noite, tenho uma dúvida de como ficaria a validação de um formulário na seguinte necessidade:

Um formulário de cadastro de pessoa, onde o primeiro campo de opção é o tipo de pessoa (física ou jurídica). Caso se escolha pessoa física o usuário deverá informar todos os campos necessários, e os campos específicos de pessoa jurídica ficarão desabilitados ou invisíves.

Levando em consideração que todos os campos do formulário deverão estar como required, caso o usuário tenha escolhido pessoa física como ficará a validação desse formulário, visto que os campos específicos de pessoa jurídica ficarão vazios ?

habilitarBotao(): string {
    if(this.formulario.valid)
      return "botao";
    else
      return "botao__desabilitado";
  }

e

<button (click)="editarPensamento()" [ngClass]="habilitarBotao()" [disabled]="!formulario.valid">Salvar</button>

Desde já agradeço.

3 respostas

Boa noite, HEBERTON,

Pode parecer complexo, mas se voce ler a documentacao de cada import no proprio site do Angular voce consegue entender


import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
import { BehaviorSubject, Subject, Observable, combineLatest } from 'rxjs';
import { map, takeUntil, distinctUntilChanged, tap, startWith } from 'rxjs/operators';

type TipoPessoa = 'fisica' | 'juridica';

interface ValidacaoCampo {
  campo: string;
  validadores: any[];
}

@Component({
  selector: 'app-cadastro-pessoa',
  template: `
    <form [formGroup]="formulario">
      <div class="form-group">
        <label>Tipo de Pessoa</label>
        <select formControlName="tipoPessoa">
          <option value="fisica">Física</option>
          <option value="juridica">Jurídica</option>
        </select>
      </div>

      <!-- Campos para Pessoa Física -->
      <div *ngIf="tipoPessoa$ | async as tipo">
        <ng-container *ngIf="tipo === 'fisica'">
          <div class="form-group">
            <label>Nome</label>
            <input type="text" formControlName="nome">
            <div *ngIf="exibirErro('nome') | async" class="error-message">
              Nome é obrigatório
            </div>
          </div>
          <div class="form-group">
            <label>CPF</label>
            <input type="text" formControlName="cpf">
            <div *ngIf="exibirErro('cpf') | async" class="error-message">
              CPF é obrigatório
            </div>
          </div>
          <!-- Outros campos de pessoa física aqui -->
        </ng-container>

        <!-- Campos para Pessoa Jurídica -->
        <ng-container *ngIf="tipo === 'juridica'">
          <div class="form-group">
            <label>Razão Social</label>
            <input type="text" formControlName="razaoSocial">
            <div *ngIf="exibirErro('razaoSocial') | async" class="error-message">
              Razão Social é obrigatória
            </div>
          </div>
          <div class="form-group">
            <label>CNPJ</label>
            <input type="text" formControlName="cnpj">
            <div *ngIf="exibirErro('cnpj') | async" class="error-message">
              CNPJ é obrigatório
            </div>
          </div>
          <!-- Outros campos de pessoa jurídica aqui -->
        </ng-container>
      </div>

      <button (click)="salvar()" [ngClass]="botaoClasse$ | async" [disabled]="!(formularioValido$ | async)">Salvar</button>
    </form>
  `
})
    <!-- Export Aqui -->
export class CadastroPessoaComponent implements OnInit, OnDestroy {
  formulario: FormGroup;
  private destroy$ = new Subject<void>();
  
  // Streams
  tipoPessoa$: Observable<TipoPessoa>;
  formularioValido$: Observable<boolean>;
  botaoClasse$: Observable<string>;
  
  // Definição das regras de validação por tipo
  private validacoesPorTipo = {
    fisica: [
      { campo: 'nome', validadores: [Validators.required] },
      { campo: 'cpf', validadores: [Validators.required] }
    ],
    juridica: [
      { campo: 'razaoSocial', validadores: [Validators.required] },
      { campo: 'cnpj', validadores: [Validators.required] }
    ]
  };

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    this.inicializarFormulario();
    this.configurarStreams();
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  inicializarFormulario(): void {
    this.formulario = this.fb.group({
      tipoPessoa: ['fisica', Validators.required],
      
      // Campos com validadores iniciais nulos
      nome: ['', null],
      cpf: ['', null],
      razaoSocial: ['', null],
      cnpj: ['', null]
    });
  }

  configurarStreams(): void {
    // Stream do tipo de pessoa
    this.tipoPessoa$ = this.formulario.get('tipoPessoa').valueChanges.pipe(
      startWith(this.formulario.get('tipoPessoa').value as TipoPessoa),
      distinctUntilChanged(),
      tap(tipo => this.atualizarValidadores(tipo as TipoPessoa)),
      takeUntil(this.destroy$)
    );

    // Stream da validade do formulário
    this.formularioValido$ = this.formulario.statusChanges.pipe(
      map(() => this.formulario.valid),
      startWith(this.formulario.valid),
      distinctUntilChanged(),
      takeUntil(this.destroy$)
    );

    // Stream da classe do botão
    this.botaoClasse$ = this.formularioValido$.pipe(
      map(valido => valido ? "botao" : "botao__desabilitado")
    );
  }

  // Função pura para obter validadores por tipo
  private getValidadoresPorTipo(tipo: TipoPessoa): ValidacaoCampo[] {
    return this.validacoesPorTipo[tipo] || [];
  }

  // Função pura para obter campos que devem ter validadores limpos
  private getCamposParaLimpar(tipo: TipoPessoa): string[] {
    return tipo === 'fisica' 
      ? ['razaoSocial', 'cnpj'] 
      : ['nome', 'cpf'];
  }

  // Aplica validadores de forma funcional
  private atualizarValidadores(tipo: TipoPessoa): void {
    // Limpa validadores dos campos não relevantes
    this.getCamposParaLimpar(tipo)
      .forEach(campo => {
        const control = this.formulario.get(campo);
        control.clearValidators();
        control.updateValueAndValidity({emitEvent: false});
      });

    // Aplica validadores aos campos relevantes
    this.getValidadoresPorTipo(tipo)
      .forEach(({campo, validadores}) => {
        const control = this.formulario.get(campo);
        control.setValidators(validadores);
        control.updateValueAndValidity({emitEvent: false});
      });
  }

  // Verifica se deve exibir erro em um campo
  exibirErro(campo: string): Observable<boolean> {
    return this.formulario.statusChanges.pipe(
      startWith(''),
      map(() => {
        const control = this.formulario.get(campo);
        return control.invalid && control.touched;
      })
    );
  }

  // Ação para salvar o formulário
  salvar(): void {
    if (this.formulario.valid) {
      const dadosTratados = this.tratarDadosFormulario(this.formulario.value);
      console.log('Dados a salvar:', dadosTratados);
      // Lógica para salvar os dados
    }
  }

  // Função pura para tratar os dados do formulário
  private tratarDadosFormulario(formValue: any): any {
    const tipo = formValue.tipoPessoa;
    const dadosComuns = { tipoPessoa: tipo };
    
    // Filtra apenas os campos relevantes baseado no tipo
    return tipo === 'fisica'
      ? { ...dadosComuns, nome: formValue.nome, cpf: formValue.cpf }
      : { ...dadosComuns, razaoSocial: formValue.razaoSocial, cnpj: formValue.cnpj };
  }
}

Boa tarde Silas, muito obrigado por responder, mas acreditava que tivesse uma solução mais parecida com o que já foi apresentado ao longo do curso.

De qualquer forma agradeço!