Desenvolvimento

22 mai, 2019

Criando um CRUD com THF – Parte 04: criando um novo cliente

Publicidade

Olá, mundo!

O objetivo deste artigo é criar um formulário para cadastrar novos clientes em nossa aplicação de exemplo, brincar com mais alguns componentes de entrada de dados e conhecer melhor o thf-page-edit.

Caso tenha perdido os artigos anteriores, não deixe de conferir:

Pré-requisitos

Não deixe de ler os artigos anteriores para ter uma compreensão mais completa do projeto, e para continuar do ponto anterior, basta clonar o projeto com o que já foi implementado.

git clone --branch post-3 https://github.com/jhosefmarks/sample-thf-crud-customers.git <sua-pasta-de-trabalho>

Passo 1 – Iniciando nossa página de cadastro

Vamos iniciar criando o componente da nossa nova página.

Estamos usando o sufixo “-form”, pois podemos usar o mesmo componente para criar e atualizar os dados do cliente.

ng g c customers/customer-form

Uma nova pasta será criada, a src/app/customers/customer-form, e dentro dela, os seguintes arquivos também devem ter sido criados após o comando:

  • customer-form.component.css
  • customer-form.component.html
  • customer-form.component.spec.ts
  • customer-form.component.ts
Criando componente customer-form.

Além da nova pasta e dos novos arquivos, o arquivo src/app/customers/customers.module.ts também foi atualizado, adicionando o novo componente dentro do módulo:

Módulo atualizado com o componente customer-form.

Configurando uma nova rota

Abra o arquivo customers-routing.module.ts e inclua a rota para criação de um novo cliente:

Configuração da nova rota

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

// NOVO IMPORT
import { CustomerFormComponent } from './customer-form/customer-form.component';
import { CustomerListComponent } from './customer-list/customer-list.component';

const routes: Routes = [
  { path: '', component: CustomerListComponent },
  // NOVA ROTA
  { path: 'new', component: CustomerFormComponent }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CustomersRoutingModule { }

Bom, se você está acompanhando nossa série e lembra que já fizemos algo parecido na segunda parte, isso ainda não é suficiente para vermos algum resultado visual, portanto, abra o arquivo customer-form.component.html e insira o componente thf-page-edit.

Configuração inicial do thf-page-edit

<thf-page-edit
  t-title="Novo cliente">

</thf-page-edit>

Agora, sim! Com isso já podemos acessar nossa nova página através da url http://localhost:4200/customers/new.

Nova página em http://localhost:4200/customers/new.

Passo 2 – Inserindo o formulário e os primeiros inputs em nossa página

Vamos iniciar apenas com um campo em nosso formulário para entender alguns conceitos e testar algumas propriedades do thf-page-edit antes de nos aprofundarmos na parte visual da nossa página.

Abra o arquivo customer-form.component.ts e adicione a propriedade customer. Ela será responsável por armazenar os dados do nosso futuro cliente.

Poderíamos criar uma propriedade para cada campo (name, email, genre, city, etc), mas é mais prático ter um único objeto para manipular conforme nossa necessidade.

Além disso, também vamos criar um método chamado save.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-customer-form',
  templateUrl: './customer-form.component.html',
  styleUrls: ['./customer-form.component.css']
})
export class CustomerFormComponent implements OnInit {

  // NOVA PROPRIEDADE
  public customer: any = { };

  constructor() { }

  ngOnInit() { }

  // NOVO MÉTODO
  save() {

  }

}

Só com isso nossa página já começa a ganhar super poderes. Confira o botão de salvar aparecendo em nossa página:

Ação de salvar já configurada em nossa página.

Agora, vamos adicionar nosso primeiro campo de entrada na página. Abra o arquivo customer-form.component.html e insira um thf-input:

Adicionando o primeiro thf-input

<thf-page-edit
  t-title="Novo cliente">
  
  <!-- NOVO INPUT -->
  <thf-input class="thf-md-6"
    name="name"
    [(ngModel)]="customer.name"
    t-label="Nome completo">
  </thf-input>

</thf-page-edit>

Dica: caso queira visualizar se o que está sendo preenchido no campo está realmente dentro da nossa propriedade, você pode incluir no seu html a propriedade usando uma expression com o pipe json.

