Angularのテンプレート変数とViewChild
Angularのテンプレート変数(HTML内で#で名前を付けるもの)について、入門時に少しふれただけで曖昧なままだったのでここで整理します。 公式ページを参考に進めていきます。
基本の使い方
例えば、テンプレート(HTML)内で次のようにして変数を宣言数することができます。
component.html
<input #yourname type="text"/>そして、同じコンポーネント内で次のようにすることで、定義した「yourname」を使って該当のinputへの参照ができます。ここでは要素への参照なので、valueを使って入力された文字を取得しています。
component.html
<span>こんにちは</span><span>{{yourname.value}}</span><span>さん</span>*ngIfや*ngFor、あとで紹介する<ng-template>といった構造ディレクティブはテンプレート変数のスコープの境界となります。このあたりの挙動はJavaScriptでletで定義した変数とforやifとの関係と同じです。
これらはHTML要素だけでなく、テンプレートやコンポーネントなどにも使えます。
ng-template
テンプレートでの使用例の前に、Angularのテンプレートであるng-templateについての情報整理をします。
ng-templateはHTMLのtemplate要素同様に、通常はレンダリングしない要素を定義するものです。
*ngIfなどの構造化ディレクティブでは、これを暗黙のうちに生成して機能を実現しています。
Algularの構造化ディレクティブにつく*印は短縮を意味します。次の2つはその短縮形と短縮前の記述方法になります。短縮形を展開するとng-templateが現れます。
<div *ngIf="hero" class="name">{{hero.name}}</div><ng-template [ngIf]="hero"> <div class="name">{{hero.name}}</div> </ng-template>
もう少し理解を深めるために、ngIfディレクティブを自作してみます。先の公式参考サイトではngIfの逆を行うunlessを作っているのでそれを参考にしています。
Angularプロジェクトのappディレクトリにmyngif.directive.tsファイルができ、app.module.tsに次のような記述が追加されます。
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyngifDirective } from './myngif.directive';
@NgModule({
declarations: [
AppComponent,
MyngifDirective
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
myngif.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
@Input() set appUnless(condition: boolean) {
if (condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (!condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
@Inputは親コンポーネントから子コンポーネントへデータを渡す際に使うものでした。子コンポーネントとしてセッターを使って値を受け取ります。
trueを受け取った際ビューがなければ作成、falseを受け取ったときビューがあれば削除します。作成時は、コンストラクタに設定したteplateRefを利用しテンプレートをからビューコンテナに挿入しています。削除時はビューコンテナをクリアします。
ビューコンテナにアクセスするためにコンストラクタでViewContainterRefを定義しています。
このコードは次のような記述で稼働を確認することができます。
app.component.html
<div *appMyngif="true">短縮形</div>
<ng-template [appMyngif]="true"><div>短縮形を展開</div></ng-template>
ng-templateでテンプレート変数を使う
少し寄り道が長くなってしまいましたが、このng-template内にテンプレート変数を記述すると好きな箇所でテンプレートを利用できるようになります。
定義したng-templateを使う場合は、ng-containerとngTemplateOutletを使って次のように記述します。
app.component.html
<ng-template [appMyngif]="true"><div>HTML内で利用する</div></ng-template>
<ng-container *ngTemplateOutlet="temp"></ng-container>
ngFormで使う
通常form要素でテンプレート変数を用いるとHTMLFormElementになりますが、form要素の中で、「#変数名="ngForm"」とすると、変数はNgFormディレクティブへの参照となります。
これは他のディレクティブやコンポーネントでも利用できます。
コンポーネント(.ts)から利用する
テンプレート(HTML)で設定したテンプレート変数を、コンポーネント(.ts)から参照したい場合はViewChildデコレータを利用します。
test.component.ts
import { Component, OnInit,ViewChild,TemplateRef,ElementRef } from '@angular/core';
export class TestComponent implements OnInit {
//テンプレートの参照を取得するなら
@ViewChild('変数名') public temp: TemplateRef<any> | undefined;
//エレメントへの参照はElementRefで受けます
@ViewChild('変数名') public element: ElementRef | undefined;
...
//例えばinput要素の値を取得するなら次のようにします。
//?はundefinedのコンパイルエラー回避です。undefinedの際はプロパティの参照はしません
//宣言時に 「public element!」としてundefineddではないと宣言してしまうか、
//anyで受けても回避できます。
console.log(this.element?.nativeElement.value);
...