Crafting Angular Components
27.05.2019, Michael Gerber

Angular components shall be used in the template like common HTML components. This blog post shows how this can be achieved with simple means.

Why declarative

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?

Using the Table Component

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
  }];
}

Creating the Table Component

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>;
}

Conclusion

With this approach, tables and other components can be used declaratively. This makes the template code easier to read and easier to understand.