Exploring Angular DOM manipulation techniques using ViewContainerRef
Angular で DOM を扱うことについて読むときはいつも、これらのクラスの 1 つかいくつかが言及されているのを目にします。 ElementRef
、TemplateRef
、ViewContainerRef
などです。 残念ながら、これらのいくつかは Angular のドキュメントや関連記事で取り上げられていますが、全体的なメンタルモデルの説明や、これらがどのように一緒に動作するかの例はまだ見つかっていません。 この記事はそのようなモデルを説明することを目的としています。
Renderer とビュー コンテナーを使用した Angular での DOM 操作についてより深い情報をお探しの場合は、NgVikings で私の講演をチェックしてください。 または、動的 DOM 操作に関する詳細な記事をお読みください。 Angular で DOM を操作する: 予期しない結果と最適化テクニック
angular.js
の世界から来た人は、DOM を操作するのがかなり簡単だったことをご存知でしょう。 Angular は DOM element
を link
関数に注入し、コンポーネントのテンプレート内の任意のノードを照会し、子ノードを追加または削除し、スタイルを変更することなどが可能でした。 しかし、このアプローチには 1 つの大きな欠点があり、それは、ブラウザー プラットフォームに強く縛られていたことです。 そのため、プラットフォーム固有の API とフレームワーク インターフェイスの間に立つ、抽象化のレベルが必要です。 Angularでは、これらの抽象化は次のような参照タイプの形で提供されています。 ElementRef
、TemplateRef
、ViewRef
、ComponentRef
、ViewContainerRef
です。 この記事では、各参照タイプを詳しく見て、それらを使用して DOM を操作する方法を示します。
@ViewChildLink to this section
DOM 抽象化機能を調べる前に、コンポーネント/ディレクティブ クラス内でこれらの抽象化にアクセスする方法を理解しましょう。 Angular は、DOM クエリーと呼ばれるメカニズムを提供します。 これは @ViewChild
と @ViewChildren
というデコレーターの形で提供されています。 前者は1つの参照を返し、後者は複数の参照をQueryListオブジェクトとして返すだけで、動作は同じです。 この記事の例では、主に ViewChild
デコレータを使用し、その前の @
シンボルは使用しません。
通常、これらのデコレータはテンプレート参照変数と対になっています。 テンプレート参照変数は、単にテンプレート内の DOM 要素への名前付き参照です。 これは、html
要素の id
属性と似たようなものと見なすことができます。 DOM 要素をテンプレート参照でマークし、 ViewChild
デコレータを使用してクラス内でそれを問い合わせます。 以下は基本的な例です。
<>Copy@Component({ selector: 'sample', template: ` <span #tref>I am span</span> `})export class SampleComponent implements AfterViewInit { @ViewChild("tref", {read: ElementRef}) tref: ElementRef; ngAfterViewInit(): void { // outputs `I am span` console.log(this.tref.nativeElement.textContent); }}
ViewChild
デコレーターの基本構文は次のとおりです。
<>Copy@ViewChild(, {read: });
この例では、html
でテンプレート参照名としてtref
を指定し、この要素に関連するElementRef
を受け取っていることが分かります。 AngularはDOM要素の型によって参照型を推測することができるため、2番目のパラメータread
は必ずしも必要ではありません。 例えば、span
のような単純なhtml要素であれば、AngularはElementRef.
を返します。 template
要素であれば、TemplateRef
を返します。 ViewContainerRef
のようないくつかの参照は推測することができず、read
パラメータで明確に要求する必要があります。
Okay, now we know how to query for the references, let’s start exploring them.
ElementRefLink to this section
This is the most basic abstraction.このセクションは、最も基本的な抽象です。 そのクラス構造を観察すると、それが関連付けられているネイティブ要素を保持するだけであることがわかります。 ここで見られるように、ネイティブ DOM 要素にアクセスするのに便利です:
<>Copy// outputs `I am span`console.log(this.tref.nativeElement.textContent);
しかしながら、このような使用法は Angular チームによって推奨されません。 セキュリティ上のリスクがあるだけでなく、アプリケーションとレンダリング レイヤーを緊密に結合するため、複数のプラットフォームでアプリを実行することが困難になるからです。 私は、抽象化を破るのは nativeElement
へのアクセスではなく、textContent
のような特定の DOM API の使用だと考えています。
ElementRef
は ViewChild
デコレーターを使用して任意の DOM 要素に対して返すことができます。 しかし、すべてのコンポーネントはカスタム DOM 要素内でホストされ、すべてのディレクティブは DOM 要素に適用されるので、コンポーネントおよびディレクティブ クラスは依存性注入 (DI) によりホスト要素に関連付けられた ElementRef
のインスタンスを取得することができます。
<>Copy@Component({ selector: 'sample', ...export class SampleComponent{ constructor(private hostElement: ElementRef) { //outputs <sample>...</sample> console.log(this.hostElement.nativeElement.outerHTML); }
したがって、コンポーネントは DI によってそのホスト要素へのアクセスを取得できますが、ViewChild
デコレーターはそのビュー (テンプレート) で DOM 要素への参照を取得するのに最もよく使用されます。 しかし、ディレクティブの場合は逆で、ビューを持たず、通常はアタッチされている要素で直接動作します。
TemplateRef このセクションへのリンク
テンプレートの概念は、ほとんどの Web 開発者にとってなじみ深いものであるはずです。 それは、アプリケーション全体のビューで再利用される DOM 要素のグループです。 HTML5 標準がテンプレート タグを導入する前は、ほとんどのテンプレートは type
属性のいくつかのバリエーションを持つ script
タグにラップされてブラウザに表示されました。 template
タグを使用すると、ブラウザは html
を解析し、DOM
ツリーを作成しますが、レンダリングは行いません。 その後、content
プロパティを通じてアクセスできます。
<>Copy<script> let tpl = document.querySelector('#tpl'); let container = document.querySelector('.insert-after-me'); insertAfter(container, tpl.content);</script><div class="insert-after-me"></div><ng-template> <span>I am span in template</span></ng-template>
Angular はこのアプローチを取り入れ、テンプレートと連携する TemplateRef
クラスを実装しています。 以下はその使用方法です。
<>Copy@Component({ selector: 'sample', template: ` <ng-template #tpl> <span>I am span in template</span> </ng-template> `})export class SampleComponent implements AfterViewInit { @ViewChild("tpl") tpl: TemplateRef<any>; ngAfterViewInit() { let elementRef = this.tpl.elementRef; // outputs `template bindings={}` console.log(elementRef.nativeElement.textContent); }}
フレームワークは、DOM から template
要素を削除し、その場所にコメントを挿入します。 レンダリングすると、このようになります:
<>Copy<sample> <!--template bindings={}--></sample>
それ自体では、TemplateRef
クラスは単純なクラスです。 それは elementRef
プロパティでそのホスト要素への参照を保持し、1つのメソッドを持っています。 createEmbeddedView
. しかし、このメソッドはビューを作成し、その参照を ViewRef
として返すことができるので、非常に便利です。
ViewRef このセクションへのリンク
このタイプの抽象化は Angular ビューを表します。 Angularの世界では、ViewはアプリケーションUIの基本的なビルディングブロックです。 それは、一緒に作成および破棄される要素の最小のグループ化です。
- テンプレートにリンクされている埋め込みビュー
- コンポーネントにリンクされているホスト ビュー
埋め込みビューの作成このセクションへのリンク
テンプレートは単にビュー用の青写真を保持します。 ビューは、次のように前述の createEmbeddedView
メソッドを使用してテンプレートからインスタンス化できます:
<>CopyngAfterViewInit() { let view = this.tpl.createEmbeddedView(null);}
Creating a host viewLink to this section
Host view is created when a component is dynamically instantiated. コンポーネントは ComponentFactoryResolver
:
<>Copyconstructor(private injector: Injector, private r: ComponentFactoryResolver) { let factory = this.r.resolveComponentFactory(ColorComponent); let componentRef = factory.create(injector); let view = componentRef.hostView;}
を使用して動的に作成できます。
Angular では、それぞれのコンポーネントはインジェクターの特定のインスタンスに結合されているので、コンポーネント作成時に現在のインジェクターのインスタンスを渡しています。 また、動的にインスタンス化されるコンポーネントは、モジュールまたはホスト コンポーネントの EntryComponents に追加されなければならないことを忘れないでください。
では、埋め込みビューとホスト ビューの両方を作成する方法を見てきました。 ビューが作成されると、ViewContainer
を使用して DOM に挿入することができます。
ViewContainerRef このセクションへのリンク
ViewContainerRef は、1 つまたは複数のビューを添付できるコンテナを表します。 興味深いのは、Angular はビューを要素の内部に挿入せず、ViewContainer
にバインドされた要素の後に追加していることです。 これは、router-outlet
がコンポーネントを挿入する方法と似ています。
通常、ViewContainer
が作成されるべき場所をマークするための良い候補は ng-container
要素です。 これはコメントとしてレンダリングされるので、DOM に冗長な HTML 要素を導入することはありません。 以下は、コンポーネント テンプレート内の特定の場所に ViewContainer
を作成する例です:
<>Copy@Component({ selector: 'sample', template: ` <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span> `})export class SampleComponent implements AfterViewInit { @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef; ngAfterViewInit(): void { // outputs `template bindings={}` console.log(this.vc.element.nativeElement.textContent); }}
他の DOM 抽象要素と同様、ViewContainer
は element
プロパティを介してアクセスされる特定の DOM 要素に結び付けられます。 上の例では、コメントとしてレンダリングされた ng-container
要素にバインドされているので、出力は template bindings={}
です。
Manipulating views このセクションへのリンク
ViewContainer
は、ビューを操作するための便利な API を提供します:
<>コピーclass ViewContainerRef { ... clear() : void insert(viewRef: ViewRef, index?: number) : ViewRef get(index: number) : ViewRef indexOf(viewRef: ViewRef) : number detach(index?: number) : ViewRef move(viewRef: ViewRef, currentIndex: number) : ViewRef}
我々は以前にテンプレートとコンポーネントから手動で作成できる 2 タイプのビューについて見ました。 ビューを作成したら、insert
メソッドを使用して DOM に挿入することができます。 以下は、テンプレートから埋め込みビューを作成し、ng-container
要素でマークされた特定の場所に挿入する例です。
<>Copy@Component({ selector: 'sample', template: ` <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span> <ng-template #tpl> <span>I am span in template</span> </ng-template> `})export class SampleComponent implements AfterViewInit { @ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef; @ViewChild("tpl") tpl: TemplateRef<any>; ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); this.vc.insert(view); }}
この実装では、結果の html
は次のようになります。
<>Copy<sample> <span>I am first span</span> <!--template bindings={}--> <span>I am span in template</span> <span>I am last span</span> <!--template bindings={}--></sample>
DOM からビューを除去するには、detach
メソッドを使用することができます。 他のすべてのメソッドは説明不要で、インデックスによってビューへの参照を取得したり、ビューを別の場所に移動したり、コンテナーからすべてのビューを削除したりするために使用できます。
ビューの作成このセクションへのリンク
ViewContainer
も自動的にビューを作成する API を提供しています。 4004>
ngTemplateOutlet および ngComponentOutlet このセクションへのリンク
基礎となるメカニズムがどのように動作するかを知ることは常に良いことですが、通常は何らかのショートカットを持つことが望ましいです。 このショートカットは2つのディレクティブの形で提供されています。 ngTemplateOutlet
と ngComponentOutlet
です。 この文章を書いている時点では、両方とも実験的なもので、ngComponentOutlet
はバージョン 4 で利用可能になる予定です。
ngTemplateOutlet このセクションへのリンク
これは DOM 要素を ViewContainer
としてマークし、コンポーネント クラスで明示的にこれを行う必要なしに、テンプレートによって作成された埋め込みビューを挿入するものです。 つまり、ビューを作成して #vc
DOM 要素に挿入する上記の例は、次のように書き直すことができます:
<>Copy@Component({ selector: 'sample', template: ` <span>I am first span</span> <ng-container ="tpl"></ng-container> <span>I am last span</span> <ng-template #tpl> <span>I am span in template</span> </ng-template> `})export class SampleComponent {}
ご覧のように、コンポーネント クラスでビューのインスタンス化コードを使用しないでください。 非常に便利です!
ngComponentOutlet このセクションへのリンク
この指示は ngTemplateOutlet
と似ていますが、違いは埋め込みビューではなく、ホストビュー (コンポーネントをインスタンス化) を作成する点です。 4004>
<>Copy<ng-container *ngComponentOutlet="ColorComponent"></ng-container>
Wrapping upこのセクションへのリンク
この情報はすべて消化するのに大変かもしれないと私は理解しています。 しかし、実際にはかなり首尾一貫しており、ビューを介して DOM を操作するための明確なメンタル モデルをレイアウトしています。 テンプレート変数の参照とともに ViewChild
クエリを使用することにより、Angular DOM の抽象化への参照を取得します。 DOM要素の最も単純なラッパーはElementRef
です。 テンプレートにはTemplateRef
があり、埋め込みビューを作成することができます。 ホストビューは ComponentFactoryResolver
を使って作成された componentRef
でアクセスできます。 ビューは ViewContainerRef
で操作することができます。 手動処理を自動化するディレクティブが2つあります。 ngTemplateOutlet
– は埋め込みビュー、ngComponentOutlet
はホストビュー(ダイナミックコンポーネント)です。
Leave a Reply