Angular Grids Destructuring

Julia Passynkova
6 min readApr 29, 2017

--

There are multiple open source and commercial Angular grids on the market and I really suggest to use one of them. So far I worked and experimented with Wijmo, Kendo, ngx-datatable, Ag-Grid Angular grids. All of them are complex grid libraries with a rich set functions that are required by enterprise customers, such as grouping, sorting, filtering, scrolling, freezing, formatting, exporting, pagination, cell and row selection and much more.

Grid examples

What I realized that all these grids follow the similar pattern that application developer should use to create a grid.

Let’s use open source ngx-datatable and commercial Wijmo one to create a simple grid. Here is the set of rows to display:

this.rows = [
{
id: '1', name: 'angularjs',
image: 'https://angularjs.org/img/AngularJS-large.png'
},
{
id: '2', name: 'angulario',
image: 'https://angular.io/resources/images/logos/angular/angular.svg'
}
];

Here is ngrx-datatable html markup for the grid that an application developer needs to use to create a grid:

<ngx-datatable class='material' [rows]='rows'>
<ngx-datatable-column name="Id" prop="id">
</ngx-datatable-column>
<ngx-datatable-column name="Name" prop="name">
<template let-value="value" ngx-datatable-cell-template>
<a [attr.href]="'http://' + value + '.com'">{{value}}</a>
</template>
</ngx-datatable-column>
<ngx-datatable-column name="Image" prop="image">
<template let-value="value" ngx-datatable-cell-template>
<img style="height: 16px;" [src]="value">
</template>
</ngx-datatable-column>
</ngx-datatable>

and here is the grid:

The same grid with Wijmo looks like:

<wj-flex-grid [itemsSource]="rows">
<wj-flex-grid-column [header]="'Id'" [binding]="'id'">
</wj-flex-grid-column>

<wj-flex-grid-column [header]="'Name'" [binding]="'name'">
<template wjFlexGridCellTemplate [cellType]="'Cell'" let-item="item">
<a [attr.href]="'http://' + item.name + '.com'">{{item.name}}</a>
</template>
</wj-flex-grid-column>

<wj-flex-grid-column [header]="'Image'" [binding]="'image'">
<template wjFlexGridCellTemplate [cellType]="'Cell'" let-item="item">
<img style="height: 16px;" [src]="item.image">
</template>
</wj-flex-grid-column>
</wj-flex-grid>

and here is the grid:

Grid Destructuring

See the similarities? Yes, each grid markup has the following basic elements:

Main grid container. It is the root element of the grid markup. Input: array of rows. Example: ngx-datatable, wj-flex-grid.

<ngx-datatable class='material' [rows]='rows'>...</ngx-datatable>

Column container. It provides configuration of each column. Input: name of the column and biding to row data. Example: ngx-datatable-column, wj-flex-grid-column.

<ngx-datatable-column name="Id" prop="id">
</ngx-datatable-column>

Cell template (optional). It provides a custom rendering for the column. Examples: ngx-datatable-cell-template, wjFlexGridCellTemplate

<template let-value="value" ngx-datatable-cell-template>
<a [attr.href]="'http://' + value + '.com'">{{value}}</a>
</template>

Create your own grid

In order to understand how these grids work I decided to create my own simple grid system. This is just an experiment for educational purpose and I will alway use a third party grid for my enterprise customers. Promise.resolve(true) 😀

Markup

Let’s create a simple markup (very similar to previous ones).

<grid [rows]="rows">
<column name="Id" prop="id"></column>
<column name="Name" prop="name">
<ng-template let-value="value" cell>
<a [attr.href]="'http://' + value + '.com'">{{value}}</a>
</ng-template>
</column>
<column name="Image" prop="img">
<ng-template let-value="value">
<img style="height: 16px;" [src]="value">
</ng-template>
</column>
</grid>

Out main container is grid, column is column and cell template for custom rendering. Easy? So for each container we need to define either Angular component or directive. Component will contain logic and template and directives only logic. Let’s start!

Grid Component

I will use simple HTML table to define the grid HTML markup. We assume that we can calculate list of columns and iterate over rows and columns to build a simple HTML table.

<table>
<tr>
<th *ngFor="let column of columns">{{column.name}}</th>
</tr>
<tr *ngFor="let row of rows">
<td *ngFor="let col of columns">{{row[col.prop]}}</td>
<tr>
</table>

Here is the Grid component shell:

@Component({
selector: 'grid',
template: `
<table>
<-- See above table --->
</table>
`
})
export class Grid {
@Input() rows: Array<Object>;
columns: Array<Object>;
}

How to get these columns? We have to use @ContentChildren Angular decorator to get access to content of grid component. So “val” variable will contain a list of our columns.

<grid [rows]="rows">
<column name="Id" prop="id"></column>
<column name="Name" prop="name"></column>
</grid>
@ContentChildren(Column) val: QueryList<Column>;

