Angular Komponenten erstellen
27.05.2019, Michael Gerber

Angular Komponenten sollten im Template so verwendet werden können, wie gängige HTML Komponente. Dieser Blogpost zeigt, wie dies mit einfachen Mitteln zu erreichen ist.

Wieso deklarativ

Wir sind uns gewohnt, HTML Komponenten deklarativ zu beschreiben. Eine einfache Tabelle sieht zum Beispiel wie folgt aus:

<table>
  <tr>
    <th>Name</th>
    <th>Age</th>
  </tr>
  <tr>
    <td>Superman</td>
    <td>18</td>
 </tr>
</table>

Wäre es nicht toll, wenn wir eine Tabellenkomponente in Angular ähnlich verwenden könnten?

Verwendung der Tabellenkomponente

Folgendes Codebeispiel zeigt, wie eine Tabellenkomponente in Angular deklarativ verwendet werden kann. Die Tabelle besteht aus der app-table Komponente und der app-column Direktive. Der Spaltentitel und der Property-Name, kann direkt auf der app-column Direktive gesetzt werden. Dynamischer Inhalt, wie z. B. die Anzeige eines Bildes, kann der app-column Direktive mit einem ng-template angegeben werden. Im ng-template kann einerseits mit dem Attribut let-value auf den Spaltenwert und andererseits mit dem Attribut let-row="row" auf den Inhalt der gesamten Reihe zugegriffen werden.

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

Erstellung der Tabellenkomponente

Wie bereits erwähnt, besteht die Tabelle aus einer Komponente und einer Direktive. Die app-column Direktive dient zur Beschreibung einer Spalte. Sie hat zwei Input Werte, den key und den header. Durch den ContentChild Decorator kann die Direktive auf das ng-template zugreifen.

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

Die app-table Komponente erhält die darzustellenden Daten via Input Property. Mittels des ContentChildren Decorators kann auf alle angegebenen app-column Direktiven zugegriffen werden. Über diese Liste von Direktiven wird zweimal iteriert. Einmal um die Titel und einmal um die Werte darzustellen. Mittels der strukturellen Direktive *ngTemplateOutlet kann das Template einer Spalte dynamisch dargestellt werden.

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

Fazit

Mit dem vorgestellten Ansatz können Tabellen und andere Komponenten deklarativ verwendet werden. Dies macht den Template Code besser lesbar und verständlicher.