Search code examples
angularangular-directiveng-bootstrap

Reuse custom directive to make sortable table in Angular Bootstrap


I am using Angular Bootstrap in my project. I have created a table following the link (https://ng-bootstrap.github.io/#/components/table/examples). This table has sortable and searchable features. Following the example from link (https://stackblitz.com/run?file=app/table-complete.ts) I have created a table in my project. This example has a custom directive for sorting the table. The code is given below:

import { Directive, EventEmitter, Input, Output } from "@angular/core";
import { IncomeHeadListModel } from "../_models/income-head-list.model";

export type SortColumn = keyof IncomeHeadListModel | '';
export type SortDirection = 'asc' | 'desc' | '';
const rotate: {[key: string]: SortDirection} = { 'asc': 'desc', 'desc': '', '': 'asc' };

export interface SortEvent {
  column: SortColumn;
  direction: SortDirection;
}

@Directive({
  selector: 'th[sortable]',
  host: {
    '[class.asc]': 'direction === "asc"',
    '[class.desc]': 'direction === "desc"',
    '(click)': 'rotate()'
  }
})
export class NgbdSortableHeader {

  @Input() sortable: SortColumn = '';
  @Input() direction: SortDirection = '';
  @Output() sort = new EventEmitter<SortEvent>();

  rotate() {
    this.direction = rotate[this.direction];
    this.sort.emit({column: this.sortable, direction: this.direction});
  }
}

In this directive I have to pass a model (IncomeHeadListModel ) to make the field sortable. The line is:

export type SortColumn = keyof IncomeHeadListModel | '';

Now I want to reuse the same directive for another table. That table use another model which is: ExpenseHeadListModel. How can I pass this another model in the below line:

export type SortColumn = keyof IncomeHeadListModel | '';

Solution

  • The simple solution would be to ease the typings and just use a string for column names.

    import { Directive, EventEmitter, Input, Output } from "@angular/core";
    import { IncomeHeadListModel } from "../_models/income-head-list.model";
    
    export type SortDirection = 'asc' | 'desc' | '';
    const rotate: {[key: string]: SortDirection} = { 'asc': 'desc', 'desc': '', '': 'asc' };
    
    export interface SortEvent {
      column: string;
      direction: SortDirection;
    }
    
    @Directive({
      selector: 'th[sortable]',
      host: {
        '[class.asc]': 'direction === "asc"',
        '[class.desc]': 'direction === "desc"',
        '(click)': 'rotate()'
      }
    })
    export class NgbdSortableHeader {
    
      @Input() sortable: string = '';
      @Input() direction: SortDirection = '';
      @Output() sort = new EventEmitter<SortEvent>();
    
      rotate() {
        this.direction = rotate[this.direction];
        this.sort.emit({column: this.sortable, direction: this.direction});
      }
    }
    

    There could be a better solution leveraging generics and inferring the type from the parent component or context. Or with providing the type as an input property. I'm not sure if it's possible thought.