Para visualizar os dados sendo alterados em tempo de desenvolvimento

Com isso, nossa página já tem a ação de salvar e o nosso primeiro campo configurados.

Customer form com input.

Agora já podemos completar nossa função save para pegar o que foi digitado pelo usuário e enviar para o nosso back-end.

...
// NOVO IMPORT
import { HttpClient } from '@angular/common/http';

...

export class CustomerFormComponent implements OnInit {

  // NOVA PROPRIEDADE COM A URL DA API
  private readonly url: string = 'https://sample-customers-api.herokuapp.com/api/thf-samples/v1/people';

  ...

  // INJEÇÃO DO SERVIÇO HTTPCLIENT
  constructor(private httpClient: HttpClient) { }

  ...

  save() {
    // ENVIO DA REQUISIÇÃO DE CRIAÇÃO DE UM NOVO CLIENTE
    this.httpClient.post(this.url, this.customer);
  }

}

Se você apertou o botão de salvar depois de preencher o campo que colocamos em nossa página, vai perceber que nada aconteceu. Isso porque o método post não será disparado se ninguém estiver inscrito nele.

Confira os links de referências no final deste artigo para entender melhor sobre o assunto.

Então, para deixar nosso método mais bacana, vamos nos inscrever no método post e quando tivermos a resposta, nós vamos exibir uma mensagem de sucesso e redirecionar nossa aplicação para nossa página de listagem.

