Angularにて、下記記事で紹介されているようなファイル選択ボタンを作りたかった。
環境
- Angular: 12.0.5
- TypeScript: 4.2.4
- エディタ: VSCode
修正前
<button
mat-raised-button
color="primary"
(click)="onClick()">
<mat-icon>attach_file</mat-icon>
ファイルを選択
</button>
<input #fileInput type="file" style="display:none;">
import { Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('fileInput') fileInput: ElementRef;
onClick(): void {
this.fileInput.nativeElement.click();
}
}
こちらで保存すると、コンパイルエラー(VSCode上でもエラーが出る)
error TS2564: Property 'fileInput' has no initializer and is not definitely
assigned in the constructor.
10 @ViewChild('fileInput') fileInput: ElementRef;
~~~~~~~~~
fileInputに初期化子がなく、コンストラクターで明確に割り当てられていない・・・
解決策
こちらの記事によれば、
TypeScript 2.7 からクラスプロパティの初期化をチェックするstrictPropertyInitialization
オプションが導入され、
tsconfig
のstrictPropertyInitialization
オプションを有効にすると、undefined
を許容していないプロパティがプロパティ宣言時あるいはコンストラクタで初期化されていないときにコンパイルエラーになる。
とのこと。そして
@ViewChild
や@ContentChildren
などは、クラスの初期化時ではなくコンポーネントのビューツリーの解決時に初期化されるので、strictPropertyInitialization
がうまく噛み合わなくなる。
これがコンパイルエラーの原因。
@ViewChild
や@ContentChild
はオプショナルプロパティとして扱うべし
これに言及してくれている記事がほとんどなく解決に苦労しました。ありがとうございます
修正後
というわけで、修正後のコードはこちら。
import { Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
@ViewChild('fileInput') fileInput?: ElementRef; // オプショナルプロパティに
onClick(): void {
if (this.fileInput !== undefined) { // 追加
this.fileInput.nativeElement.click();
}
}
}