Skip to content
Snippets Groups Projects
pagination.component.ts 12.9 KiB
Newer Older
import {
Giuseppe Digilio's avatar
Giuseppe Digilio committed
  ChangeDetectionStrategy, ChangeDetectorRef,
  Component,
  EventEmitter,
  Input,
  OnDestroy,
  OnInit,
} from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router';

import { Subscription } from 'rxjs/Subscription';
import { isNumeric } from 'rxjs/util/isNumeric';

import { Observable } from 'rxjs/Observable';

import { HostWindowService } from '../host-window.service';
import { HostWindowState } from '../host-window.reducer';
import { PaginationComponentOptions } from './pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
Lotte Hofstede's avatar
Lotte Hofstede committed
import { hasValue, isNotEmpty } from '../empty.util';
Giuseppe Digilio's avatar
Giuseppe Digilio committed
import { PageInfo } from '../../core/shared/page-info.model';

/**
 * The default pagination controls component.
 */
@Component({
  exportAs: 'paginationComponent',
  selector: 'ds-pagination',
  styleUrls: ['pagination.component.scss'],
  templateUrl: 'pagination.component.html',
  changeDetection: ChangeDetectionStrategy.Default,
  encapsulation: ViewEncapsulation.Emulated
export class PaginationComponent implements OnDestroy, OnInit {
  /**
   * Number of items in collection.
   */
  @Input() collectionSize: number;

Giuseppe Digilio's avatar
Giuseppe Digilio committed
  /**
   * Page state of a Remote paginated objects.
   */
  @Input() pageInfoState: Observable<PageInfo> = undefined;

  /**
   * Configuration for the NgbPagination component.
   */
  @Input() paginationOptions: PaginationComponentOptions;

  /**
   * Sort configuration for this component.
   */
  @Input() sortOptions: SortOptions;

  /**
   * An event fired when the page is changed.
   * Event's payload equals to the newly selected page.
   */
  @Output() pageChange: EventEmitter<number> = new EventEmitter<number>();

  /**
   * An event fired when the page wsize is changed.
   * Event's payload equals to the newly selected page size.
   */
  @Output() pageSizeChange: EventEmitter<number> = new EventEmitter<number>();

  /**
   * An event fired when the sort direction is changed.
   * Event's payload equals to the newly selected sort direction.
   */
  @Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>();

  /**
   * An event fired when the sort field is changed.
   * Event's payload equals to the newly selected sort field.
   */
  @Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();

  /**
   * An event fired when the sort field is changed.
   * Event's payload equals to the newly selected sort field.
   */
  @Output() paginationChange: EventEmitter<any> = new EventEmitter<any>();

  /**
   * Option for hiding the gear
   */
  @Input() public hideGear = false;

  /**
   * Option for hiding the pager when there is less than 2 pages
   */
  @Input() public hidePagerWhenSinglePage = true;

  /**
   * Current page.
   */
Giuseppe Digilio's avatar
Giuseppe Digilio committed
  public currentPage;

  /**
   * Current page in the state of a Remote paginated objects.
   */
  public currentPageState: number = undefined;
  public currentQueryParams: any;

  /**
   * An observable of HostWindowState type
   */
  public hostWindow: Observable<HostWindowState>;

  /**
   * ID for the pagination instance. Only useful if you wish to
   * have more than once instance at a time in a given component.
   */
  private id: string;

  /**
   * A boolean that indicate if is an extra small devices viewport.
   */
  public isXs: boolean;

  /**
   * Number of items per page.
   */
Giuseppe Digilio's avatar
Giuseppe Digilio committed
  public pageSize;

  /**
   * Declare SortDirection enumeration to use it in the template
   */
Giuseppe Digilio's avatar
Giuseppe Digilio committed
  public sortDirections = SortDirection;

  /**
   * A number array that represents options for a context pagination limit.
   */
  private pageSizeOptions: number[];

  /**
   * Direction in which to sort: ascending or descending
   */
  public sortDirection: SortDirection = SortDirection.Ascending;

  /**
   * Name of the field that's used to sort by
   */
  public sortField = 'id';

  /**
   * Array to track all subscriptions and unsubscribe them onDestroy
   * @type {Array}
   */
  private subs: Subscription[] = [];
  /**
   * Method provided by Angular. Invoked after the constructor.
   */
  ngOnInit() {
    this.subs.push(this.hostWindowService.isXs()
      .subscribe((status: boolean) => {
        this.isXs = status;
Giuseppe Digilio's avatar
Giuseppe Digilio committed
        this.cdRef.markForCheck();
      }));
    this.checkConfig(this.paginationOptions);
    this.initializeConfig();
    // Listen to changes
    this.subs.push(this.route.queryParams
      .subscribe((queryParams) => {
        if (this.isEmptyPaginationParams(queryParams)) {
          this.initializeConfig();
        } else {
          this.currentQueryParams = queryParams;
          const fixedProperties = this.validateParams(queryParams);
          if (isNotEmpty(fixedProperties)) {
            this.fixRoute(fixedProperties);
          } else {
            this.setFields();
  private fixRoute(fixedProperties) {
    this.updateRoute(fixedProperties);
  }

  /**
   * Method provided by Angular. Invoked when the instance is destroyed.
   */
  ngOnDestroy() {
    this.subs
      .filter((sub) => hasValue(sub))
      .forEach((sub) => sub.unsubscribe());
  }
  /**
   * Initializes all default variables
   */
  private initializeConfig() {
    // Set initial values
    this.id = this.paginationOptions.id || null;
    this.pageSizeOptions = this.paginationOptions.pageSizeOptions;
    this.currentPage = this.paginationOptions.currentPage;
    this.pageSize = this.paginationOptions.pageSize;
    this.sortDirection = this.sortOptions.direction;
    this.sortField = this.sortOptions.field;
    this.currentQueryParams = {
Lotte Hofstede's avatar
Lotte Hofstede committed
      pageId: this.id,
      page: this.currentPage,
      pageSize: this.pageSize,
      sortDirection: this.sortDirection,
      sortField: this.sortField
    };
  /**
   * @param route
   *    Route is a singleton service provided by Angular.
   * @param router
   *    Router is a singleton service provided by Angular.
   */
  constructor(private cdRef: ChangeDetectorRef,
              private route: ActivatedRoute,
              private router: Router,
              public hostWindowService: HostWindowService) {
   * Method to change the route to the given page
   *
   * @param page
   *    The page being navigated to.
   */
  public doPageChange(page: number) {
    this.updateRoute({ page: page });
  }

  /**
   * Method to change the route to the given page size
   *
   * @param pageSize
   *    The page size being navigated to.
   */
  public doPageSizeChange(pageSize: number) {
    this.updateRoute({ page: 1, pageSize: pageSize });
  }

  /**
   * Method to change the route to the given sort direction
   *
   * @param sortDirection
   *    The sort direction being navigated to.
   */
  public doSortDirectionChange(sortDirection: SortDirection) {
    this.updateRoute({ page: 1, sortDirection: sortDirection });
  }

  /**
   * Method to change the route to the given sort field
   *
   * @param sortField
   *    The sort field being navigated to.
   */
  public doSortFieldChange(field: string) {
    this.updateRoute({ page: 1, sortField: field });
  }

  /**
   * Method to set the current page and trigger page change events
   *
   * @param page
   *    The new page value
   */
  public setPage(page: number) {
    this.currentPage = page;
    this.pageChange.emit(page);
    this.emitPaginationChange();
   * Method to set the current page size and trigger page size change events
   *    The new page size value.
   */
  public setPageSize(pageSize: number) {
    this.pageSize = pageSize;
    this.pageSizeChange.emit(pageSize);
    this.emitPaginationChange();
   * Method to set the current sort direction and trigger sort direction change events
   *    The new sort directionvalue.
   */
  public setSortDirection(sortDirection: SortDirection) {
    this.sortDirection = sortDirection;
    this.sortDirectionChange.emit(sortDirection);
    this.emitPaginationChange();
   * Method to set the current sort field and trigger sort field change events
   *
   * @param sortField
   *    The new sort field.
   */
  public setSortField(field: string) {
    this.sortField = field;
    this.sortFieldChange.emit(field);
    this.emitPaginationChange();
  }

  /**
   * Method to emit a general pagination change event
   */
  private emitPaginationChange() {
    this.paginationChange.emit({
Lotte Hofstede's avatar
Lotte Hofstede committed
      pageId: this.id,
      page: this.currentPage,
      pageSize: this.pageSize,
      sortDirection: this.sortDirection,
      sortField: this.sortField
    });
  /**
   * Method to update the route parameters
   */
  private updateRoute(params: {}) {
      queryParams: Object.assign({}, this.currentQueryParams, params)
   * Method to get pagination details of the current viewed page.
  public getShowingDetails(collectionSize: number): any {
    let showingDetails = { range: null + ' - ' + null, total: null };
    if (collectionSize) {
      let firstItem;
      let lastItem;
      const pageMax = this.pageSize * this.currentPage;
      firstItem = this.pageSize * (this.currentPage - 1) + 1;
      if (collectionSize > pageMax) {
        lastItem = pageMax;
      } else {
        lastItem = collectionSize;
      }
      showingDetails = { range: firstItem + ' - ' + lastItem, total: collectionSize };
    return showingDetails;
   * Method to validate query params
   *
   * @param page
   *    The page number to validate
   * @param pageSize
   *    The page size to validate
   * @returns valid parameters if initial parameters were invalid
  private validateParams(params: any): any {
    const validPage = this.validatePage(params.page);
    const filteredSize = this.validatePageSize(params.pageSize);
    const fixedFields: any = {};
    if (+params.page !== validPage) {
      fixedFields.page = validPage.toString();
    }
    if (+params.pageSize !== filteredSize) {
      fixedFields.pageSize = filteredSize.toString();
    }
    return fixedFields;
  }
  /**
   * Method to update all pagination variables to the current query parameters
   */
  private setFields() {
    // (+) converts string to a number
    const page = this.currentQueryParams.page;
    if (this.currentPage !== +page) {
      this.setPage(+page);
    }

    const pageSize = this.currentQueryParams.pageSize;
    if (this.pageSize !== +pageSize) {
      this.setPageSize(+pageSize);
    }

    const sortDirection = this.currentQueryParams.sortDirection;
    if (this.sortDirection !== +sortDirection) {
      this.setSortDirection(+sortDirection);
    }

    const sortField = this.currentQueryParams.sortField;
    if (this.sortField !== sortField) {
      this.setSortField(sortField);
    this.cdRef.detectChanges();
  /**
   * Method to validate the current page value
   *
   * @param page
   *    The page number to validate
   * @returns returns valid page value
   */
  private validatePage(page: any): number {
    let result = this.currentPage;
    if (isNumeric(page)) {
      result = +page;
    return result;
  /**
   * Method to validate the current page size value
   *
   * @param page size
   *    The page size to validate
   * @returns returns valid page size value
   */
  private validatePageSize(pageSize: any): number {
    const filteredPageSize = this.pageSizeOptions.find((x) => x === +pageSize);
    let result = this.pageSize;
    if (filteredPageSize) {
      result = +pageSize;
    return result;
   * Method to ensure options passed contains the required properties.
   *
   * @param paginateOptions
   *    The paginate options object.
   */
  private checkConfig(paginateOptions: any) {
    const required = ['id', 'currentPage', 'pageSize', 'pageSizeOptions'];
    const missing = required.filter((prop) => {
      return !(prop in paginateOptions);
    });
    if (0 < missing.length) {
      throw new Error('Paginate: Argument is missing the following required properties: ' + missing.join(', '));
   * Method to check if none of the query params necessary for pagination are filled out.
   *
   * @param paginateOptions
   *    The paginate options object.
   */
  private isEmptyPaginationParams(paginateOptions): boolean {
    const properties = ['id', 'currentPage', 'pageSize', 'pageSizeOptions'];
    const missing = properties.filter((prop) => {
      return !(prop in paginateOptions);
    });

    return properties.length === missing.length;
  }

  /**
   * Property to check whether the current pagination object has multiple pages
   * @returns true if there are multiple pages, else returns false
   */
  get hasMultiplePages(): boolean {
    return this.collectionSize > this.pageSize;
  }
   * Property to check whether the current pagination should show a bottom pages
   * @returns true if a bottom pages should be shown, else returns false
   */
  get shouldShowBottomPager(): boolean {
    return this.hasMultiplePages || !this.hidePagerWhenSinglePage
  }