Angular 2 @Inputのアレコレ
おはようございます、@armorik83 です。今回はAngular 2の特徴的な API のうち@Input
を取り上げます。
本稿執筆時点での最新バージョンは Angular 2 alpha.48 なのですが、ビルドが通らない致命的な問題があったため、仕方なく alpha.47 で動かしています。@Input
については a47, 48 で破壊的変更はありません。
【追記 151226】Angular 2 beta.0 に対応しました。
@Input
とは
@Input
(Docs) は、Angular 2 の Component に属性を定義する API です。つまり、HTML での独自の要素に対して独自の属性を持たせることができます。
<bank-account bankName="RBC" accountId="4747"></bank-account>
たとえば上記のように<bank-account>
という Component を定義したとすると、そこにbankName
属性とaccountId
属性を持たせるために使うのが@Input
API です。この時、データが自動的に子 Component にバインドされるのが特徴です。
例
AngularJS 1.4 Directive bindToController
AngularJS 1.4 ではbindToController
が使用できますが、AngularJS 経験者ならば同じようなものとイメージすればよいでしょう。
class BankAccount {
bankName: string;
accountId: string;
}
angular.app("my-app").directive("bank-account", () => {
return {
restrict: "E",
controller: BankAccount,
controllerAs: "BankAccount",
scope: {},
bindToController: {
bankName: "@",
accountId: "@"
}
};
});
AngularJS 1.4 で書くならば上のようになります。これを Angular 2 で書くと次の通りです。
Angular 2 Component + Input
import {Component, Input} from 'angular2/core';
import {CORE_DIRECTIVES} from 'angular2/common'
@Component({
selector: `bank-account`,
template: `
<p>Bank Name: {{bankName}}</p>
<p>Account ID: {{accountId}}</p>
`
})
class BankAccount {
@Input() bankName: string;
@Input() accountId: string;
constructor() {
// ...
}
}
bindToController
とclass
内プロパティで 2 重に記述する必要があった点から改善され、とても書きやすくなりました。@Input()
アノテーションのおかげで、どのプロパティがインタフェースとして開いているか、分かりやすいですね。
これらは、TS 内ではthis.bankName
などで扱うことができますし、テンプレート内では<p>{{bankName}}</p>
とできるのです。そう、もう面倒くさい$scope
もcontrollerAs
も考えなくていいのです。AngularJS の本質のみに特化して記述でき、とても洗練されました。
動くサンプルを置いておきます。
アレコレ試してみる
Input として宣言していない属性に値を渡すと?
<bank-account
bankName="RBC"
accountId="4747"
swiftCode="ROYCCAT2"
></bank-account>
swiftCode
なんて属性を勝手に作り値を入れてみました。@Input() swiftCode
は宣言していません。この場合、当然ですがバインディングされず、表示は空欄のままです。つまり表示に問題があった場合、属性名にミスがないか、@Input
が適切に宣言されているか、テンプレートに typo はないか、などを確認すればよいでしょう。
属性名と class 内のプロパティを別の名称にしたい
属性名としてはaccountId
としたいが Component 内部ではthis.id
として扱いたいとします。こんな場合も簡単です。
@Component({
selector: `bank-account`,
template: `
<p>Bank Name: {{bankName}}</p>
<p>Account ID: {{id}}</p>
`
})
class BankAccount {
@Input() bankName: string;
@Input('accountId') id: string;
ngOnInit() {
console.log(this.bankName, this.id, this.accountId); // this.accountIdはundefined
}
}
@Input('accountId')
のように Input の引数に文字列を与えると、そのまま属性名となります。このとき AngularJS ではキャメルケースとチェインケース(ハイフン)を意識的に扱う必要があり煩雑でしたが、Angular 2 では改善され、すべてキャメルケースで記述できるようになりました。めでたい!
以下に対応をまとめておきます。
-
@Input() accountId: string;
- 属性名:
accountId
- プロパティ名:
accountId
- 属性名:
-
@Input('accountId') id: string;
- 属性名:
accountId
- プロパティ名:
id
- 属性名:
-
@Input('account-id') id: string;
- 属性名:
account-id
- プロパティ名:
id
- 属性名:
ハイフンがお好きな方でも@Input
引数にハイフン付きで指定すれば大丈夫。でも標準はキャメルケースとなったので標準に慣れておくのもよいでしょう。
ちなみに AngularJS では{bindToController: {id: "@accountId"}}
といったことをしてました。あれはとても煩雑だった…。
値を式として評価したい
ここまでに紹介した例では、バインディングは全て string 型として行われていました。AngularJS には@&=
といった記号でバインド方法を指定できましたが、これと同じようなことは Angular 2 でも可能です。
ただしクセがあるので、実際に Angular 2 を用いた開発を進める際には、チーム内での統一的なルールを設けたほうがよさそうと予想します。
<example attrA="1+1" attrB="{{1+1}}" [attrC]="1+1"></example>
<example attrA="true" attrB="{{true}}" [attrC]="true"></example>
<example attrA="false" attrB="{{false}}" [attrC]="false"></example>
このような例を用意してみました。
-
attrA="1+1"
は文字列として評価。"1+1"
という string リテラルが格納される。 -
attrB="{{1+1}}"
は{{}}
内評価後に文字列として評価。"2"
という string リテラルが格納される。 -
[attrC]="1+1"
は式評価。2
という number リテラルが格納される。
実はすべて異なります。値の{{}}
と属性名の[]
はどちらも式評価をするものの、バインディング結果が異なるところに注意してください。特に罠になりそうなのがattrB="{{false}}"
。string として格納されるので truesy 値となってしまいます。文字列評価と式評価を使い分ける場面としては主に、XSS 対応などが挙げられます。外部から送られてくる文字列を式評価できてしまうのは問題なので、その際は[]
を使わない文字列評価にすべきでしょう。(もしくは別途 safe string を生成してからなら OK)
私の個人的な推奨としては、常に[attrC]
のルールを用いることです。なお、その時に文字列を渡したい場合は[attrC]="'string'"
のようにシングルクオーテーションで挟む必要があります。[]
表記にどうしても抵抗がある方はbind-
を付けてbind-attrC="1+1"
としても同様となるので安心してください。
まとめ
-
@Input()
で属性宣言もエイリアス名の指定も簡単 - AngularJS のようにキャメルかハイフンかで迷う必要はない
- テンプレート側では
[]
で属性名を括ることで式評価
AngularJS の「学習コストが高い!」と言われがちな部分を整理してきました。シンプルでいいですね!このように Angular 2 は 1 系のよい所を洗練させ、多くの面倒くさい点を合理的に解決しています。
次回、私の担当する 12/14 の記事では@Input
の対となる API@Output
について紹介しますのでお楽しみに。それではまた。
Discussion