詳細ガイド
ディレクティブ

構造ディレクティブ

構造ディレクティブは、<ng-template>要素に適用され、その<ng-template>の内容を条件付きまたは繰り返しでレンダリングするディレクティブです。

使用例

このガイドでは、指定されたデータソースからデータを取得し、そのデータが利用可能になったときにテンプレートをレンダリングする構造ディレクティブを作成します。このディレクティブは、SQLキーワードSELECTにちなんでSelectDirectiveと呼ばれ、属性セレクター[select]と一致させます。

SelectDirectiveには、使用するデータソースの名前を指定する入力があり、selectFromと呼びます。この入力のselectプレフィックスは、省略記法にとって重要です。ディレクティブは、選択されたデータを提供するテンプレートコンテキストを使用して、<ng-template>をインスタンス化します。

以下は、<ng-template>に直接このディレクティブを適用した例です。

      
<ng-template select let-data [selectFrom]="source">  <p>The data is: {{ data }}</p></ng-template>

構造ディレクティブは、データが利用可能になるまで待機してから、<ng-template>をレンダリングできます。

HELPFUL: Angularの<ng-template>要素は、デフォルトでは何もレンダリングしないテンプレートを定義します。構造ディレクティブを適用せずに<ng-template>で要素をラップした場合、それらの要素はレンダリングされません。

詳細については、ng-template APIのドキュメントを参照してください。

構造ディレクティブの省略記法

Angularは、構造ディレクティブの省略記法をサポートしており、<ng-template>要素を明示的に記述する必要がなくなります。

構造ディレクティブは、アスタリスク(*)をディレクティブ属性セレクターの前に付けることで、要素に直接適用できます。たとえば、*selectのようにします。Angularは、構造ディレクティブの前にあるアスタリスクを、ディレクティブをホストし、要素とその子孫を囲む<ng-template>に変換します。

以下は、SelectDirectiveを使用した例です。

      
<p *select="let data from source">The data is: {{data}}</p>

この例は、構造ディレクティブの省略記法の柔軟性を示しており、これはマイクロシンタックスと呼ばれることもあります。

このように使用した場合、構造ディレクティブとそのバインディングのみが<ng-template>に適用されます。<p>タグの他の属性やバインディングはそのまま残されます。たとえば、次の2つの形式は同等です。

      
<!-- 省略記法: --><p class="data-view" *select="let data from source">The data is: {{data}}</p><!-- 長形式の記法: --><ng-template select let-data [selectFrom]="source">  <p class="data-view">The data is: {{data}}</p></ng-template>

省略記法は、一連の規則によって展開されます。以下に、より詳細な文法が定義されていますが、上記の例では、この変換を次のように説明できます。

*select式の最初の部分はlet dataで、テンプレート変数dataを宣言しています。割り当てが続かないため、テンプレート変数はテンプレートコンテキストプロパティ$implicitにバインドされます。

2番目の構文は、キーと式のペアであるfrom sourceです。fromはバインディングキーで、sourceは通常のテンプレート式です。バインディングキーは、PascalCaseに変換し、構造ディレクティブセレクターを先頭に付けることで、プロパティにマップされます。fromキーはselectFromにマップされ、次に式sourceにバインドされます。そのため、多くの構造ディレクティブは、すべて構造ディレクティブのセレクターで始まるプレフィックスを持つ入力を持つことになります。

要素あたりの構造ディレクティブは1つのみ

省略記法を使用する場合、要素ごとに適用できる構造ディレクティブは1つだけです。これは、ディレクティブが展開される<ng-template>要素が1つしかないためです。複数のディレクティブには、複数の子ネストされた<ng-template>が必要であり、どのディレクティブを最初にすべきかは明確ではありません。<ng-container>は、同じ物理的なDOM要素またはコンポーネントに複数の構造ディレクティブを適用する必要がある場合に、ラッパーレイヤーを作成するために使用できます。これにより、ユーザーはネストされた構造を定義できます。

構造ディレクティブの作成

