We are used to describing HTML components declaratively. For example, a simple table looks like this:
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Superman</td>
<td>18</td>
</tr>
</table>
Wouldn’t it be great if we could use a table component similar in Angular?
The following code example shows how a table component can be used declaratively in Angular.
The table consists of the app-table
component and the app-column
directive.
The column header and property name can be set explicitly on the app-column
directive.
Dynamic content, such as displaying an image, can be specified in the app-column
directive with an ng-template
.
In ng-template
the column value can be accessed with the attribute let-value
on the one hand and the content of the whole row can be accessed with the attribute let-row="row"
on the other hand.
import {Component} from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-table [data]="data">
<app-column header="Avatar" key="avatar">
<ng-template let-value>
<img [src]="value">
</ng-template>
</app-column>
<app-column header="Name" key="name"></app-column>
<app-column header="Age" key="age"></app-column>
</app-table>
`
})
export class AppComponent {
data = [{
name: 'Superman',
avatar: 'http://tiny.cc/ngz36y',
age: 18
}, {
name: 'Hulk',
avatar: 'http://tiny.cc/liz36y',
age: 36
}];
}
As already mentioned, the table consists of a component and a directive.
The app-column
directive is used to describe a column. It has two input values. The key
and the header
.
The ContentChild
decorator allows the directive to access the ng-template
.
import {ContentChild, Directive, Input, TemplateRef} from '@angular/core';
@Directive({
selector: 'app-column'
})
export class ColumnDirective {
@Input() key: string;
@Input() header: string;
@ContentChild(TemplateRef, {static: false}) template;
}
The app-table
component gets the data to be displayed via input property. All specified app-column
directives can be accessed using the ContentChildren
decorator.
This list of directives is iterated twice. Once to display the titles and once to display the values.
With the structural directive *ngTemplateOutlet
the template of a column can be displayed dynamically.
import {Component, ContentChildren, Input, QueryList} from '@angular/core';
import {ColumnDirective} from '../column.directive';
@Component({
selector: 'app-table',
template: `
<table>
<tr>
<th *ngFor="let column of columns">{{column.header}}</th>
</tr>
<tr *ngFor="let row of data">
<td *ngFor="let column of columns">
<ng-container *ngIf="column.template; else rawValue" >
<ng-container
*ngTemplateOutlet="column.template; context: {row: row, $implicit: row[column.key]}"></ng-container>
</ng-container>
<ng-template #rawValue>
{{row[column.key]}}
</ng-template>
</td>
</tr>
</table>
`
})
export class TableComponent {
@Input() data: object[];
@ContentChildren(ColumnDirective) columns: QueryList<ColumnDirective>;
}
With this approach, tables and other components can be used declaratively. This makes the template code easier to read and easier to understand.