AngularのコアAPIであるViewChild
、ViewChildren
デコレーターを自在に使いこなせるようになると、一段階レベルアップしたカスタムコンポーネントの作成が可能になります。
目次
ViewChild
とは
ViewChild
はクラス内プロパティに適用するタイプのデコレーターで、テンプレート側の要素をコンポーネント側から参照したい場合に利用します。
ViewChild
の使い方 – 基本
コンポーネント内にて下記の様に使用します。
// my-component.ts
@Component({
selector: 'my-component',
templateUrl: './my-component.html',
})
export class MyComponent implements AfterViewInit
{
// テンプレート内に存在する参照変数`elm`を、プロパティ`element`に紐付ける
@ViewChild('elm') public element: HTMLInputElement;
public ngAfterViewInit(): void
{
// this.elementにてビューDOM内のHTMLInputElementにアクセス出来ます
console.log(this.element);
}
}
上記MyComponent
クラスのテンプレートは下記の様な感じになります。
// my-component.html
<div>
<input #elm type="text" name="username">
</div>
#
+変数名
の記述によってテンプレート内に参照変数を宣言出来ます。この場合#elm
という記述でelm
という名前の参照変数を宣言しています。
ほとんどの場合Angularは参照変数の値を宣言された要素に設定するため、この場合elm
にはHTMLInputElement
が設定されます。
また、参照変数はref-
+変数名
でも宣言することが出来ます。(この場合、ref-elm
)
最後にコンポーネント側にてViewChild
デコレーターを任意のクラス内プロパティに適用してあげれば、テンプレート内の要素と通信が出来るようになります。
その際ViewChild
デコレーターには引数として参照変数名を渡してあげます。(※ @ViewChild('elm')
)
ViewChild
の使い方 – よくある使い方1
おそらくViewChild
の最も多いであろう使われ方として、NgForm
ディレクティブと併用する方法があります。
コンポーネント側における記述は前項の基本と大きくは変わりませんが、テンプレート側が下記の様になります。
// my-component.html
<div>
<form #elm="ngForm" (ngSubmit)="submit()">
<input type="text"
name="username"
ngModel>
<button type="submit">送信</button>
</form>
</div>
大きな違いは参照変数elm
が<form>
要素対して設定されている点、またelm
に対してngForm
という値が設定されている点です。
参照変数に対して有効なディレクティブ名を指定すると、指定したディレクティブのインスタンスがバインドされるようになります。更にその要素に対して指定したディレクティブが適用されます。
つまり<form #elm="ngForm">
によって<form>
要素に対してNgForm
ディレクティブが適用され、更に適用後のNgForm
ディレクティブのインスタンスを参照変数elm
を通じて参照出来るようになります。
※参考:[Angular – NgForm](https://angular.io/api/forms/NgForm)
ちなみにコンポーネント側はこんな感じです。
// my-component.ts
@Component({
selector: 'my-component',
templateUrl: './my-component.html',
})
export class MyComponent implements AfterViewInit
{
// テンプレート内に存在する参照変数`elm`を、プロパティ`element`に紐付ける
@ViewChild('elm') public form: NgForm;
public ngAfterViewInit(): void
{
// this.formを通じてNgFormのインスタンスを参照出来ます
console.log(this.form);
}
public submit(): void
{
// this.formを通じてNgFormのインスタンスを参照出来ます
console.log(this.form);
}
}
ViewChild
の使い方 – よくある使い方2
次は<form>
要素の内容が動的に書き換わるケースです。
ユーザーからの操作に応じて、動的に入力要素が増減するようなフォームを考えます。
まずテンプレート側は下記のとおりです。
// my-form-group.html
<div>
<form #f="ngForm" (ngSubmit)="submit()">
<div *ngFor="let item of items">
<my-input [f]="f" [item]="item"></my-input>
</div>
<button type="submit">送信</button>
<button type="button" (click)="add()">追加</button>
</form>
</div>
コンポーネント内のitems
の個数に応じて<my-input>
が動的に増減するようになっています。
<my-input>
に対してはNgForm
のインスタンスと、item
を渡すようにします。
追加ボタンを押すことで<my-input>
を追加、送信ボタンが実行された際にNgForm
内の値をコンソールに出力するだけの簡単なコンポーネントを作ってみます。
// my-form-group.ts
@Component({
selector: 'my-form-group',
templateUrl: './my-form-group.html'
})
export class MyFormGroup
{
@ViewChild('f') public f: NgForm;
public items: object[] = [];
public add(): void
{
this.items.push({ name: `input.${this.items.length}`, value: '' })
}
public submit(): void
{
console.log(this.f.value);
}
}
<my-input>
のテンプレート、及びコンポーネントは下記のとおりです。
// my-input.html
<div *ngIf="item">
<input type="text"
name="{{item.name}}"
ngModel="{{item.value}}">
</div>
// my-input.ts
@Component({
selector: 'my-input',
templateUrl: './my-input.html'
})
export class MyInput
{
@Input() public f: NgForm;
@Input() public item: object;
}
この状態でMyFormGroup
コンポーネントの追加ボタンをクリックし<my-input>
を追加、追加されたinput
要素にテキストを入力した上で送信ボタンを押します。
すると予想に反して、コンソールには空のオブジェクトが表示されますが、これは後から動的に追加されたNgModel
をNgForm
が検知出来ていないために発生します。
そのため、MyInput
コンポーネントを下記の様に拡張してあげる必要があります。
// my-input.ts
@Component({
selector: 'my-input',
templateUrl: './my-input.html'
})
export class MyInput
{
// 追加
@ViewChild(NgModel) set onModelDetected(m: NgModel) {
if (m && !this.f.controls[this.item['name']]) {
this.f.addControl(m);
}
}
@Input() public f: NgForm;
@Input() public item: object;
}
ViewChild
デコレーターの引数に対して参照変数名ではなくクラス型を指定した場合、そのクラス型と一致する要素が検出された瞬間、プロパティに対してインスタンスがバインドされるようになります。
また上記例のようにプロパティではなくセッターとして宣言していた場合、一致する要素が検出された瞬間にそのセッターが呼ばれるようになります。
検出された要素のインスタンスはセッターの引数に渡されます。
上記例ではViewChild
デコレーターが対象の要素を検出し次第、NgForm
に対して順次NgModel
を登録しています。これによりMyFormGroup
のsubmit()
にて正常に値を検出できるようになります。
以上がViewChild
の代表的な使い方となります。
ViewChildren
とは
ViewChildren
はクラス内プロパティに適用するタイプのデコレーターで、ViewChild
と同様、テンプレート側の要素をコンポーネント側から参照したい場合に利用します。
ViewChildren
という名の通り、検出したい要素がビューDOM内に複数存在する場合にはViewChild
ではなくViewChildren
を利用します。
ViewChildren
の使い方
ViewChild
の例で出てきたmy-form-group.html
を見てみます。
// my-form-group.html
<div>
<form #f="ngForm" (ngSubmit)="submit()">
<div *ngFor="let item of items">
<my-input [f]="f" [item]="item"></my-input>
</div>
<button type="submit">送信</button>
<button type="button" (click)="add()">追加</button>
</form>
</div>
このテンプレートではitems
の個数に応じて<my-input>
が増減しますが、ViewChildren
デコレーターを利用することでこの増減する要素全てを取得することが出来るようになります。
my-form-group.ts
を下記のように修正します。
@Component({
selector: 'my-form-group',
templateUrl: './my-form-group.html'
})
export class MyFormGroup
{
@ViewChild('f') public f: NgForm;
// 追加
@ViewChildren(MyInput) public myInputs: QueryList<MyInput>;
public items: object[] = [];
public add(): void
{
this.items.push({ name: `input.${this.items.length}`, value: '' })
}
public submit(): void
{
// 追加されたmy-input全て格納されています。検出されたmy-inputはQueryListインスタンスを通じて参照可能です。
console.log(this.myInputs);
}
}
ViewChildren
デコレーターを適用したプロパティにはQueryList
がセットされます。QueryList
はいわゆるコレクションクラスとなっており、検出された複数の要素をより効率的に参照できるようなAPIを提供しています。
とても便利なので、詳細な使い方はこちら(Angular – QueryList)を確認してみて下さい。
最後に
Angularのカスタムコンポーネント作成において@Input
、@Output
といった基本的なデコレーターに加えて@ViewChild
、@ViewChildren
を使いこなせるようになると、よりコンポーネント作成が捗るかと思います!是非理解して使いこなしましょう(`・∀・´)ゞ