AngularでNgTemplateOutletを使用して再利用可能なコンポーネントを作成する方法
序章
単一責任の原則は、アプリケーションの一部に1つの目的があるべきであるという考えです。 この原則に従うと、Angularアプリのテストと開発が簡単になります。
Angularでは、特定のコンポーネントを作成する代わりにNgTemplateOutlet
を使用すると、コンポーネント自体を変更することなく、さまざまなユースケースに合わせてコンポーネントを簡単に変更できます。
この記事では、既存のコンポーネントを取得して、NgTemplateOutlet
を使用するように書き直します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
- Angularプロジェクトのセットアップにある程度精通している。
このチュートリアルは、ノードv16.6.2、npm
v7.20.6、および@angular/core
v12.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
を追跡し、card
とlist
の両方のビューでitems
を表示する方法を知っている必要があります。 また、items
はheader
とcontent
でのみ表示できます。
テンプレートを使用してコンポーネントを個別のビューに分割することにより、これを変更しましょう。
ステップ2–ng-template
とNgTemplateOutlet
を理解する
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() { } }
CardOrListViewComponent
はCardItemDirective
とListItemDirective
をインポートします。
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
と同様にfirst
やlast
などのアイテムコンテキストにさらに追加したり、まったく異なるタイプのitems
を表示したりできます。
結論
この記事では、既存のコンポーネントを取得して、NgTemplateOutlet
を使用するように書き直しました。
Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。