Angular2

Angular 2 @Inputのアレコレ

More than 3 years have passed since last update.

おはようございます、@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属性を持たせるために使うのが@InputAPIです。この時、データが自動的に子Componentにバインドされるのが特徴です。



AngularJS 1.4 Directive bindToController

AngularJS 1.4ではbindToControllerが使用できますが、AngularJS経験者ならば同じようなものとイメージすればよいでしょう。


ng1.ts


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


ng2.ts

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() {
// ...
}
}


bindToControllerclass内プロパティで2重に記述する必要があった点から改善され、とても書きやすくなりました。@Input()アノテーションのおかげで、どのプロパティがインタフェースとして開いているか、分かりやすいですね。

これらは、TS内ではthis.bankNameなどで扱うことができますし、テンプレート内では<p>{{bankName}}</p>とできるのです。そう、もう面倒くさい$scopecontrollerAsも考えなくていいのです。AngularJSの本質のみに特化して記述でき、とても洗練されました。

動くサンプルを置いておきます。

http://plnkr.co/edit/YVzuUROemBx1rpJM9FIa


アレコレ試してみる


Inputとして宣言していない属性に値を渡すと?

http://plnkr.co/edit/G1h18mWJEei7ajtIBIOW

<bank-account bankName="RBC" accountId="4747" swiftCode="ROYCCAT2"></bank-account>

swiftCodeなんて属性を勝手に作り値を入れてみました。@Input() swiftCodeは宣言していません。この場合、当然ですがバインディングされず、表示は空欄のままです。つまり表示に問題があった場合、属性名にミスがないか、@Inputが適切に宣言されているか、テンプレートにtypoはないか、などを確認すればよいでしょう。


属性名とclass内のプロパティを別の名称にしたい

http://plnkr.co/edit/KiT0QEPBdXFGYGwSYZqc

属性名としてはaccountIdとしたいがComponent内部ではthis.idとして扱いたいとします。こんな場合も簡単です。


ng2.ts

@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"}}といったことをしてました。あれはとても煩雑だった…。


値を式として評価したい

http://plnkr.co/edit/tmm3EHhH2SiAZPBhXkcf

ここまでに紹介した例では、バインディングは全て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について紹介しますのでお楽しみに。それではまた。