// NOVOS IMPORTS
import { Component, OnDestroy, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

import { Subscription } from 'rxjs';

import { ThfNotificationService } from '@totvs/thf-ui';

...

export class CustomerFormComponent implements OnDestroy, OnInit {

  // NOVA PROPRIEDADE
  private customerSub: Subscription;

  ...

  // INJEÇÃO DOS NOVOS SERVIÇOS
  constructor(
    private thfNotification: ThfNotificationService,
    private router: Router,
    private httpClient: HttpClient) { }

  ...

  ngOnDestroy() {
    this.customerSub.unsubscribe();
  }

  save() {
    this.customerSub = this.httpClient.post(this.url, this.customer).subscribe(() => {
      // EXIBE UMA NOTIFICAÇÃO DE SUCESSO
      this.thfNotification.success('Cliente cadastrado com sucesso');

      // REDIRECIONA A PÁGINA PARA A LISTAGEM DE CLIENTES
      this.router.navigateByUrl('/customers');
    });
  }

}

Abra o projeto e clique em Salvar:

Erro ao salvar customer

Não, o servidor não está maluco. Se você clicar em “Detalhes” na notificação, você verá porque o servidor recusou a requisição.

Mensagem de erro devolvida pelo servidor

Como você pode ver, a propriedade name é obrigatória, assim como e-mail e status. Podemos resolver isso adicionando mais dois campos na tela, mas mesmo assim o usuário ainda vai poder clicar em “Salvar“, enviando um objeto inválido para o servidor.

Vamos desabilitar o botão de salvar quando o nosso formulário for inválido. Para isso, adicionaremos uma tag form em nossa página e deixaremos nosso thf-input como obrigatório através da propriedade t-required.

Adição do form em nossa página

<!-- ADICIONAR A PROPRIEDADE T-DISABLED-SUBMIT -->
<thf-page-edit
  t-title="Novo cliente"
  [t-disable-submit]="formCustomer.invalid">

  <!-- NOVA TAG FORM -->
  <form #formCustomer="ngForm">
    <!-- ADICIONAR PROPRIEDADE T-REQUIRED -->
    <thf-input class="thf-md-6"
      name="name"
      [(ngModel)]="customer.name"
      t-required
      t-label="Nome completo">
    </thf-input>
  </form>

</thf-page-edit>

Já conseguimos melhorar a usabilidade do nosso formulário evitando o envio de requisições desnecessárias com formulário inválido usando a propriedade t-disable-submit do thf-page-edit.

Validação do formulário de customer

A ação de “Salvar” só fica habilitada agora quando o formulário é válido, mas pelo retorno do nosso servidor ainda faltaram dois dados: e-mail e status. Vamos incluir mais dois thf-inputs, um para cada propriedade.

Adicionado thf-input para email e status

<thf-page-edit ... >

  <form #formCustomer="ngForm">

    ...
    
    <!-- NOVOS INPUTS -->
    <thf-input class="thf-md-6"
      name="email"
      [(ngModel)]="customer.email"
      t-required
      t-label="E-mail">
    </thf-input>

    <thf-input class="thf-md-6"
      name="status"
      [(ngModel)]="customer.status"
      t-required
      t-label="Status">
    </thf-input>    
    
  </form>

</thf-page-edit>

Como resultado, já podemos fazer a inclusão de um novo cliente.

Ação de salvar funcionando 100%

Passo 3 – Melhorando a usabilidade do nosso formulário

Apesar de parecer pronto, nosso formulário ainda pode enviar dados inválidos para nosso servidor, e precisamos minimizar isso o máximo possível. Por exemplo, se você colocar um valor no campo de e-mail que não é um e-mail válido, o servidor vai retornar um erro.

Em nosso teste, a única validação usada para e-mail é se o valor contém um caractere @, mas como o foco em nosso exemplo é o THF, não foram tratadas outras situações no back-end.

E-mail inválido

Existem várias formas de validar o e-mail antes de enviar para o servidor. Poderíamos avaliar o e-mail no método save, criar um método que fosse chamado conforme atualizamos o thf-input, mas como queremos usar e aproveitar o que o THF tem de melhor, nós vamos usar o componente thf-email no lugar do thf-input.

Atualização do email para thf-email

<thf-page-edit ... >

  ...
    
    <!-- TROCANDO THF-INPUT PELO THF-EMAIL -->
    <thf-email class="thf-md-6"
      name="email"
      [(ngModel)]="customer.email"
      t-required
      t-label="E-mail">
    </thf-email>
  
  ...

</thf-page-edit>

Testando o novo tipo de campo:

thf-email validando formulário

Agora, outra regra que precisamos atender é do campo status, que só aceita dois valores (Active e Inactive). Com isso, para esse campo, o contexto é semelhante a ligado/desligado.

Nós vamos usar o thf-switch, configurando as propriedades t-label-off e t-label-on.

Atualização do status para thf-switch

<thf-page-edit ... >

  ...
    
    <!-- TROCANDO THF-INPUT PELO THF-SWITCH -->
    <thf-switch class="thf-md-6"
      name="status"
      [(ngModel)]="customer.status"
      t-required
      t-label-off="Inativo"
      t-label-on="Ativo"
      t-label="Status">
    </thf-switch>
  
  ...

</thf-page-edit>
Formulário atualizado com thf-switch

Mas nós ainda teremos um problema aqui, pois hoje, o thf-switch só lida com dois valores: true e false, e precisamos dos valores Active e Inactive.

Para acertar isso vamos refatorar nossa função save para fazer a configuração do valor correto.

Função save refatorada

...

export class CustomerFormComponent implements OnDestroy, OnInit {

  ...

  constructor( ... ) { }

  ...

  save() {
    // FAZ UMA CÓPIA PARA FAZER O TRATAMENTO NECESSÁRIO ANTES DE ENVIAR OS DADOS
    const customer = {...this.customer};

    // APENAS FAZ A CONVERSÃO DOS VALORES
    customer.status = customer.status ? 'Active' : 'Inactive';

    this.customerSub = this.httpClient.post(this.url, customer).subscribe(() => {
      this.thfNotification.success('Cliente cadastrado com sucesso');

      this.router.navigateByUrl('/customers');
    });

}

Note que nesse exemplo estamos apenas transformando um valor, mas poderíamos aproveitar para fazer qualquer validação em nossa página antes de enviar os dados para o servidor e até mesmo cancelar o envio desses dados se alguma regra fosse quebrada.

Agora nosso cadastro está funcional e com uma boa usabilidade, facilitando a vida do usuário e evitando que o mesmo faça requisições desnecessárias.

Passo 4 –  Completando nosso formulários com mais campos

Vamos alterar as classes de Grid System do THF para deixar o formulário melhor e vamos incluir um novo thf-input para a propriedade nickname.

Atualização do Grid System e adição do nickname

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    <!-- ATUALIZAÇÃO DO CLASS -->
    <thf-input class="thf-md-5"
      name="name"
      ... >
    </thf-input>

    <!-- ATUALIZAÇÃO DO CLASS -->    
    <thf-email class="thf-md-5"
      name="email"
      ... >
    </thf-email>

    <!-- ATUALIZAÇÃO DO CLASS -->
    <thf-switch class="thf-md-2"
      name="status" 
      ... >
    </thf-switch>

    <!-- INCLUSÃO DO NICKNAME -->    
    <thf-input class="thf-md-3"
      name="nickname"
      [(ngModel)]="customer.nickname"
      t-label="Apelido">
    </thf-input>
  </form>

</thf-page-edit>
Atualização do Grid System e adição do nickname

Vamos adicionar um campo para o birthdate usando o thf-datepicker:

Adição do thf-datepicker para o campo de nascimento

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    ...

    <!-- INCLUSÃO DO BIRTHDATE -->    
    <thf-datepicker class="thf-md-3"
      name="birthdate"
      [(ngModel)]="customer.birthdate"
      t-label="Nascimento">
    </thf-datepicker>
  </form>

</thf-page-edit>
Página com o campo de nascimento

Agora vamos adicionar um campo para a propriedade genre. Como temos uma lista de valores definidos, nós iremos usar o thf-select dessa vez.

Primeiro vamos adicionar a lista de valores válidos:

Lista de valores para nosso thf-select

...

// IMPORTS
import { ThfNotificationService } from '@totvs/thf-ui/services/thf-notification';
import { ThfSelectOption } from '@totvs/thf-ui/components/thf-field';

...

export class CustomerFormComponent implements OnDestroy, OnInit {

  ...

  public readonly genreOptions: Array<ThfSelectOption> = [
    { label: 'Feminino', value: 'Female' },
    { label: 'Masculino', value: 'Male' },
    { label: 'Outros', value: 'Other' }
  ];
    
  ...

  constructor( ... ) { }
    
  ...

}

E agora vamos adicionar o componente na página:

Adicinando o thf-select na página

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    ...

    <!-- INCLUSÃO DO GENRE -->    
    <thf-select class="thf-md-3"
      name="genre"
      [(ngModel)]="customer.genre"
      t-label="Gênero"
      [t-options]="genreOptions">
    </thf-select>
  </form>

</thf-page-edit>

Como resultado, temos uma lista com as opções disponíveis:

Página com uma lista de seleção para o gênero

E para o primeiro grupo de dados no nosso formulário, vamos finalizar com mais um thf-input para a propriedade nationality.

Adicionado campo nationality

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    ...

    <!-- INCLUSÃO DO GENRE -->    
    <thf-input class="thf-md-3"
      name="nationality"
      [(ngModel)]="customer.nationality"
      t-label="Nacionalidade">
    </thf-input>
  </form>

</thf-page-edit>
Página com o campo de nacionalidade

Um componente simples, mas visualmente bacana e que serve para agrupar campos de entrada, é o thf-divider, e é o que vamos usar agora.

Uso do thf-divider para melhorar o layout do formulário

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    <thf-divider class="thf-sm-12" t-label="Dados pessoais"></thf-divider>
    
    ...
    
  </form>

</thf-page-edit>
Formulário com o primeiro thf-divider

Agora podemos acrescentar mais alguns grupos de informações em nosso formulário.

Primeiro, o grupo de filiação:

Adicionado 1 thf-divider e mais 2 thf-inputs

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    ...

    <thf-divider class="thf-sm-12" t-label="Filiação"></thf-divider>

    <thf-input class="thf-md-5"
      name="mother"
      [(ngModel)]="customer.mother"
      t-label="Nome da mãe">
    </thf-input>

    <thf-input class="thf-md-5"
      name="father"
      [(ngModel)]="customer.father"
      t-label="Nome do pai">
    </thf-input>
  </form>

</thf-page-edit>
Adicionado a página o grupo de filiação

E vamos finalizar com o grupo destinado ao endereço:

Adicionado grupo para endereço

<thf-page-edit ... >

  <form #formCustomer="ngForm">
    ...

    <thf-divider class="thf-sm-12" t-label="Endereço"></thf-divider>

    <thf-input class="thf-md-6"
      name="street"
      [(ngModel)]="customer.street"
      t-label="Rua">
    </thf-input>

    <thf-input class="thf-md-3"
      name="city"
      [(ngModel)]="customer.city"
      t-label="Cidade/Estado">
    </thf-input>

    <thf-input class="thf-md-3"
      name="country"
      [(ngModel)]="customer.country"
      t-label="País">
    </thf-input>
  </form>

</thf-page-edit>
Formulário completo

Com isso, nós finalizamos o nosso humilde formulário. Sim, poderíamos adotar outras estratégias aqui, colocar um thf-lookup ali, mas a ideia agora é focar no básico e entender a estrutura de um CRUD.

Teremos outras oportunidades para explorar componentes mais complexos.

Passo 5 –  Melhorando a navegação e ligando tudo

Agora, vamos só melhorar a navegação entre o nosso formulário de cadastro e nossa listagem de clientes. Dessa forma, o usuário não precisa ficar trocando de página pela URL.

Vamos adicionar uma função em nossa página para voltar para a listagem:

Função cancel

...

export class CustomerFormComponent implements OnDestroy, OnInit {
    
  ...

  constructor( ... ) { }
    
  ngOnDestroy() {
    // PEQUENO REFACTORY PARA EVITAR ERRO CASO O FORMULÁRIO NÃO TENHA SIDO SUBMETIDO NENHUMA VEZ
    if (this.customerSub) {
      this.customerSub.unsubscribe();
    }
  }

  ngOnInit() { }

  // NOVO MÉTODO PARA VOLTAR A PÁGINA DE LISTAGEM
  cancel() {
    this.router.navigateByUrl('/customers');
  }

  ...

}

O que? Só isso para incluir a ação de voltar para a página de listagem? Sim. Viu como THF te ajuda naquilo que é repetitivo?

Fizemos um pequeno refactory no método ngOnDestroy para evitar erros quando o formulário não sofre nenhuma submissão.

Com isso, já podemos ir do formulário para a página de listagem sem usar o menu ou a URL.

E agora vamos à nossa página de listagem e vamos incluir uma ação para cadastrar um novo cliente. Para isso, vamos editar os arquivos customer-list.component.ts e customer-list.component.html.

Adicionada a função onNewCustomer e adicionada o objeto actions

...

// NOVO IMPORT
import { Router } from '@angular/router';

...

// NOVO IMPORT
import { ThfPageFilter, ThfPageAction } from '@totvs/thf-ui/components/thf-page';

...

export class CustomerListComponent implements OnInit, OnDestroy {

  ...

  // NOVA PROPRIEDADE PARA ADICIONAR AÇÃO NA PÁGINA DE LISTAGEM
  public readonly actions: Array<ThfPageAction> = [
    { action: this.onNewCustomer.bind(this), label: 'Cadastrar', icon: 'thf-icon-user-add' }
  ];

  ...

  // NOVA INJEÇÃO
  constructor(private httpClient: HttpClient, private router: Router) { }
    
  ...
    
  // MÉTODO QUE SERÁ RESPONSÁVEL POR ABRIR NOSSA PÁGINA DE CRIAÇÃO
  private onNewCustomer() {
    this.router.navigateByUrl('/customers/new');
  }
  
  ...

}

Pagina de listagem configurada com a nova ação

<!-- ADICIONADA A PROPRIEDADE T-ACTIONS -->
<thf-page-list
  t-title="Listagem de clientes"
  [t-actions]="actions"
  [t-disclaimer-group]="disclaimerGroup"
  [t-filter]="filter">
  
  ...
  
</thf-page-list>
Navegação completa entre as páginas

Chegamos ao fim de mais um artigo. Vejo vocês novamente em breve!

E agora?

Com isso, já dá pra fazer muita coisa. Já vimos como montar um formulário. Agora, você já pode explorar outros componentes para criar layouts mais complexos, assim como outros controles de entrada de dados.

Ainda temos mais algumas coisas a serem feitas para finalizar nosso CRUD e é nisso que vamos continuar focando até o final dessa série.

Você pode encontrar os fontes do projeto no GitHub.

Referências e dicas de leitura

Documentação oficial do THF:

Documentação oficial do Angular:

Outras referências:

***

Artigo original publicado no TOTVS Developers e republicado com autorização do autor, Jhosef Marks: https://medium.com/totvsdevelopers/criando-um-crud-com-thf-criando-um-novo-cliente-c0c519502e92