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:
- Criando um CRUD com THF – Parte 01: iniciando o projeto
- Criando um CRUD com THF – Parte 02: listando nossos clientes
- Criando um CRUD com THF – Parte 03: pesquisando pelos nossos clientes
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
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:
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.
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:
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.
Com isso, nossa página já tem a ação de salvar e o nosso primeiro campo configurados.
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:
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.
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.
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.
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.
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:
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>
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>
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>
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:
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>
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>
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>
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>
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>
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:
- Método post do HttpClient
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