Angular Table Component

Home / Angular Table Component

angular_small

I said it before, but I’ll say it again. Writing web-based line of business applications entails creating a lot of views with tables. I wrote a (imho) a pretty nice table directive for Angular 1.x, and now it’s time to move that directive, in concept, to an Angular2 component.


Similar to the old directive, I want to be able to pass in a single object that defines all of the facets of the table that are needed to render a table.

<custom-table [options]="tableOptions">Custom table here</custom-table>

Everything that I used in Angular 1.x was non-type specific. In using TypeScript with Angular2, this provides an opportunity to strongly type some of the configuration parameters used by the table component.

For the first iteration of the table component, I have created three classes that encapsulated most of the previous parameters.

export class CustomTableColumnDefinition {
  public name: string = '';
  public value: string = '';
  public binding: string = '';
  public filter: string = '';
  public computedClass: any;
  public isComputed: bool = false;
  public isAnchor: bool = false;
  public srefBinding: string = '';
}

export class CustomTableConfig {
  public sortBy: string = '';
  public sortDirection: string = 'desc';
  public pageSize: int = 100;
  public pageNumber: int = 1;
  public totalCount: int = 0;
  public totalPages: int = 0;
  public maxSize: int = 10;
  public showSelectCheckbox: bool = true;
  public showSelectAll: bool = true;
  public showSort: bool = true;
  public clientSort: bool = false;
  public clientPaging: bool = false;
  public stickyHeader: bool = true;
  public stickyHeaderOffset: int = 0;
  public stickyContainer: string = '';
}

export class CustomTableOptions {
  public records : Observable<Array<any>>;
  public columns: Array<CustomTableColumnDefinition>;
  public rowDefns: Array<any>;
  public config: CustomTableConfig;
  public callbacks: any;
}

Most of the properties in the classes are pretty self-explanatory. Some do not, currently, have any analogous implemented functionality, though they will be used in future iterations. Remember, this is the ground work. With those classes defined, the component is actually pretty simple since I am not implementing paging or filtering on the first pass. Basically, the component will take in the parameters, and apply them to a template.

export class CustomTable implements OnInit {
    private _lipsum: any;
    public filteredData: Array<any>;
    public filteredDataObservable: Observable<Array<any>>;
    @Input() options: CustomTableOptions;
    
    constructor(private changeRef: ChangeDetectorRef, private appRef: ApplicationRef) {
        declare var LoremIpsum: any;
        this._lipsum = new LoremIpsum();
    }
    
    getCellValue(row: any, column: CustomTableColumnDefinition) :string {
      if (column.isComputed) {
        let evalfunc = new Function ('r', 'return ' + column.binding);
        let evalresult:string = evalfunc(row);
        return evalresult;
      } else {
        return column.binding.split('.').reduce((prev:any, curr:string) => prev[curr], row);
      }
    }
    
    ngOnInit() {
      this._subscription = this.options.records.subscribe(res => { this.filteredDataObservable = Observable.of(res); this.filteredData = res; });
    }
}

Note the “getCellValue” method. This is the method that will be called to render the value in each table cell. The interesting part is defining the function that does, effectively, the same as the JavaScript eval method. I stumbled across this somewhere, but forget where now. But, I thought it was a pretty handy mechanism to be able to evaluated “computed” column expressions. The other interesting thing to note is the use of the reduce method to be able to drill down to the value of the specified column within the row.

One other thing worth mention is that I’m initially expecting an Observable<any> for the records list. This should allow for handling changes to the underlying dataset from outside the component.

That’s it for the current component code. The HTML is equally basic at this point. I have not added a lot of conditional hiding/showing of elements since, at some point, I would like to have the template for the table rendered dynamically. Dynamic template generation, though, is rather involved with Angular2 at this point.

Within the template, ngFor will be used to iterate over the columns for the header, and then again for the body.

<table class="table-striped table-hover custom-table">
  <thead>
    <tr>
      <th class="th-checkbox">
        <tri-state-checkbox class="toggle-all" [items]="filteredDataObservable"></tri-state-checkbox>
      </th>
      <th *ngFor="let column of options.columns"><span [innerHTML]="column.name"></span></th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let row of filteredData">
      <td class="td-checkbox"><input type="checkbox" [(ngModel)]="row.isSelected"></td>
      <td *ngFor="let column of options.columns">{{getCellValue(row, column)}}</td>
    </tr>
  </tbody>
</table>

As I continue to build out this table component, I’ll be adding additional features that are present in the Angular 1.x directive such as paging, sorting, and sticky headers.

The plunk shows everything in action. The plunk does also include some of my other creations giving the appearance of a full application coming together. I think it helps illustrate how all of these components would work together in a real application.

One thought on “Angular Table Component”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.