AngularでNgTemplateOutletを使用して再利用可能なコンポーネントを作成する方法

提供:Dev Guides
移動先:案内検索

序章

単一責任の原則は、アプリケーションの一部に1つの目的があるべきであるという考えです。 この原則に従うと、Angularアプリのテストと開発が簡単になります。

Angularでは、特定のコンポーネントを作成する代わりにNgTemplateOutletを使用すると、コンポーネント自体を変更することなく、さまざまなユースケースに合わせてコンポーネントを簡単に変更できます。

この記事では、既存のコンポーネントを取得して、NgTemplateOutletを使用するように書き直します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、ノードv16.6.2、npm v7.20.6、および@angular/corev12.2.0で検証されました。

ステップ1-CardOrListViewComponentを構築する

modeに応じて'card'または'list'形式でitemsを表示するCardOrListViewComponentについて考えてみます。

card-or-list-view.component.tsファイルで構成されています。

card-or-list-view.component.ts

import {
  Component,
  Input
} from '@angular/core';

@Component({
  selector: 'card-or-list-view',
  templateUrl: './card-or-list-view.component.html'
})
export class CardOrListViewComponent {

  @Input() items: {
    header: string,
    content: string
  }[] = [];

  @Input() mode: string = 'card';

}

そしてcard-or-list-view.component.htmlテンプレート:

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <div *ngFor="let item of items">
      <h1>{{item.header}}</h1>
      <p>{{item.content}}</p>
    </div>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      {{item.header}}: {{item.content}}
    </li>
  </ul>
</ng-container>

このコンポーネントの使用例を次に示します。

使用法.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

このコンポーネントには単一の責任はなく、柔軟性もあまりありません。 modeを追跡し、cardlistの両方のビューでitemsを表示する方法を知っている必要があります。 また、itemsheadercontentでのみ表示できます。

テンプレートを使用してコンポーネントを個別のビューに分割することにより、これを変更しましょう。

ステップ2–ng-templateNgTemplateOutletを理解する

CardOrListViewComponentがあらゆる種類のitemsを表示できるようにするには、それらの表示方法を指示できる必要があります。 これは、itemsをスタンプするために使用できるテンプレートを提供することで実現できます。

テンプレートは<ng-template>を使用したTemplateRefsになり、スタンプはTemplateRefsから作成されたEmbeddedViewRefsになります。 EmbeddedViewRefsは、独自のコンテキストでAngularのビューを表し、最小の必須ビルディングブロックです。

Angularは、NgTemplateOutletを使用してテンプレートからビューをスタンプアウトするというこの概念を使用する方法を提供します。

NgTemplateOutletは、TemplateRefとコンテキストを取得し、提供されたコンテキストでEmbeddedViewRefをスタンプアウトするディレクティブです。 テンプレートでlet-テンプレート:TemplateVariableName="contextProperty"属性を介してコンテキストにアクセスし、テンプレートが使用できる変数を作成します。 コンテキストプロパティ名が指定されていない場合は、$implicitプロパティが選択されます。

次に例を示します。

import { Component } from '@angular/core';

@Component({
  template: `
    <ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container>
    <ng-template #templateRef let-default let-other="aContextProperty">
      <div>
        $implicit = '{{default}}'
        aContextProperty = '{{other}}'
      </div>
    </ng-template>
`
})
export class NgTemplateOutletExample {
  exampleContext = {
    $implicit: 'default context property when none specified',
    aContextProperty: 'a context property'
  };
}

例からの出力は次のとおりです。

<div>
  $implicit = 'default context property when none specified'
  aContextProperty = 'a context property'
</div>

defaultおよびother変数は、let-defaultおよびlet-other="aContextProperty"プロップによって提供されます。

ステップ3–リファクタリングCardOrListViewComponent

CardOrListViewComponentに柔軟性を提供し、任意のタイプのitemsを表示できるようにするために、テンプレートとして読み込む2つの構造ディレクティブを作成します。 これらのテンプレートは、カードとリストアイテムになります。

card-item.directive.tsは次のとおりです。

card-item.directive.ts

import { Directive } from '@angular/core';

@Directive({
  selector: '[cardItem]'
})
export class CardItemDirective {

  constructor() { }

}

そしてここにlist-item.directive.tsがあります:

list-item.directive.ts

import { Directive } from '@angular/core';

@Directive({
  selector: '[listItem]'
})
export class ListItemDirective {

  constructor() { }

}

CardOrListViewComponentCardItemDirectiveListItemDirectiveをインポートします。

card-or-list-view.component.ts

import {
  Component,
  ContentChild,
  Input,
  TemplateRef 
} from '@angular/core';
import { CardItemDirective } from './card-item.directive';
import { ListItemDirective } from './list-item.directive';

@Component({
  selector: 'card-or-list-view',
  templateUrl: './card-or-list-view.component.html'
})
export class CardOrListViewComponent {

  @Input() items: {
    header: string,
    content: string
  }[] = [];

  @Input() mode: string = 'card';

  @ContentChild(CardItemDirective, {read: TemplateRef}) cardItemTemplate: any;
  @ContentChild(ListItemDirective, {read: TemplateRef}) listItemTemplate: any;

}

このコードは、構造ディレクティブをTemplateRefsとして読み取ります。

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <ng-container *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="cardItemTemplate"></ng-container>
    </ng-container>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="listItemTemplate"></ng-container>
    </li>
  </ul>
</ng-container>

このコンポーネントの使用例を次に示します。

使用法.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
      <div *cardItem>
        Static Card Template
      </div>
      <li *listItem>
        Static List Template
      </li>
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

これらの変更により、CardOrListViewComponentは、提供されたテンプレートに基づいて、カードまたはリストフォームに任意のタイプのアイテムを表示できるようになりました。 現在、テンプレートは静的です。

最後に行う必要があるのは、コンテキストを与えることでテンプレートを動的にすることです。

card-or-list-view.component.html

<ng-container [ngSwitch]="mode">
  <ng-container *ngSwitchCase="'card'">
    <ng-container *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="cardItemTemplate; context: {$implicit: item}"></ng-container>
    </ng-container>
  </ng-container>
  <ul *ngSwitchCase="'list'">
    <li *ngFor="let item of items">
      <ng-container *ngTemplateOutlet="listItemTemplate; context: {$implicit: item}"></ng-container>
    </li>
  </ul>
</ng-container>

このコンポーネントの使用例を次に示します。

使用法.component.ts

import { Component } from '@angular/core';

@Component({
  template: `
    <card-or-list-view
        [items]="items"
        [mode]="mode">
      <div *cardItem="let item">
        <h1>{{item.header}}</h1>
        <p>{{item.content}}</p>
      </div>
      <li *listItem="let item">
        {{item.header}}: {{item.content}}
      </li>
    </card-or-list-view>
`
})
export class UsageExample {
  mode = 'list';
  items = [
    {
      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
      content: 'The single responsibility principle...'
    } // ... more items
  ];
}

注目すべき興味深い点は、構文糖衣にアスタリスクプレフィックスとmicrosyntaxを使用していることです。 それは次と同じです:

<ng-template cardItem let-item>
  <div>
    <h1>{{item.header}}</h1>
    <p>{{item.content}}</p>
  </div>
</ng-template>

以上です! 元の機能はありますが、テンプレートを変更することで必要なものを表示できるようになり、CardOrListViewComponentの責任が軽減されました。 ngForと同様にfirstlastなどのアイテムコンテキストにさらに追加したり、まったく異なるタイプのitemsを表示したりできます。

結論

この記事では、既存のコンポーネントを取得して、NgTemplateOutletを使用するように書き直しました。

Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。