Olá, mundo!
O objetivo deste artigo é criar uma tela com a lista dos clientes cadastrados em nosso back-end.
Pré-requisitos
Ler o artigo anterior e seguir o passo a passo seria ótimo, mas se não estiver a fim, basta clonar o projeto de onde paramos no artigo anterior e continuar a partir do que já foi implementado.
git clone --branch post-1 https://github.com/jhosefmarks/sample-thf-crud-customers.git <sua-pasta-de-trabalho>
Passo 1 – Criando e configurando o módulo customers
Criaremos um módulo para nossas telas. Para isso, execute o comando abaixo:
ng g m customers --routing
Será criada a pasta src/app/customers com esses dois arquivos:
- customers.module.ts
- customers-routing.module.ts
Agora vamos criar o componente da nossa página de listagem.
ng g c customers/customer-list
Uma nova pasta será criada, a src/app/customers/customer-list, e dentro dela, os seguintes arquivos também devem ser criados após o comando:
- customer-list.component.css
- customer-list.component.html
- customer-list.component.spec.ts
- customer-list.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:
Finalizando a configuração do módulo customers
Abra o arquivo customers.module.ts e inclua o módulo SharedModule. Com isso, nós teremos acesso aos componentes do THF, já que o SharedModule já faz a importação para todo o projeto.
Importação do SharedModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// IMPORTAÇÃO DO SHARED MODULE
import { SharedModule } from './../shared/shared.module';
import { CustomersRoutingModule } from './customers-routing.module';
import { CustomerListComponent } from './customer-list/customer-list.component';
@NgModule({
declarations: [CustomerListComponent],
imports: [
CommonModule,
SharedModule, // IMPORTAÇÃO DO SHARED MODULE
CustomersRoutingModule
]
})
export class CustomersModule { }
Configurando as rotas
Vamos incluir uma rota no módulo principal usando Lazy Loading. Faça isso no arquivo app-routing.module.ts.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{ path: 'home', loadChildren: './home/home.module#HomeModule' },
// ROTA PRINCIPAL PARA O MÓDULO CUSTOMERS
{ path: 'customers', loadChildren: './customers/customers.module#CustomersModule' },
{ path: '', redirectTo: '/home', pathMatch: 'full'}
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule { }
Agora, vamos configurar a rota padrão do módulo customers. Abra o arquivo customers-routing.module.ts e inclua a rota padrão:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// IMPORT DO COMPONENTE CARREGADO NA ROTA PADRÃO
import { CustomerListComponent } from './customer-list/customer-list.component';
const routes: Routes = [
// ROTA PADRÃO DO MÓDULO CUSTOMERS
{ path: '', component: CustomerListComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CustomersRoutingModule { }
Até agora só configuramos a rota, mas com isso já é possível acessar o link http://localhost:4200/customers e ver o resultado.
Incluindo um item no menu da aplicação
Pois é, faltou ainda incluir um item no menu para facilitar o acesso e iniciar a configuração da nossa página.
Vamos iniciar pelo item do menu. Para isso, vá até o arquivo app.component.ts e inclua o item de menu para a nossa página que listará os clientes:
import { Component } from '@angular/core';
import { ThfMenuItem } from '@totvs/thf-ui';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
readonly menus: Array<ThfMenuItem> = [
{ label: 'Home', link: '/home' },
// ITEM DE MENU PARA A PÁGINA CUSTOMERS
{ label: 'Customers', link: '/customers' }
];
}
Passo 2 – Iniciando nossa página de listagem
Bom, agora que já temos o módulo pronto e as rotas configuradas, está na hora de focar na nossa página.
Antes de iniciar, é importante saber que o THF tem alguns componentes que facilitam a criação de páginas, desde o mais básico até o mais complexo e dinâmico.
O componente que vamos usar neste exemplo é o thf-page-list. Outros componentes desse tipo serão abordados conforme nosso exemplo for evoluindo e em futuros artigos desta série.
Abra o arquivo customer-list.component.html e adicione o componente thf-page-list. Já vamos aproveitar o momento para configurar o título da nossa página para “Listagem de clientes“.
<thf-page-list
t-title="Listagem de clientes">
</thf-page-list>
Com isso, já teremos o seguinte resultado:
Passo 3 – Exibindo os dados dos clientes
Para esta série, já deixei um serviço com os dados que usaremos em nosso exemplo:
Primeiro vamos criar a lógica que faz a requisição para o servidor que pega a lista de clientes que vamos exibir em nossa página.
Para isso, abra o arquivo customer-list.component.ts.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-customer-list',
templateUrl: './customer-list.component.html',
styleUrls: ['./customer-list.component.css']
})
export class CustomerListComponent implements OnInit, OnDestroy {
// Url do servidor de exemplo
private readonly url: string = 'https://sample-customers-api.herokuapp.com/api/thf-samples/v1/people';
private customersSub: Subscription;
// Nossa lista de clientes
public customers: Array<any> = [];
constructor(private httpClient: HttpClient) { }
ngOnInit() {
// Faz a requisição para o servidor
this.customersSub = this.httpClient.get(this.url)
.subscribe((response: { hasNext: boolean, items: Array<any>}) => {
this.customers = response.items;
});
}
ngOnDestroy() {
this.customersSub.unsubscribe();
}
}
Entendendo a lógica, no ngOnInit nós apenas fazemos a requisição e armazenamos na propriedade customers a lista de clientes.
O servidor devolve um objeto com a lista de clientes (items) e uma propriedade que informa se existem mais dados (hasNext).
Agora, no arquivo customer-list.component.html, vamos incluir uma tabela (thf-table) e exibir os dados do nosso servidor:
<thf-page-list
t-title="Listagem de clientes">
<!-- Tabela para exibição dos dados -->
<thf-table
[t-items]="customers">
</thf-table>
</thf-page-list>
Com isso, já teremos nossa lista de clientes sendo exibida em nossa página:
É possível perceber que o thf-table já cuidou da renderização dos dados de forma automática. Se os dados que você quer exibir já vierem formatados e com o nome das propriedades configuradas, não é necessário fazer mais nada.
E com isso, você já tem uma tela de listagem básica com o THF.
Passo 4 – Melhorando a apresentação dos dados
Mas é lógico que vamos melhorar o visual da nossa tabela. Para isso, nós vamos configurar a aparência de cada coluna que queremos exibir.
Vamos criar uma propriedade chamada columns, que é do tipo Array<ThfTableColumn>
, ou seja, uma lista de definições para as colunas que queremos exibir.
Abra o arquivo customer-list.component.ts:
...
import { ThfTableColumn } from '@totvs/thf-ui/components/thf-table';
...
export class CustomerListComponent implements OnInit, OnDestroy {
...
public readonly columns: Array<ThfTableColumn> = [
// Definição das colunas
];
...
}
E no arquivo customer-list.component.html vamos passar essa propriedade para a nossa tabela.
<thf-page-list
t-title="Listagem de clientes">
<thf-table
[t-columns]="columns"
[t-items]="customers">
</thf-table>
</thf-page-list>
Configurando as colunas
O nosso back-end está devolvendo as seguintes propriedades:
- id: é o identificador do nosso registro e não temos interesse de exibir essa informação para o usuário, então podemos deixar de fora da configuração
- name e nickname: não têm muito segredo. São apenas textos simples – basta adicionar na lista de colunas
...
public readonly columns: Array<ThfTableColumn> = [
{ property: 'name' },
{ property: 'nickname' }
];
...
- e-mail: para a coluna de e-mail, nós iremos configurá-lo como um link e vamos passar uma função de envio de e-mail
...
public readonly columns: Array<ThfTableColumn> = [
{ property: 'name' },
{ property: 'nickname' },
{ property: 'email', type: 'link', action: this.sendMail.bind(this) }
];
...
private sendMail(email, customer) {
const body = `Olá ${customer.name}, gostariamos de agradecer seu contato.`;
const subject = 'Contato';
window.open(`mailto:${email}?subject=${subject}&body=${body}`, '_self');
}
...
- birthdate: data de nascimento do nosso cliente. Vamos configurar o tipo da coluna, o formato que queremos exibir e um tamanho fixo, já que data sempre terá o mesmo tamanho
...
public readonly columns: Array<ThfTableColumn> = [
...
{ property: 'birthdate', type: 'date', format: 'dd/MM/yyyy', width: '100px' }
];
...
- genre: para o gênero, só usaremos o tipo de coluna subtitle
...
public readonly columns: Array<ThfTableColumn> = [
...
{ property: 'genre', type: 'subtitle', width: '80px', subtitles: [
{ value: 'Female', color: 'color-05', content: 'F', label: 'Feminino' },
{ value: 'Male', color: 'color-02', content: 'M', label: 'Masculino' },
{ value: 'Other', color: 'color-08', content: 'O', label: 'Outros' },
]}
];
...
- city: assim como o name e o nickname, é apenas texto simples
...
public readonly columns: Array<ThfTableColumn> = [
...
{ property: 'city' }
];
...
- status: para essa coluna, vamos usar o tipo label, que dá um aspecto bem mais bacana para a coluna
...
public readonly columns: Array<ThfTableColumn> = [
...
{ property: 'status', type: 'label', labels: [
{ value: 'Active', color: 'success', label: 'Ativo' },
{ value: 'Inactive', color: 'danger', label: 'Inativo' }
]}
];
...
Continuando as configuração das colunas, faltou apenas definir o label das colunas.
...
public readonly columns: Array<ThfTableColumn> = [
{ property: 'name', label: 'Nome' },
{ property: 'nickname', label: 'Apelido' },
{ property: 'email', label: 'E-mail', type: 'link', action: this.sendMail.bind(this) },
{ property: 'birthdate', label: 'Nascimento', type: 'date', format: 'dd/MM/yyyy', width: '100px' },
{ property: 'genre', label: 'Gênero', type: 'subtitle', width: '80px', subtitles: [
{ value: 'Female', color: 'color-05', content: 'F', label: 'Feminino' },
{ value: 'Male', color: 'color-02', content: 'M', label: 'Masculino' },
{ value: 'Other', color: 'color-08', content: 'O', label: 'Outros' },
]},
{ property: 'city', label: 'Cidade' },
{ property: 'status', type: 'label', labels: [
{ value: 'Active', color: 'success', label: 'Ativo' },
{ value: 'Inactive', color: 'danger', label: 'Inativo' }
]}
];
...
Com isso, nossas colunas ficaram com esse aspecto:
Adicionando ordenação às colunas
Para adicionar ordenação à tabela, basta incluir a propriedade t-sort.
<thf-page-list
t-title="Listagem de clientes">
<thf-table
t-sort="true"
[t-columns]="columns"
[t-items]="customers">
</thf-table>
</thf-page-list>
Passo 5 – Adicionando paginação e loading
Para que o usuário tenha uma experiência melhor durante a carga dos dados, é possível usar a propriedade t-loading.
Mudaremos a tabela para iniciar com o status de carregando ativado e vamos desativar esse status apenas quando os dados forem carregados.
Crie a propriedade loading, inicializando com true e, ao final da carga dos dados, atribua false.
...
export class CustomerListComponent implements OnInit, OnDestroy {
...
// INICIALIZA COM TRUE PARA A TABELA APARECER COM LOADING ATIVADO
public loading: boolean = true;
constructor(private httpClient: HttpClient) { }
ngOnInit() {
this.customersSub = this.httpClient.get(this.url)
.subscribe((response: { hasNext: boolean, items: Array<any>}) => {
this.customers = response.items;
this.loading = false; // FINAL DA CARGA, DESATIVA O LOADING
});
}
...
}
Não podemos esquecer de adicionar a propriedade t-loading
no thf-table.
<thf-page-list
t-title="Listagem de clientes">
<thf-table
t-sort="true"
[t-columns]="columns"
[t-items]="customers"
[t-loading]="loading">
</thf-table>
</thf-page-list>
Vocês se lembram do hasNext que o nosso servidor retorna? Vamos usá-lo para trabalhar com a funcionalidade de paginação.
Primeiro, criaremos uma propriedade chamada hasNext, do tipo boolean e a função loadData para carregar os dados.
O conteúdo dela é semelhante ao que está dentro da função ngOnInit e faremos com que o ngOnInit passe a usar essa função. Dessa forma, vamos reaproveitar código.
...
export class CustomerListComponent implements OnInit, OnDestroy {
...
// NOVA PROPRIEDADE
public hasNext: boolean = false;
constructor(private httpClient: HttpClient) { }
ngOnInit() {
// NGONINIT PASSA A USAR A FUNÇÃO LOADDATA()
this.loadData();
}
...
// NOVA FUNÇÃO PARA BUSCAR OS DADOS
public loadData() {
this.loading = true;
this.customersSub = this.httpClient.get(this.url)
.subscribe((response: { hasNext: boolean, items: Array<any>}) => {
this.customers = response.items;
this.hasNext = response.hasNext;
this.loading = false;
});
}
}
No thf-table, passe a chamar a função loadData no evento t-show-more e associe a propriedade t-show-more-disabled com a propriedade hasNext.
<thf-page-list
t-title="Listagem de clientes">
<thf-table
t-sort="true"
[t-columns]="columns"
[t-items]="customers"
[t-loading]="loading"
[t-show-more-disabled]="!hasNext"
(t-show-more)="loadData()">
</thf-table>
</thf-page-list>
Com isso, habilitamos a função de carregar mais resultados da tabela.
Se você apertar o botão que aparece abaixo da tabela, você não vai ver nenhum resultado surpreendente – isso porque nós continuamos fazendo a mesma requisição para o servidor.
Precisamos incluir na url qual página queremos carregar.
Para isso, vamos atualizar no método que faz a carga dos dados.
...
export class CustomerListComponent implements OnInit, OnDestroy {
...
// NOVA PROPRIEDADE PARA CONTROLAR QUAL PÁGINA QUEREMO CARREGAR
private page: number = 1;
constructor(private httpClient: HttpClient) { }
...
public loadData() {
// URL COM PAGINAÇÃO
const urlWithPagination = `${this.url}?page=${this.page}`;
this.loading = true;
this.customersSub = this.httpClient.get(urlWithPagination)
.subscribe((response: { hasNext: boolean, items: Array<any>}) => {
// PASSAMOS AGORA A CONCATENAR O RESULTADO COM O QUE JÁ FOI CARREGADO ANTERIORMENTE
this.customers = [...this.customers, ...response.items];
this.hasNext = response.hasNext;
// ATUALIZAMOS O NÚMERO DA PÁGINA PARA A PRÓXIMA REQUISIÇÃO
this.page++;
this.loading = false;
});
}
}
Com a função loadData ajustada, agora temos a funcionalidade de paginação funcionando do jeito que deveria.
E agora?
Já vimos como fazer a listagem dos nossos clientes. No próximo artigo vamos incluir a função de pesquisa e aprender a usar mais alguns componentes do THF.
Como a ideia é ser o mais didático possível aqui, o código que faz a requisição foi mantido junto com o componente. Ao invés de ser separado em um serviço, vamos passar por um refactory no futuro que vai alterar isso.
Você pode pegar os fontes atualizados no repositório do GithHub – fiz o possível para commitar passo a passo, para que seja possível acompanhar a evolução do que fizemos até aqui.
Referências e dicas de leitura
- Documentação oficial do thf-page-list
- Documentação oficial do thf-table
- Angular.io: CLI – ng generate
- Angular.io: Lazy Loading Feature Modules
- Angular.io: HttpClient
- Curso de Angular da Loiane
***
Artigo original publicado no TOTVS Developers e republicado com autorização do autor, Jhosef Marks: https://medium.com/totvsdevelopers/criando-um-crud-com-thf-listando-nossos-clientes-cfd80b9d8b00