How to Create a Reusable Component in Angular

Question:

I have a system where all modules have a standardized record listing screen with paging. I adapted a paging code I found on the Internet to do paging on demand. However, rather than repeating this code on all the list screens I would like to create a component to only be called on those screens. I do this as follows:

Adapted service that does the paging

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class PagerService {

  getPager(totalItems: number, currentPage: number = 0, pageSize: number = 10) {
    // calculate total pages
    let totalPages = Math.ceil(totalItems / pageSize);

    // ensure current page isn't out of range
    if (currentPage < 0) {
      currentPage = 0;
    } else if (currentPage > totalPages) {
      currentPage = totalPages;
    }

    let startPage: number, endPage: number;
    if (totalPages <= 10) {
      // less than 10 total pages so show all
      startPage = 1;
      endPage = totalPages;
    } else {
      // more than 10 total pages so calculate start and end pages
      if (currentPage <= 5) {
        startPage = 1;
        endPage = 10;
      } else if (currentPage + 3 >= totalPages) {
        startPage = totalPages - 9;
        endPage = totalPages;
      } else {
        startPage = currentPage - 4;
        endPage = currentPage + 3;
      }
    }

    // calculate start and end item indexes
    let startIndex = (currentPage) * pageSize;
    let endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);

    // create an array of pages to ng-repeat in the pager control
    let pages = Array.from(Array((endPage + 1) - startPage).keys()).map(i => startPage + i);

    // return object with all pager properties required by the view
    return {
      totalItems: totalItems,
      currentPage: currentPage,
      pageSize: pageSize,
      totalPages: totalPages,
      startPage: startPage,
      endPage: endPage,
      startIndex: startIndex,
      endIndex: endIndex,
      pages: pages
    };
  }
}

Listing Component

import { AppSettings } from './../../settings/app-settings';
import { PageEquipamento } from './../entity/page-equipamento';
import { Component, OnInit } from '@angular/core';
import AppHelper from '../../helper/AppHelper';
import { LoadingHugeComponent } from '../../loading/huge/loading-huge.component';
import { Title } from '@angular/platform-browser';
import { SharedDataService } from '../../services/SharedDataService';
import { Router, ActivatedRoute } from '@angular/router';
import { EquipamentosService } from '../services/equipamentos.service';
import { ToastrService } from 'ngx-toastr';
import { environment } from '../../../environments/environment';
import { Equipamento } from '../../entity/equipamento';
import { PagerService } from '../../services/pager.service';
import { Observable, of } from 'rxjs';
declare var $: any;

@Component({
  selector: 'app-listar-equipamentos',
  templateUrl: './listar-equipamentos.component.html',
  styleUrls: ['./listar-equipamentos.component.css'],
  preserveWhitespaces: true
})
export class ListarEquipamentosComponent implements OnInit {

  //Rotas
  private appHelper: AppHelper = new AppHelper();
  public loadingHugeComponent = LoadingHugeComponent;
  public listUrls;
  //Equipamento Selecionado
  public indexEquipamentoSelecionado: number;
  public nomeSelecionado: string;
  //Paginação
  pager: any = {};
  pagedItems: any[];
  //Page Equipamentos  
  private subPageEquipamento;
  public pageEquipamento: PageEquipamento = new PageEquipamento();


  constructor(private titleService: Title, private sharedDataService: SharedDataService,
    private router: Router,
    private route: ActivatedRoute,
    private equipamentosService: EquipamentosService,
    private toastrService: ToastrService,
    private pagerService: PagerService
  ) {
    const allowed = ['listarEquipamentos', 'gravarEquipamento'];
    this.listUrls = this.appHelper.urlNotAllowed(allowed, environment.urls);

  }

  ngOnInit() {
    this.loadRegistrosPaginados();
  }

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

  confirmaExcluirEquipamento(i: number) {
    this.indexEquipamentoSelecionado = i;
    this.nomeSelecionado = this.pageEquipamento.listaEquipamentos[i].nome;
    $('#modalLista').modal('show');
  }

  excluirEquipamento() {
    setTimeout(() => {
      this.equipamentosService.excluirEquipamento(this.pageEquipamento.listaEquipamentos[this.indexEquipamentoSelecionado].id).subscribe(
        resp => {
          this.toastrService.info(this.pageEquipamento.listaEquipamentos[this.indexEquipamentoSelecionado].nome, ' Excluido com sucesso!');
          this.pageEquipamento.listaEquipamentos.splice(this.indexEquipamentoSelecionado, 1);
          this.loadRegistrosPaginados();
        }, error => {
          this.toastrService.error('Não foi possível excluir');
        }
      );
    }, 900);
  }

  //Carrega os registros paginados
  loadRegistrosPaginados() {
    this.subPageEquipamento = this.equipamentosService.getEquipamentosPage(AppSettings.RESGISTROS_POR_PAGINA)
      .subscribe(resp => {
        this.pageEquipamento.listaEquipamentos = resp.content;
        this.pageEquipamento.totalElements = resp.totalElements;
        //Gera o HTML da Paginação
        this.pager = this.pagerService.getPager(this.pageEquipamento.totalElements, 1, AppSettings.RESGISTROS_POR_PAGINA);
        this.pagedItems = this.pageEquipamento.listaEquipamentos.slice(this.pager.startIndex, this.pager.endIndex + 1);
      });
  }

  //Método chamado pela paginação
  setPage(page: number) {
    this.subPageEquipamento = this.equipamentosService.getEquipamentosPage(AppSettings.RESGISTROS_POR_PAGINA, page - 1)
      .subscribe(resp => {
        this.pageEquipamento.listaEquipamentos = resp.content;
        this.pageEquipamento.totalElements = resp.totalElements
      });

    this.pager = this.pagerService.getPager(this.pageEquipamento.totalElements, page, AppSettings.RESGISTROS_POR_PAGINA);
    this.pagedItems = this.pageEquipamento.listaEquipamentos.slice(this.pager.startIndex, this.pager.endIndex + 1);
  }

}

Page HTML

  <!--Paginação-->
  <div *ngIf="!(pageProposta.listaEquipamentos?.length===0)">
    <div class="container">
      <div class="text-center">
        <!-- items being paged -->
        <div *ngFor="let item of pagedItems">{{item.name}}</div>

        <!-- pager -->
        <ul *ngIf="pager.pages && pager.pages.length" class="pagination">
          <li [ngClass]="{disabled:pager.currentPage === 1}">
            <a [routerLink]="" (click)="setPage(1)">Primeiro</a>
          </li>
          <li *ngFor="let page of pager.pages" [ngClass]="{active:pager.currentPage === page}">
            <a [routerLink]="" (click)="setPage(page)">{{page}}</a>
          </li>
          <li [ngClass]="{disabled:pager.currentPage === pager.totalPages}">
            <a [routerLink]="" (click)="setPage(pager.totalPages)">Último</a>
          </li>
        </ul>
      </div>
    </div>
  </div>

Using this logic is it possible to adapt it to create a reusable component for all list screens in the system?

Thanks

Answer:

Andrew, I advise you to create a module (shared) and within that module you create all components that will be shared with all or most of the system, for example, in my application I use the shared module to share error validation messages.

It would work more or less like this:

shared module has list-records and validate-form components

Then, to call in other components, you just use the selector of your list-records component.

Note: don't forget to export the module and import if necessary!

Scroll to Top