Marginalia

Angular 2のローカル変数とexportAs

Angular 2にはlocal variables(ローカル変数)という機能があります。 公式のチュートリアルやデベロッパーガイドを読んでいると突然登場してみなさんを惑わしているかもしれません。 しかし、この機能はAngular 2を使いこなす上でとても重要なものなので、ぜひ知っておきましょう。

ローカル変数と#シンタックス

ローカル変数とは、コンポーネントのテンプレート中で定義して使用できる変数のことです。 ローカル変数には原則として、ローカル変数を定義した要素のインスタンスが代入されます。 ローカル変数の定義は#シンタックスを使います。

次の例では、input要素をローカル変数iとして定義し、 ボタンをクリックするときにinput要素の値をコンポーネントに渡しています。

@Component({
  selector: 'my-app',
  template: `
    <input type="text" #i>
    <button (click)="submit(i.value)">submit</button>
    <p>
      {{ value }}
    </p>
  `
})
export class App {
  value: string;
  
  submit(value: string) {
    this.value = value;
  }
}

ここでローカル変数iHTMLInputElementのインスタンスになっています。 ローカル変数を使うと、データバインディングを行うことなく、要素が持つプロパティや属性を直接使うことができます。

var-*シンタックス

#シンタックスは簡単に書けて便利ですが、通常だと#記号はHTMLの中で使うことができないため、 Angular以外の何らかのツールを使おうとするとエラーを引き起こすことがあります。 そこで正常なHTMLとしての体裁を守ったままローカル変数を定義する方法として、var-*シンタックスも用意されています。

var-*シンタックスを使って先程のテンプレートを書き直すと次のようになります。

@Component({
  selector: 'my-app',
  template: `
    <input type="text" var-i>
    <button (click)="submit(i.value)">submit</button>
    <p>
      {{ value }}
    </p>
  `
})

ローカル変数とフォーム

Angular 2のローカル変数は、HTML要素のインスタンスを簡単に得ることができるので、 form要素やvideo要素といった、メソッドを持つ複雑な要素と併用することでとても便利になります。 公式のチートシートにはvideo要素をローカル変数に代入してplay()メソッドを使っている例が紹介されています。

<video #movieplayer ...>
  <button (click)="movieplayer.play()">
</video>

同じようにform要素をローカル変数に代入して便利に使ってみましょう! 次の例ではform要素をローカル変数fに代入し、ボタンのクリックイベントでreset()メソッドを呼び出しています。

@Component({
  selector: 'my-app',
  template: `
    <form #f>
      <input type="text">
      <button (click)="f.reset()">reset</button>
    </form>
  `
})
export class App {
}

一切スクリプトを書かずにフォームのリセットが実装できました! 初めは見慣れない#記号に戸惑うかもしれませんが、実はとても簡単で便利なものだということがわかってきましたか?

exportAs属性

ここまでのローカル変数は定義された要素のインスタンスを引き出すだけでしたが、 ローカル変数はexportAsという機能によってさらに強力な機能になります。さっそく見ていきましょう!

exportAs属性はディレクティブのメタデータの1つで、 ローカル変数にHTML要素ではなくディレクティブのインスタンスを代入させたいときに使います。 Angular 2に組み込まれているngFormディレクティブはexportAsを活用しているいいサンプルです。 ngFormディレクティブのセレクタは<form>に一致するようになっていて、 HTML標準のform要素が自動的にngFormディレクティブで拡張されています。

@Directive({
  selector: 'form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]',
  bindings: [formDirectiveProvider],
  host: {
    '(submit)': 'onSubmit()',
  },
  outputs: ['ngSubmit'],
  exportAs: 'ngForm'
})

angular/ng_form.ts at master · angular/angular

ここで、exportAs: 'ngForm'というメタデータの設定に注目してください。 ngFormform要素に一致しますが、ただ<form #f>のようにローカル変数を定義しても代入されるのはHTMLFormElementのインスタンスだけです。 form要素に隠れているngFormディレクティブのインスタンスを得るには、<form #f="ngForm">という定義を行います。 つまり、exportAsで指定された名前をキーに、ディレクティブのインスタンスを得ることができるのです。

ngFormのインスタンスを使うと、フォームの操作がとても簡単になります。次の例ではフォームに入力された値をJSONオブジェクトとして取り出して表示しています。

@Component({
  selector: 'my-app',
  template: `
    <form #f #ngf="ngForm" (ngSubmit)="submit(ngf.value)">
      <input type="text" ngControl="name">
      
      <button type="submit">submit</button>
      <button (click)="f.reset()">reset</button>
    </form>
    <p>{{ value | json }}</p>
  `,
  directives: []
})
export class App {
  value: any;
  
  submit(value: any) {
    this.value = value;
  }
}

ローカル変数fは何も値を与えていないので、form要素のインスタンスになりますが、 ローカル変数ngfngFormをキーにngFormディレクティブのインスタンスが代入されます。 ngFormディレクティブはフォーム全体の値をオブジェクトとして扱えるvalueプロパティを持っているので、 ngSubmitイベントでコンポーネントに値を渡しています。

このように、テンプレート中でディレクティブのメソッドやプロパティにアクセスできるのがローカル変数とexportAsの力です。

exportAsを使ってみよう

もちろん自分で作るディレクティブにもexportAsを使うことができます。 最後に自作ディレクティブをローカル変数として扱う例を紹介します。

MyDivディレクティブは、div要素に一致するセレクタと、自身のtext-transformスタイルを切り替えるメソッドを持っています。 そしてインスタンスをmyDivとして公開しています。

@Directive({
  selector: "div",
  exportAs: "myDiv"
})
class MyDiv {
  
  constructor(private element: ElementRef, private renderer: Renderer) {
  }
  
  toUpper() {
    return this.renderer.setElementStyle(this.element.nativeElement, "text-transform", "uppercase");
  }
  
  toLower() {
    return this.renderer.setElementStyle(this.element.nativeElement, "text-transform", "lowercase");
  }
  
  reset() {
    return this.renderer.setElementStyle(this.element.nativeElement, "text-transform", "");
  }
}

これをコンポーネントから使うと、次のようになります。

@Component({
  selector: 'my-app',
  template: `
    <div #d="myDiv">Angular 2</div>
    <button (click)="d.toUpper()">toUpper</button>
    <button (click)="d.toLower()">toLower</button>
    <button (click)="d.reset()">Reset</button>
  `,
  directives: [MyDiv]
})
export class App {
}

こちら で実際に動くサンプルを見ることができます。

まとめ

Angular 2のローカル変数について基礎的な部分を紹介し、exportAsを活用することで機能的なディレクティブを作れることがわかってもらえたと思います。 ローカル変数を使うとコンポーネントのコード量を減らし、テンプレート内で直感的にHTML要素やディレクティブのインスタンスを扱うことができます。 ぜひ活用してみてください。