エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

AngularのViewChild()の値はビューの初期化まではundefinedである

どうも、最近小規模な失敗談が尽きません…😭 今回はその中から、フレームワーク仕様の理解不足による失敗談を晒していきたいと思います!

ハマったこと

とあるComponentでViewChild()デコレータで取得したプロパティを参照しようとしたところ、undefinedがだった為にその後の処理で例外になった。

今回、以下のソースコードのような内容を書いていました。(実際のコードとは異なります)

import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
  title = 'sample';
  @ViewChild('foo') fooElement;

  ngOnInit() {
    // コンポーネントの初期化時にthis.fooElementを参照したかった
    console.log(this.fooElement.nativeElement.id);
  }
}

this.fooElementにはテンプレートで定義したHTMLElementが代入される…はずでした。

f:id:tansantktk:20210217195517p:plain

しかし、this.fooElement = undefinedだった為、参照エラーに。

ViewChild()とは

テンプレートで定義した要素や子コンポーネントを参照し、プロパティに格納したい時に使うデコレータです。 親コンポーネント側から子コンポーネントのメソッドやプロパティを呼びたい時や、特定要素の値を参照したい時によく使います。

何故undefinedだったのか

ViewChild()で参照しようとしている値が代入されるタイミングは、ビューの初期化が完了した後だった為です。

ビューの初期化が完了した後というのは、ライフサイクルメソッドであるngAfterViewInit()が呼び出されたタイミングということになります。

↓ライフサイクルメソッドについては公式を確認下さい。

delete-circleci-and-firebase--angular-ja.netlify.app

先ほどのソースコードでは、ライフサイクルメソッドngOnInit()の中でViewChild()した値を参照しようとしていました。 しかし、ngOnInit()ngAfterViewInit()より前の段階で呼び出されるメソッド(=ビューの初期化が未完了の状態)である為、 undefinedとして処理が進行してしまっていたわけです。

修正後

import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
  title = 'sample';
  @ViewChild('foo') fooElement;

  ngOnInit() {
    // ngOnInit()内ではthis.fooElementは参照しない
  }

  ngAfterViewInit() {
    // ビューの初期化が完了してからthis.fooElementを参照する
    console.log(this.fooElement.nativeElement.id);
  }
}

f:id:tansantktk:20210217195916p:plain

無事、this.fooElement.nativeElement.idを参照することができました。

ViewChild()は便利なデコレータですが、代入されるタイミングはしっかり意識しながらコーディングが必要です🤤