かなり初歩的なことだけどAngularの@Input()
周りで少しハマったので忘れないように記しておきます。
バージョン
Angular 9.1.0を対象としています。
何にハマったか
@Input
が付与されたnumber型の変数に渡ってくる値がなぜか文字列になってしまう現象です。
以下のようなコードを書いたとき、countに渡ってくる値がなぜか文字列になってしまいます。
@Component({
selector: 'app-sample',
templateUrl: './sample.component.html',
styles: []
})
export class SampleComponent implements OnChanges {
@Input() public count: number;
ngOnChanges(changes: SimpleChanges): void {
console.log(this.count); // なぜか文字列になって渡ってきている
}
}
上記コンポーネントを呼び出すhtmlは以下のように書いていました。
<app-sample count="5"></app-sample>
原因
Angularでは@Input()
が付与された変数に値を渡すとき、渡す側ではプロパティを[]
で囲って渡す必要があります。
[]
を付けないと文字列定数として渡すことになります。
これはしっかりドキュメントにも記載されています。
Angular 日本語ドキュメンテーション - テンプレート構文#角括弧を忘れずに
解決策
ということで値を渡す側ではプロパティを[]
で囲いましょう。
<app-sample [count]="5"></app-sample>
これで解決。
プロパティバインディングについて
[]
で囲って値を渡す渡し方はプロパティバインディングと呼びます。プロパティバインディングは[プロパティ]=式
のように右辺に式を書いて渡します。
この式はテンプレート式と呼ばれます。テンプレート式には一部を除いてJavaScript式を用いることができます。例えば以下のような式は全部有効です。
<app-sample [count]="5"></app-sample>
<app-sample [count]="5 + 5"></app-sample> <!-- 足し算など可能 -->
<app-sample [isDisabled]="true"></app-sample> <!-- boolean値も可能 -->
<app-sample [count]="totalCount"></app-sample> <!-- コンポーネントに定義したプロパティも指定可能 -->
<app-sample [count]="add(5, 10)"></app-sample> <!-- コンポーネントに定義した関数も指定可能 -->
<app-sample [count]="isDisabled === true ? 10 : 0"></app-sample> <!-- 三項演算子も可能 -->
<app-sample [message]="'メッセージ' + 'です'"></app-sample> <!-- 文字列も指定可能(ただしシングルクォートで囲む必要がある) -->
@Component({
selector: 'app-parent-sample',
templateUrl: './parent-sample.component.html',
styles: []
})
export class ParentSampleComponent {
public totalCount: number = 5;
public add(a: number, b: number): number {
return a + b;
}
}
ただし文字列を渡したいとき、以下の場合に限り[]
で囲まなくても適切に渡すことができます。
- 渡す文字列の値が固定値
- 渡す文字列の値にテンプレート式による表現がない
<!-- 要はhtmlに直接書ける文字列なら[]なしで渡せる -->
<app-sample message="メッセージです"></app-sample>
この辺の話は詳しくは以下を参照してください。
Angular 日本語ドキュメンテーション - テンプレート構文#テンプレート式
余談:HTML属性とDOMプロパティの関係
この辺の話を調べていく上でHTML属性とDOMプロパティの関係をあまり意識してなかったので両者の関係を書いておきます。この両者は全く別の概念になります。
Angularにおけるバインディングを理解する上で2つの関係性を理解しておくことは重要です。
HTML属性はHTMLに書かれる属性値、DOMプロパティはDOMオブジェクトの値です。(そのまま)
少し言い方を変えると、HTML属性は単にHTMLに書かれた静的な値であり、DOMプロパティはDOM構築後に割り当てられる動的な値です。
例を用いて見てみます。例えば以下のようなHTMLを書いたとします。
<input type="text" value="Hello" />
このときのvalueの値について考えます。HTML属性としての値は「Hello」です。このHTMLがレンダリングされるときDOMオブジェクトinput
が生成され、そのオブジェクトはvalue
プロパティを持ちます。DOMプロパティinput.value
には初期値としてHTML属性値で指定した値が割り当てられます。よって最初DOMプロパティはHTML属性値と同じく「Hello」となります。
次にこのテキストを「Good Morning」に編集したとします。このときHTML属性値はもちろん「Hello」のままですが、DOMプロパティは「Good Morning」に変わります。このようにHTML属性値は変更されないのに対し、DOMプロパティは動的に変わっていきます。これがHTML属性とDOMプロパティの1つ目の関係になります。
ちなみに先程DOMプロパティにはHTML属性値で指定した値が初期値として割り当てられると書きましたが、これはHTML属性が標準のものに限ります。もう少し厳密に言うと、標準の属性のみDOMプロパティが作られます。
以下のコードを例に見てみます。
<input type="text" value="Hello" something="Yeah"/>
このとき、生成されるDOMオブジェクトinput
はvalue
プロパティは持ちますが、something
プロパティは持ちません。あくまで標準の属性だけがDOMプロパティにも引き継がれます。
標準の属性については以下を参照してください。
HTML 属性リファレンス - HTML: HyperText Markup Language | MDN
またもう一つの関係としてHTML属性とDOMプロパティは必ずしも同じ名前ではないです。
例えば<td>
タグにはテーブルセルの結合列数を表すcolspan
という属性があります。これがDOMプロパティになるとcolSpan
という名前に変更されます(s
が大文字になる)。
上記を踏まえて、Angularのデータバインディングは全てDOMプロパティへの代入になります。
そのため、先程の<td>
タグのcolSpan
を動的に変更したい場合はcolspan
ではなく、colSpan
に代入しなければいけません。
<!-- 'colspan' ではなく 'colSpan' -->
<tr><td [colSpan]="2">colspan 2</td></tr>
@Input()
を付与した変数に値を渡すときもDOMプロパティに値を渡していることになります。この辺は意識しておくと良いでしょう。
<app-sample [count]="5"></app-sample>