← 解説「viewChild() — テンプレートの要素をクラスから取得する」に戻る
サンプルコードで身につける: viewChild() — テンプレートの要素をクラスから取得する
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1ボタンから入力欄へフォーカスを移す
viewChild.required でテンプレートの #inputEl を取得し、クラス側のメソッドから focus() を呼ぶ基本形です。取得結果はシグナルなので、使うときは inputEl() と関数として呼んでから nativeElement に触ります。
import { Component, viewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-reply-form',
template:
'<button (click)="startReply()">返信する</button>' +
'<textarea #inputEl placeholder="返信を入力"></textarea>',
})
export class ReplyFormComponent {
// シグナルとして取得。読むときは inputEl() と呼ぶ
inputEl = viewChild.required<ElementRef>('inputEl');
startReply() {
this.inputEl().nativeElement.focus();
}
}
2子コンポーネントを型で取得する
viewChild にはコンポーネントのクラスも指定できます。参照変数を付けなくても型で子が見つかり、親のメソッドから子のpublicメソッドを呼べます。タイマーやカルーセルのような「操作の指示を受ける部品」でよく使う形です。
import { Component, viewChild, signal } from '@angular/core';
@Component({
selector: 'app-timer',
template: '<p>経過: {{ seconds() }} 秒</p>',
})
export class TimerComponent {
seconds = signal(0);
reset() { this.seconds.set(0); }
}
@Component({
selector: 'app-workout',
imports: [TimerComponent],
template:
'<app-timer />' +
'<button (click)="restart()">リセット</button>',
})
export class WorkoutComponent {
// クラスを指定すると、その型の子コンポーネントが手に入る
timer = viewChild.required(TimerComponent);
restart() {
this.timer().reset(); // 子のpublicメソッドを呼べる
}
}
3存在しないかもしれない要素は required なしで
@if の中にある要素は、条件次第でDOMに存在しません。required 付きで取得すると実行時エラーになるため、required なしの viewChild で受けて undefined を確認してから使います。「必ずある要素か」で使い分けるのがルールです。
import { Component, viewChild, signal, ElementRef } from '@angular/core';
@Component({
selector: 'app-detail-panel',
// #panel は isOpen() が true のときしか存在しない
template:
'<button (click)="isOpen.set(!isOpen())">開閉</button>' +
'@if (isOpen()) {' +
' <div #panel>詳細情報</div>' +
'}',
})
export class DetailPanelComponent {
isOpen = signal(false);
// required なしなら「ElementRef | undefined」として受け取れる
panel = viewChild<ElementRef>('panel');
logHeight() {
const el = this.panel();
if (el) {
console.log('高さ:', el.nativeElement.offsetHeight);
}
}
}
4effect と組み合わせて出現に反応する
viewChild の結果はシグナルなので、effect の中で読めば「要素が現れたら実行する」処理が書けます。@if で後から現れるパネルに、表示された瞬間フォーカスを移す例です。ライフサイクルフックを使わず宣言的に書けるのが新APIの利点です。
import { Component, viewChild, signal, effect, ElementRef } from '@angular/core';
@Component({
selector: 'app-inline-editor',
template:
'<button (click)="editing.set(true)">編集する</button>' +
'@if (editing()) {' +
' <input #editEl placeholder="新しい名前">' +
'}',
})
export class InlineEditorComponent {
editing = signal(false);
editEl = viewChild<ElementRef>('editEl');
constructor() {
// 入力欄がDOMに現れたタイミングでフォーカスを移す
effect(() => {
this.editEl()?.nativeElement.focus();
});
}
}
5まずバインディングで書けないか考える
viewChild で classList を操作するのは、[class.xxx] バインディングで書ける典型的なやりすぎ例です。同じ結果ならバインディングの方が短く安全で、Angularが変更を追跡できます。DOM直接操作はフォーカスや測定など本当に必要な場面に絞りましょう。
import { Component, signal } from '@angular/core';
// ×: viewChild で classList.add / remove を自分で呼ぶ
// (Angularの外でDOMを書き換えるので追跡できず、コードも長い)
// ○: 同じことは [class.xxx] バインディングで宣言的に書ける
@Component({
selector: 'app-highlight-note',
template:
'<p [class.highlighted]="active()">大事なメモ</p>' +
'<button (click)="active.set(!active())">強調を切り替え</button>',
styles: ['.highlighted { background: yellow; }'],
})
export class HighlightNoteComponent {
active = signal(false);
}