The next step is to covert QueryList to Array of objects (columns) that HTML table’ s TR uses. We will change the @ContentChildren to become a setter to do this conversion:

@ContentChildren(Column) set columnTemplates(val: QueryList<Column>) {
const templates = val.toArray();
let cols = [];
for (const template of templates) {
const col = {};
col['name'] = template['name'];
col['prop'] = template['prop'];
cols.push(col);
}
this.columns = cols;
}

Column Directive

In the previous step we already use Column directive but did not defined it yet. In our case it should hold its name and binding. Here is a simple definition of Column directive.

@Directive({selector: 'column'})
export class Column {
@Input() name: string;
@Input() prop: string;
}

That’s it. Our grid is already able to display a simple table. Let’s add an optional Cell support to allow developer to customize rendering of each column.

Cell support (let’s talk about templates)

In oder to allow developer provide custom rendering for each colum we will use Angular template tag (ng-template in Angular 4) . Here is what MSDN says about about template:

The HTML <template> element is a mechanism for holding client-side content that is not to be rendered when a page is loaded but may subsequently be instantiated during runtime using JavaScript.

Here is how our image template looks like:

<ng-template let-value="value">
<img style="height: 16px;" [src]="value">
</ng-template>

As MSDN says this template won’t be rendered by Angular automatically but we can use it in TypeScript/JavaScript to render the image column.

Angular provides an easy way to render a custom template using ngTemplateOutlet directive. The directive accepts reference to Angular template and context to bind data.

<ng-template [ngTemplateOutlet]="template" 
[ngOutletContext]="{ value: row[col.prop] }">

So for example, here is a simple code that uses ngTemplateOutlet directive to render a reusable template. The App component gets access to <ng-template> and pass it along with its bindings (name) to a child component to render using ngTemplateOutlet.

The full plnkr code is here: https://plnkr.co/edit/J0yQi26O0Npxo8OHdYF5

@Component({
selector: 'child',
template:`
<ng-template [ngTemplateOutlet]="template"
[ngOutletContext]="{ name: name }"></ng-template>
`
})
export class Child {
@Input() template: TemplateRef<any>;
@Input() name: string;
}
@Component({
selector: 'my-app',
template: `
<div>
<input #inp type="text">
<button (click)="setName(inp.value)">Set Name</button>
<ng-template #tpl>My template {{name}}</ng-template>

<div>
<child *ngIf="name" [template]="tpl" [name]="name"></child>
</div>
</div>
`,
})
export class App {
@ViewChild('tpl') tpl: TemplateRef<any>;
name: string;

constructor() {}
setName(name) {
this.name = name;
}
}

I hope you get the idea. Let’s use this pattern to build our cell template.

Cell support (let’s use templates)

Our column might contain a template like that:

<column name="Image" prop="img">
<ng-template let-value="value">
<img style="height: 16px;" [src]="value">
</ng-template>
</column>

We can access to the template using @ContentChild and we know that it is a template (Angular TemplateRef):

@Directive({selector: 'column'})
export class Column {
@Input() name: string;
@Input() prop: string;
@ContentChild(TemplateRef) cellTemplate: TemplateRef<any>;
}

We are almost done. We just need to use this template to render data in the grid using Angular’s ngTemplateOutlet directive. So instead of initial code:

<td *ngFor="let col of columns">{{row[col.prop]}}</td>

we will add support for both cases:

  1. simple column that does not have a template and
  2. for column with a template:
<td *ngFor="let col of columns">
<span *ngIf="!col.cellTemplate"> {{row[col.prop]}}</span>
<ng-template *ngIf="col.cellTemplate" [ngTemplateOutlet]="col.cellTemplate"
[ngOutletContext]="{ value: row[col.prop] }">
</ng-template>
</td>

And here is the last step make these template be part of column definition. So we upgrade columnTemplate Grid’s component method to add cellTemplate:

@ContentChildren(Column) set columnTemplates(val: QueryList<Column>) {
const templates = val.toArray();
let cols = [];
for (const template of templates) {
const col = {};
col['name'] = template['name'];
col['prop'] = template['prop'];
if (template.cellTemplate) {
col['cellTemplate'] = template.cellTemplate;
}

cols.push(col);
}
this.columns = cols;
}

And here is our own grid:

Here is a link to the full custom grid solution: https://plnkr.co/edit/vu6RPHp1IxAqngZ4XiZR?p=preview

I want to thank Austin for creating a beautiful ngx-datatable grid library that helps me to deliver solutions for my enterprise customers.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

By the way, if you liked this post, you may also like my other blog posts about Angular and RxJS at https://medium.com/@juliapassynkova

--

--

Julia Passynkova

Front-End developer working as an independent contractor with Angular and React in Toronto, Canada.