🅰️

Angular 2 @Inputのアレコレ

2021/07/04に公開

おはようございます、@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について紹介しますのでお楽しみに。それではまた。

Discussion