Exploring Angular DOM manipulation techniques using ViewContainerRef

Angular で DOM を扱うことについて読むときはいつも、これらのクラスの 1 つかいくつかが言及されているのを目にします。 ElementRefTemplateRefViewContainerRef などです。 残念ながら、これらのいくつかは Angular のドキュメントや関連記事で取り上げられていますが、全体的なメンタルモデルの説明や、これらがどのように一緒に動作するかの例はまだ見つかっていません。 この記事はそのようなモデルを説明することを目的としています。

Renderer とビュー コンテナーを使用した Angular での DOM 操作についてより深い情報をお探しの場合は、NgVikings で私の講演をチェックしてください。 または、動的 DOM 操作に関する詳細な記事をお読みください。 Angular で DOM を操作する: 予期しない結果と最適化テクニック

angular.js の世界から来た人は、DOM を操作するのがかなり簡単だったことをご存知でしょう。 Angular は DOM elementlink 関数に注入し、コンポーネントのテンプレート内の任意のノードを照会し、子ノードを追加または削除し、スタイルを変更することなどが可能でした。 しかし、このアプローチには 1 つの大きな欠点があり、それは、ブラウザー プラットフォームに強く縛られていたことです。 そのため、プラットフォーム固有の API とフレームワーク インターフェイスの間に立つ、抽象化のレベルが必要です。 Angularでは、これらの抽象化は次のような参照タイプの形で提供されています。 ElementRefTemplateRefViewRefComponentRefViewContainerRefです。 この記事では、各参照タイプを詳しく見て、それらを使用して 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 の使用だと考えています。

ElementRefViewChild デコレーターを使用して任意の 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 メソッドを使用してテンプレートからインスタンス化できます:

<>Copy
ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null);}

Creating a host viewLink to this section

Host view is created when a component is dynamically instantiated. コンポーネントは ComponentFactoryResolver:

<>Copy
constructor(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 抽象要素と同様、ViewContainerelement プロパティを介してアクセスされる特定の 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つのディレクティブの形で提供されています。 ngTemplateOutletngComponentOutlet です。 この文章を書いている時点では、両方とも実験的なもので、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