Angular の ng-template、ng-content、ng-container、および *ngTemplateOutlet について知っておくべきすべてのこと
オフィス プロジェクトで新しい機能に取り組むのに忙しかったある日のことでした。
DOM を検査しているときに、Angular によって要素に適用される ngcontent
を見ました。 うーん…最終的なDOMに要素が含まれているなら、<ng-container>
の使い道は何なんだ? 当時は<ng-container>
と<ng-content>
を混同していました。
自分の疑問に対する答えを知るために、私は<ng-template>
という概念を発見したのです。 驚いたことに、*ngTemplateOutlet
もあったのです。 私は2つの概念を明確にするために旅を始めましたが、今では4つの概念があり、ほとんど同じように聞こえます!
このような状況に陥ったことはありますか? もしそうなら、あなたは正しい場所にいるのです。
<ng-template>
名前が示すように、<ng-template>
は Angular が構造ディレクティブ (*ngIf
、*ngFor
、 およびカスタム ディレクティブ) とともに使用するテンプレート要素です。 Angular はホスト要素 (ディレクティブが適用される) を
<ng-template>
内にラップし、診断コメントで置き換えることにより、完成した DOM で <ng-template>
を消費します。
*ngIf
の簡単な例について考えてみましょう:
上に示すのは *ngIf
に対する Angular の解釈です。 Angularはディレクティブが適用されるホスト要素を<ng-template>
内に置き、ホストをそのまま維持する。 最終的な DOM は、この記事の冒頭で見たものと同様です。
Usage:
ここまでで Angular による <ng-template>
の使用方法を確認しましたが、これを使用したい場合はどうすればよいでしょうか。 これらの要素は構造的な指示でのみ機能するので、次のように書くことができます:
ここで home
は true
値に設定したコンポーネントの boolean
属性です。 上記のコードの DOM:
何もレンダリングされませんでした!
DOMの出力は次のとおりです。 🙁
しかし、構造ディレクティブで <ng-template>
を正しく使用しても、なぜメッセージが表示されないのでしょうか。
これは予想された結果でした。 すでに説明したように、Angular は <ng-template>
を診断コメントで置き換えます。 Angularはあなたのユースケースに完全に対応しているので、上記のコードがエラーを発生させないことは間違いないでしょう。
Angular によってレンダリングされた上記の 2 つの DOM を比較してみましょう。
よく見ると、例 2 の最終 DOM に 1 つの追加のコメントタグがあります。 Angular が解釈したコードは次のとおりです:
Angular はあなたのホスト <ng-template>
を別の <ng-template>
で包み込み、外の <ng-template>
を診断コメントにするだけでなく内のものにも変換していました!
Angular version 2.1.0 では、あなたのホスト <ng-template>
を診断コメントとして使用することができます。
これを取り除くには、希望する結果を得るために 2 つの方法があります。
Method 1:
この方法では、さらなる処理の必要がない砂糖抜き形式を Angular に提供することになります。 今回、Angular は <ng-template>
をコメントに変換するだけで、その中のコンテンツはそのままにします(以前のケースのように <ng-template>
の中にはもう入りません)。
他の構造的なディレクティブでこの形式を使用する方法について詳しくは、この記事を参照してください。
このように複数の <ng-template>
を使用することはお勧めしません (代わりに <ng-container>
を使用することができます)。 これらは、複数の場所で再利用できるテンプレートへのコンテナとして使用されます。 この点については、この記事の後のセクションで詳しく説明します。
<ng-container>
以下のようなコードを書いたり見たりしたことがありますか:
多くのユーザーがこのコードを書く理由は、Angular で単一のホスト要素に複数の構造ディレクティブを使えないことです。 さて、このコードは問題なく動作しますが、item.id
が不要かもしれないfalsy値である場合、DOMにいくつかの余分な空の<div>
を導入しています。
このような単純な例では気にならないかもしれませんが、(数万のデータを表示する)複雑な DOM を持つ巨大アプリケーションでは、要素がリスナーを持つ場合があるので、DOM でイベントを聞くために、これは面倒になる可能性があります。
No worries, we have <ng-container>
to the rescue!
The Angular <ng-container>
is a grouping element that doesn’t put it in the DOM because Angular has interfere with styles and layout.
そこで、私たちの例1を<ng-container>
で書くと、
として最終DOMを得ることができます。
見てください、これらの空の <div>
を取り除いたことがわかります。 DOM に余分な要素を導入せずに複数の構造ディレクティブを適用したい場合は、<ng-container>
を使用する必要があります。 もう一つの使用例として、ページにテンプレートを動的に注入するために使用されます。 この使用例は、この記事の最後のセクションで説明します。
<ng-content>
これらは、設定可能なコンポーネントを作成するために使用されます。 つまり、コンポーネントはそのユーザーのニーズに応じて構成することができます。 これは、コンテンツ プロジェクションとしてよく知られています。 公開ライブラリで使用されるコンポーネントは、<ng-content>
を使用して自分自身を設定可能にします。
単純な <project-content>
コンポーネントを考えてみましょう。
<project-content>
の開始および終了タグ内で渡される HTML コンテンツは、投影すべきコンテンツと見なされます。 これがコンテンツプロジェクションというものです。 コンテンツはコンポーネント内の<ng-content>
の中にレンダリングされる。 これにより、<project-content>
コンポーネントの消費者は、コンポーネント内の任意のカスタム フッターを渡し、それがどのようにレンダリングされるかを正確に制御することができます。
Multiple Projections:
どのコンテンツをどこに配置するかを決めることができるとしたらどうでしょうか。 すべてのコンテンツが単一の <ng-content>
の内部に投影されるのではなく、<ng-content>
の select
属性を使用して、コンテンツがどのように投影されるかを制御することも可能です。
以下にその方法を示します。
によるマルチコンテンツ投影では、<project-content>
の定義を修正してマルチコンテンツ投影を実行しました。 select
属性は、特定の <ng-content>
の内部にレンダリングされるコンテンツの種類を選択します。 ここでは、ヘッダh1
要素をレンダリングするために最初のselect
があります。 もし投影されたコンテンツにh1
要素がなければ、何もレンダリングされません。 同様に、2 番目の select
は div
を探します。 残りのコンテンツは、select
がない最後の <ng-content>
の内部にレンダリングされます。
コンポーネントを呼び出すと次のようになります:
*ngTemplateOutlet
… 複数の場所で再利用できるテンプレートへのコンテナーとして使用されています。 これについては、この記事の後のセクションで詳しく説明します。
…ページにテンプレートを動的に注入するために使用される別のユースケースがあります。 このユースケースについては、この記事の最後のセクションで取り上げる予定です。
このセクションでは、前に述べた上記の2つのポイントについて説明します。 *ngTemplateOutlet
は、ループや条件に関係なくビューのさまざまなセクションに共通のテンプレートを挿入する場合と、高度に構成されたコンポーネントを作成する場合の 2 つのシナリオで使用されます。 たとえば、Web サイト内に配置される会社のロゴなどです。
以下はコード スニペットです:
見てのとおり、ロゴのテンプレートを一度書き、1 行のコードで同じページに 3 回使用しました!
*ngTemplateOutlet
もコンテキスト オブジェクトを受け取り、共通のテンプレート出力をカスタマイズするために渡すことができます。 コンテキスト オブジェクトについての詳細は、公式ドキュメントを参照してください。
カスタマイズ可能なコンポーネント:
*ngTemplateOutlet
の 2 番目の使用例は、高度にカスタマイズされたコンポーネントです。
上記は <project-content>
コンポーネントの修正版で、headerTemplate
, bodyTemplate
, footerTemplate
という 3 つの入力プロパティが利用可能です。 以下は、project-content.ts
:
ここで達成しようとしているのは、<project-content>
の親コンポーネントから受け取ったヘッダー、ボディ、フッターを表示することです。 これらのうち1つでも提供されていない場合、我々のコンポーネントはその代わりにデフォルトのテンプレートを表示します。
最近変更したコンポーネントを使用するには:
にテンプレート参照を渡す方法について説明しています。
ng-content vs. *ngTemplateOutlet
どちらも高度にカスタマイズされたコンポーネントを実現するのに役立ちますが、いつ、どちらを選ぶべきでしょうか。
明らかに、*ngTemplateOutlet
は何も提供されなければ、デフォルトテンプレートを表示する力をより多く与えてくれることが分かります。 これはコンテンツをそのままレンダリングします。 最大でも、select
属性の助けを借りて、コンテンツを分割し、ビューの異なる場所に表示することができます。 ng-content
内のコンテンツは、条件付きでレンダリングすることはできません。 親から受け取ったコンテンツを表示する必要があり、コンテンツに基づいて判断する手段はありません。
ただし、2 つのうちどちらを選択するかは、ユースケースに完全に依存します。 少なくとも、私たちは今、ng-content
の機能に加えて、コンテンツに対してより多くの制御を提供する新しい武器 *ngTemplateOutlet
を持っています!
。