トピック
Angular2 CORE DOCUMENTATIONのGUIDEの翻訳です。
- DOCUMENTATION OVERVIEW - ドキュメント概要 -
- ARCHITECTURE OVERVIEW - 構文概要 -
- DISPLAYING DATA - データ表示 -
- USER INPUT - ユーザー入力 -
- FORMS - フォーム -
- DEPENDENCY INJECTION - 依存性注入 -
- STYLE GUIDE - スタイルガイド -
注意1)ここに掲載されていない項目は、Angular2 CORE DOCUMENTATIONのGUIDEを直接参照してください。
注意2)2016年10月24日時点の翻訳です。翻訳者はTOEICで700点くらいの英語力なので、英訳が間違っている可能性があります。しかもかなり意訳している箇所もあります。もし意訳を通り越して、誤訳になっているような箇所がありましたらご指摘ください。
USER INPUT - ユーザー入力 -
DOMイベントは、ユーザーの入力によって引き起されます。イベントバインディングされたDOMイベントを読み取り、データの更新をコンポーネントやモデルに反映させます。
ユーザーは知りたいことがあるとき、リンクをクリックしたり、ボタンを押したり、文章を入力したりします。こういったユーザーの行動はすべて、DOMイベントを発生させます。この章では、Angularのイベントバインディングに関するシンタックスを使って、DOMイベントをバインディングする方法を学びます。
live exampleを実行してみてください。
ユーザーの入力イベントと紐づける
Angularのイベントバインディングは、どんなDOMイベントに対しても反応することができます。
その文法は簡単です。DOMイベントの名称を丸かっこでくくって、引用符号のついたテンプレートステートメントに割り当てます。クリックハンドラを意味するイベントバインディングを、例として挙げてみます。
<button (click)="onClickMe()">Click me!</button>
イコール記号の左側に配置された(click)
は、バインディングの対象とされたボタンのクリックイベントを特定します。右側にある引用符のついた文は、コンポーネントにあるonClickMe
メソッドを呼び出し、クリックイベントを発生させるテンプレートステートメントです。テンプレートステートメントは、制限付の、少し手を加えられたJavaScriptのサブセットです。
バインディングを書くときは、テンプレートステートメントのエクスキューションコンテキストを意識しないといけません。ステートメントの中に表示される識別子は、ある特定のコンテキストオブジェクトに属しています。このオブジェクトはテンプレートを管理するAngularコンポーネントであることが多いですが、次のコンポーネントにあるHTMLスニペットで、この事例をわかりやすく示してみます。
@Component({
selector: 'click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe() {
this.clickMessage = 'You are my hero!';
}
}
ユーザーがボタンをクリックすると、AngularはコンポーネントにあるonClickMe
メソッドを呼び出します。
$eventオブジェクトからユーザー入力を取得する
どんなイベントであっても、バインディングは可能です。インプットボックスのkeyupイベントをバインドして、ユーザーが画面上で入力したことを反映してみましょう。
今回は(1)イベントを読み取って、(2)ユーザーの入力内容を取得します。
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
Angularでは$event
変数を使うことで、イベントオブジェクトを利用できるようになります。$event
変数はコンポーネントのonKey()
メソッドに渡され、必要とされるユーザーのデータは、そこにあるイベント変数に渡されます。
export class KeyUpComponent_v1 {
values = '';
// without strong typing
onKey(event:any) {
this.values += event.target.value + ' | ';
}
}
イベントが何によって発生したかによって、$event
オブジェクトの形式が決められます。
keyup
イベントがDOMから生じたのであれば、$event
はスタンダードDOMイベントオブジェクトとなります。$event.target
を使えば、ユーザーの入力情報を含むvalue
プロパティを持った、HTMLInputElement
を取得することができます。
onKey()
コンポーネントメソッドは、イベントオブジェクトからユーザーの入力情報を抽出してユーザーのデータリストに加え、コンポーネントのvalues
プロパティに蓄積させていきます。それからインターポレーションを使って蓄積されたvalues
プロパティを画面に表示させます。
"abc"という文字を入力し、バックスペースを押してその文字を削除してみてください。UIは次のように表示されます。
a | ab | abc | ab | a | |
$event
をany
型として投げていますが、これはコードを簡略化させるストロングタイピングを使わないことを意味します。通常、TypeScriptが提供するストロングタイピングを使うことが推奨されていますので、HTMLのDOMオブジェクトを次のように投げるよう書き直してみました。
app/keyup.components.ts(class_v.1_strongly_typed)
export class KeyUpComponent_v1 {
values = '';
// with strong typing
onKey(event: KeyboardEvent) {
this.values += (event.target).value + ' | ';
}
}
>ストロングタイピングを使えば、DOMイベントがメソッドに渡ったときに、致命的な問題があるかどうかを教えてくれます。テンプレートの細かいところまで気にする必要はなく、逆に意識しなさすぎるということもなくなります。
> ユーザーのキー操作プロセスにおけるこういった問題を、次の試みで考えてみましょう。
## テンプレート参照変数からユーザーの入力情報を取得する
`$event`変数を使わずに、ユーザーのデータを取得する方法がもうひとつあります。
Angularには[テンプレート参照変数](https://angular.io/docs/ts/latest/guide/template-syntax.html#ref-vars)というシンタックス機能があり、この変数を使えば要素に直接アクセスすることができるようになります。テンプレート参照変数はハッシュ(#)記号を使った識別子を用いて宣言します。
次の例では、簡易的なテンプレートでテンプレート参照変数を使っています。キー操作でかっこよくループバックをやってみましょう。
```ts:app/loop-back.component.ts
@Component({
selector: 'loop-back',
template: `
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }
<input>
上にbox
と名付けたテンプレート参照変数を宣言しました。box
変数は<input>
そのものを参照し、input要素のvalue
を取得して<p>
タグにあるインターポレーションに反映します。
テンプレートは、自分自身をすべて包括する仕組みになっています。コンポーネントとバインディングしているわけではなく、コンポーネントもテンプレートとバインディングしているわけではありません。
インプットボックスに入力し、キー操作による画面の更新を見てみてください。
できました!
イベントバインディングしなければ、これはまったく動きません。
キー操作のような非同期のイベントだったとしても、Angularであればバインディングを更新(つまり画面の更新)することができます。
そこで
keyup
イベントをある状態、そう、何もない状態と紐づけています。考えられうる限りで一番短い状態である数字の0をバインディングしました。そうすれば、Angularは全てよしなにやってくれます。賢いですね!
テンプレート参照変数は、魅惑的な変数です。$event
オブジェクトを利用するよりも、その変数を使えばよっぽど簡単にテキストボックスへたどり着くことができます。前述したkeyupのサンプルについても、ユーザーの入力情報を取得するために、テンプレート参照変数を使って書き換えられるかもしれません。やってみましょう。
@Component({
selector: 'key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';
onKey(value: string) {
this.values += value + ' | ';
}
}
確かに、簡単になっているようです。このアプローチで特に優れている点は、ビューから得られるvaluesのデータがコンポーネントのコード上で簡潔にになっていることです。もはや$event
オブジェクトやその構造の知識は必要ありません。
キーイベントフィルタリング(key.enter
を使う)
おそらくですが、全てのキー操作を意識するようなことは誰もしません。大体の場合、私たちが気にするのはユーザーがエンターキーを押したときに得られるインプットボックスの値だけで、それ以外の操作は全部無視したいとすら思っているかもしれません。
(keyup)
イベントをバインディングするとき、状態を扱うイベントは全てのキー操作を監視しています。最初に全ての$event.keyCode
を調査してフィルタリングを行い、キーがエンターだったときにだけvalues
プロパティを更新していました。
Angularはキーイベントをフィルタリングすることができます。Angularではキーボードイベント用の特殊なシンタックスを扱っており、keyup.enter
という擬似イベントとバインディングすることで、エンター入力だけを読み取ることができるようになります。
ではお待ちかね、コンポーネントにあるvalues
プロパティを更新してみましょう。(この例では、イベントが状態をバインディングしている状況で更新が発生しています。コンポーネントの中に更新用のコードを書いた方が、より実践的でいいでしょう。)
@Component({
selector: 'key-up3',
template: `
<input #box (keyup.enter)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v3 {
values = '';
}
例はこんな風に動作します。
On blur
前の例では、ユーザーがマウスを離しそのページのどこかをクリックしたときは、インプットボックスの現在の状態を読み取ることができません。ユーザーがエンターキーを押したときのみ、コンポーネントのvalues
プロパティを更新しますが、その間フォーカスはつねにインプットボックスの中にある必要があります。
インプットボックスのblurイベントも読み取れるように、前の例を修正してみましょう。
@Component({
selector: 'key-up4',
template: `
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v4 {
values = '';
}
まとめてやってみる
前章ではデータ表示の方法について学びましたが、この章ではイベントバインディングのちょっとしたテクニックを習得しました。
heroesリストを表示し、そのリストに新しいheroを追加するミニアプリを使って、それらをまとめてやってみましょう。まず、ユーザーがインプットボックスに入力し、それからエンターを押すか、Addボタンを押すか、ページのどこかをクリックさせるかしてheroを追加します。
下にあるのは"Little Tour of Heroes"コンポーネントです。ミニマリストの栄光の瞬間に軽く浸ったら、注目すべき点を見てみます。
@Component({
selector: 'little-tour',
template: `
<input #newHero
(keyup.enter)="addHero(newHero.value)"
(blur)="addHero(newHero.value); newHero.value='' ">
<button (click)=addHero(newHero.value)>Add</button>
<ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
`
})
export class LittleTourComponent {
heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero: string) {
if (newHero) {
this.heroes.push(newHero);
}
}
}
これまでのものはすべて見ることができました。新しいことはあと少ししかありませんので、もうちょっと我慢してください。
要素を参照するために、テンプレート変数を使う
newHero
テンプレート変数は、<input>
要素を参照しています。newHero
は<input>
要素のどんな兄弟要素や子要素からでも使用することができます。
テンプレート変数から要素を取得すると、ボタンクリックの扱いが簡単になります。テンプレート変数がないと、インプット要素を見つけるために、あるのかないのかわからないCSSセレクタを使わないといけません。
要素ではなく、値を渡す
newHero
はコンポーネントにあるaddHero
メソッドに渡されています。
しかし、最初に設定したkeyup componentの中で、<input>
のDOM要素を通してaddHero
を取得する方法は、あまり好ましくないものであることを学びました。
その代り、インプットボックスのvalueを取得して、addHero
に渡すようにします。コンポーネントが、HTMLもしくはDOMがないことを把握している方が、より好ましい手法となります。
テンプレートステートメントをシンプルにしておく
これまでのところで、(blur)
を2つのJavaScript ステートメントに紐づけました。
addHero
という最初のステートメントは良いのですが、2つ目のただ空の文字列をインプットボックス(値)に渡すだけのものは好ましくありません。
2つ目のステートメントには、残すべき理由があります。新しいheroをリストに追加した後、インプットボックスを空にしなければなりません。コンポーネントには(今回デザインとして選んだ)インプットボックスにアクセスする手段がないので、そうせざるをえないのです。
サンプルは動きますが、HTML中のJavaScriptは注意深く扱わないといけません。テンプレートステートメントは強力です。私たちはテンプレートステートメントの使用に対し責任を持つことになりますが、HTML中の複雑なJavaScriptには責任を負いかねます。
インプットボックスをコンポーネントへいやいや渡すのではなく、ここで一旦よく考えてみませんか。
もっと最適な、3つ目の方法があります。Formsの章でNgModel
について学習するときに、改めて確認します。
ソースコード
この章で説明したコードの全体は以下の通りです。
import { Component } from '@angular/core';
@Component({
selector: 'click-me',
template: `
<button (click)="onClickMe()">Click me!</button>
{{clickMessage}}`
})
export class ClickMeComponent {
clickMessage = '';
onClickMe() {
this.clickMessage = 'You are my hero!';
}
}
import { Component } from '@angular/core';
@Component({
selector: 'key-up1',
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v1 {
values = '';
/*
// without strong typing
onKey(event:any) {
this.values += event.target.value + ' | ';
}
*/
// with strong typing
onKey(event: KeyboardEvent) {
this.values += (<HTMLInputElement>event.target).value + ' | ';
}
}
//////////////////////////////////////////
@Component({
selector: 'key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';
onKey(value: string) {
this.values += value + ' | ';
}
}
//////////////////////////////////////////
@Component({
selector: 'key-up3',
template: `
<input #box (keyup.enter)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v3 {
values = '';
}
//////////////////////////////////////////
@Component({
selector: 'key-up4',
template: `
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v4 {
values = '';
}
import { Component } from '@angular/core';
@Component({
selector: 'loop-back',
template: `
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }
import { Component } from '@angular/core';
@Component({
selector: 'little-tour',
template: `
<input #newHero
(keyup.enter)="addHero(newHero.value)"
(blur)="addHero(newHero.value); newHero.value='' ">
<button (click)=addHero(newHero.value)>Add</button>
<ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
`
})
export class LittleTourComponent {
heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
addHero(newHero: string) {
if (newHero) {
this.heroes.push(newHero);
}
}
}
総括
ユーザー入力とジェスチャーに対応する基礎的なプリミティブについて習得しました。
これらのプリミティブは強力で、ユーザー入力の数が多いと扱いが少し難しくなります。データ入力フィールドとモデルのプロパティとの間で双方向バインディングを書かないといけないような場合は、イベントレベルを低く抑えるようにしています。
AngularにはNgModel
という双方向バインディングがありますが、それについては、Forms
の章で学習していきましょう。
Next Step
FORMS - フォーム -