Desenvolvimento

14 mai, 2019

Criando um CRUD com THF – Parte 02: listando nossos clientes

Publicidade

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
Criando módulo customers com routing

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
Criando componente customer-list.

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-list.

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.

Onde está a nossa página?

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:

Página exibindo os dados de forma simples.

É 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' }
  ];
  
  ...
Tabela com as colunas name e nickname configuradas.
  • 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');
  }
  
  ...
Tabela com a coluna de e-mail configurada.
  • 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' },
    ]}
  ];
  
  ...
Tabela com a coluna genre configurada.
  • 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' }
    ]}
  ];
  
  ...
Tabela com a coluna status configurada.

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:

Tabelas com os titulos configurados.

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>
Tabela usando loading para melhorar a experiência do usuário.

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.

Tabela com a opção de carregar mais ativada.

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.

Requisição padrão para o backend

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.

Tabela com a paginação funcionando corretamente.

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

***

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