このセクションでは、SelectDirectiveの作成について説明します。

  1. ディレクティブを生成

    Angular CLIを使用して、次のコマンドを実行します。ここで、selectはディレクティブの名前です。

          
    ng generate directive select

    Angularは、ディレクティブクラスを作成し、テンプレートでディレクティブを識別するCSSセレクター[select]を指定します。

  2. ディレクティブを構造化

    TemplateRefViewContainerRefをインポートします。TemplateRefViewContainerRefをプライベート変数としてディレクティブコンストラクターにインジェクトします。

          
    import {Directive, TemplateRef, ViewContainerRef} from '@angular/core';@Directive({  standalone: true,  selector: '[select]',})export class SelectDirective {  constructor(private templateRef: TemplateRef, private ViewContainerRef: ViewContainerRef) {}}
  3. 'selectFrom'入力を追加

    selectFrom @Input()プロパティを追加します。

          
    export class SelectDirective {  // ...  @Input({required: true}) selectFrom!: DataSource;}
  4. ビジネスロジックを追加

    SelectDirectiveが、入力を持つ構造ディレクティブとしてスキャフォールディングされたので、データを取得し、テンプレートをデータと共にレンダリングするロジックを追加できます。

          
    export class SelectDirective {  // ...  async ngOnInit() {    const data = await this.selectFrom.load();    this.viewContainerRef.createEmbeddedView(this.templateRef, {      // テンプレートコンテキストに`$implicit`キーでデータを含むオブジェクトが含まれるように、      // 埋め込みビューを作成します。      $implicit: data,    });  }}

これでSelectDirectiveは起動して実行できるようになりました。次のステップとして、テンプレートタイプチェックのサポートを追加できます。

構造ディレクティブの構文リファレンス

独自の構造ディレクティブを作成する際は、次の構文を使用します。

      
*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"

次のパターンは、構造ディレクティブ文法の各部分を説明しています。

      
as = :export "as" :local ";"?keyExp = :key ":"? :expression ("as" :local)? ";"?let = "let" :local "=" :export ";"?
キーワード 詳細
prefix HTML属性キー
key HTML属性キー
local テンプレートで使用されるローカル変数名
export 与えられた名前の下でディレクティブによってエクスポートされる値
expression 標準的なAngular式

Angularが省略記法をどのように変換するか

Angularは、構造ディレクティブの省略記法を次の通常のバインディング構文に変換します。

省略記法 変換
prefixと裸のexpression [prefix]="expression"
keyExp [prefixKey]="expression" (prefixkeyに追加されます)
let local let-local="export"

省略記法の例

次の表は、省略記法の例を示しています。

省略記法 Angularが構文をどのように解釈するか
*ngFor="let item of [1,2,3]" <ng-template ngFor let-item [ngForOf]="[1, 2, 3]">
*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i" <ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index">
*ngIf="exp" <ng-template [ngIf]="exp">
*ngIf="exp as value" <ng-template [ngIf]="exp" let-value="ngIf">

カスタムディレクティブのテンプレートタイプチェックを改善する

カスタムディレクティブのテンプレートタイプチェックを改善するには、ディレクティブ定義にテンプレートガードを追加します。 これらのガードは、Angularテンプレートタイプチェッカーがコンパイル時にテンプレート内の間違いを見つけるのに役立ち、ランタイムエラーを回避できます。 2種類のガードが可能です。

  • ngTemplateGuard_(input)を使用すると、特定の入力の型に基づいて入力式をどのように絞り込むかを制御できます。
  • ngTemplateContextGuardは、ディレクティブ自体の型に基づいて、テンプレートのコンテキストオブジェクトの型を判断するために使用されます。

このセクションでは、両方の種類のガードの例を示します。 詳細については、テンプレートタイプチェックを参照してください。

テンプレートガードによるタイプの絞り込み

テンプレート内の構造ディレクティブは、そのテンプレートがランタイムにレンダリングされるかどうかを制御します。一部の構造ディレクティブは、入力式の型に基づいてタイプの絞り込みを実行したいと考えています。

入力ガードで可能な絞り込みは2つあります。

  • TypeScriptの型アサーション関数に基づいて入力式を絞り込む。
  • 入力式の真偽値に基づいて入力式を絞り込む。

型アサーション関数を定義して入力式を絞り込むには、次の手順を実行します。

      
// このディレクティブは、アクターがユーザーの場合にのみテンプレートをレンダリングします。// テンプレート内では、`actor`式の型が`User`に絞り込まれていることを// アサートしたいと考えています。@Directive(...)class ActorIsUser {  @Input() actor: User|Robot;  static ngTemplateGuard_actor(dir: ActorIsUser, expr: User|Robot): expr is User {    // 実際にはreturn文は不要ですが、    // TypeScriptエラーを防ぐために含めています。    return true;  }}

タイプチェックは、ngTemplateGuard_actorが入力にバインドされた式に対してアサートされたかのように、テンプレート内で動作します。

一部のディレクティブは、入力の真偽値が真の場合にのみテンプレートをレンダリングします。真偽値の完全な意味を型アサーション関数で捉えることはできないため、代わりに'binding'というリテラル型を使用して、テンプレートタイプチェッカーに、バインディング式自体をガードとして使用する必要があることを示すことができます。

      
@Directive(...)class CustomIf {  @Input() condition!: any;  static ngTemplateGuard_condition: 'binding';}

テンプレートタイプチェッカーは、conditionにバインドされた式がテンプレート内で真であるとアサートされたかのように動作します。

ディレクティブのコンテキストの型付け

構造ディレクティブがインスタンス化されたテンプレートにコンテキストを提供する場合、ディレクティブ自体型に基づいてコンテキストの型を導き出すことができる静的ngTemplateContextGuard型アサーション関数を提供することで、テンプレート内でそのコンテキストを正しく型付けできます。これは、ディレクティブの型がジェネリックである場合に役立ちます。

上記のSelectDirectiveでは、データソースがジェネリックであっても、ngTemplateContextGuardを実装してデータ型を正しく指定できます。

      
// テンプレートコンテキストのインターフェースを宣言します。export interface SelectTemplateContext<T> {  $implicit: T;}@Directive(...)export class SelectDirective<T> {  // ディレクティブのジェネリック型`T`は、  // 入力に渡される`DataSource`型から推測されます。  @Input({required: true}) selectFrom!: DataSource<T>;  // ジェネリック型のディレクティブを使用して、コンテキストの型を絞り込みます。  static ngTemplateContextGuard<T>(dir: SelectDirective<T>, ctx: any): ctx is SelectTemplateContext<T> {    // 前述のように、ガードの本体は実行時に使用されず、    // TypeScriptエラーを防ぐためだけに含まれています。    return true;  }}