前のページ

次のページ


ユーザ入力

最初に「クイック・スタート」の章の「IntelliJでこのアプリを開いて見てみよう」の節に従って、このアプリケーションIDE上で開いてみましょう。

このアプリケーション(Pub get実行後)をIDEのプロジェクト・ビューでみると次のような構成になっていることが判ります。



総てのAngularアプリケーションはlibweb2つのフォルダで構成されています。libフォルダの中はコンポネントたちのファイルとなっています。この章はこれらのファイルの内容を使って解説していますので、IDE上でしかるべきファイルを見ながら読んで行かれることをお勧めします。



********



ユーザがリンクをクリックする、ボタンを押す、あるいはテキストを入力するときは、それを知る必要があります。これらのユーザ・アクションのすべてがDOMイベントを生起させます。この章では、Angularのイベンド・バインディング構文を使ったこれらのイベントのバインドに就いて学習します。

ライブのサンプルソース・コードを見る)を走らせてみましょう。



ユーザ・イベントへバインドする

どのDOMイベントに対しても応答するためにAngularイベント・バインディングが使えます。

構文はシンプルなものです。

<button (click)="onClickMe()">Click me!</button>

等号符の左側の(click)バインディングのターゲットtarget of the binding)としてのこのボタンのクリック・イベントを指定しています。右側の引用符内のテキストはテンプレート文template statement)で、我々はこの文の中ででこのコンポネントのonClickMeメソッドを呼ぶことでこのクリック・イベントに応答しています。テンプレート文はDartのサブセットで、幾つかの制限と幾つかの付加的なトリックがついたものです。

バインディングを書いているときは、我々はテンプレート文の実行コンテキスト(実行環境:execution context)に注意しなければなりません。文の中で出現する識別子は特定のコンテキスト・オブジェクトに属します。このオブジェクトは通常このテンプレートをコントロールするAngularのコンポネントです...この場合は上記の枠内のHTMLの文は以下のコンポネントに属しているので、絶対的にそうです:

lib/click_me_component.dart

@Component(
    selector: 'click-me',
    template: '''
      <button (click)="onClickMe()">Click me!</button>
      {{clickMessage}}''')
class ClickMeComponent {
  String clickMessage = '';
  void onClickMe() {
    clickMessage = 'You are my hero!';
  }
}

ユーザがこのボタンをクリックすると、AngularはこのコンポネントのonClickMe()メソッドを呼びます。

訳者注:インラインのテンプレートを使うときはDartの複行文字列に慣れましょう。



$eventオブジェクトからユーザ入力を取得する

総ての種類のイベントにバインド可能です。入力ボックスのキーアップ・イベントにバインドして、ユーザがタイプしたものをスクリーンに戻すようにしてみましょう。

今回は(1)イベントをリスンし、次に(2)ユーザの入力を補足します。

lib/keyup_components.dart (template v.1)

template: '''
  <input (keyup)="onKey(\$event)">
  <p>{{values}}</p>
''')

Angularはイベント・オブジェクトを$event変数の中で得られるようにします。この$event変数を我々はこのコンポネントのonKey()メソッドに渡します。我々が欲しいユーザ・データはこの変数の中にあります。



$EVENT \$EVENT

Dartファイル内のテンプレートでは$の前には \ が必要です。もしこのテンプレートがHTMLファイル内にある時は\$eventではなくて$eventを使います。


lib/keyup_components.dart (class v.2)

class KeyUpComponentV2 {
  String values = '';
  onKey(value) {
    values += '$value | ';
  }
}

$eventオブジェクトの様相はいったい何がこのイベントを生起させたかによって決まります。このキーアップ・イベントはDOMから来ており、従って$eventは我々のユーザの入力データを含んだ値のプロパティを持った標準DOMイベント・オブジェクトでならねばなりません。

onKey()というコンポネント・メソッドはこのイベント・オブジェクトからユーザの入力を抽出し、その入力をこのコンポネントのvaluesプロパティの中に我々が累積させるユーザ・データのリストに付加します。次に累積しているvaluesプロパティをスクリーンに戻して表示させるのに内挿を使います。

文字列"abc"を入力し、次にそれらを削除するのにbackspaceキーを押してみてください。以下はこのUIが何を表示するかを示しています:

a | ab | abc | ab | a | |


我々は$eventを任意の型としてキャストしており、これは自分のコードをシンプルにするために強い片付けを放棄したことを意味します。一般的に我々はDartが持っている強い片付けのほう好みます。我々はこのメソッドを書き換えて、HTML DOMオブジェクトを次のようにキャストすることもできます:

lib/keyup_components.dart (class v.1 - 強い型付け)

class KeyUpComponentV1 {
  String values = '';
  onKey(KeyboardEvent event) {
    InputElement el = event.target;
    values += '${el.value}  | ';
  }
}

強い型付けではDOMイベントをこのメソッドに渡す際の深刻な問題を明らかにします:テンプレートの詳細に気をつけすぎなければならず、それほど関心の分離(separation of concerns)に寄与しません。この問題に対しては次のユーザ・キー・ストロークの処理で触れます。



テンプレート参照変数からユーザ入力を取得する

$event変数なしでユーザ・データを取得する別の手段があります。

Angularにはテンプレート参照変数(template reference variables)と呼ばれる構文機能があります。これらの変数によりある要素に直接アクセスできるようになります。我々は識別子の前にシャープ文字(#)を付すことでテンプレート参照変数を宣言します。以下は超簡単なテンプレートのなかで賢いキー・ストロークのループバックを実装するのにこのテンプレート参照変数を使った例です。

lib/loop_back_component.dart

@Component(
    selector: 'loop-back',
    template: '''
      <input #box (keyup)="0">
      <p>{{box.value}}</p>
    ''')
class LoopBackComponent {}

ここで我々は<input>要素上にboxという名前のテンプレート参照変数を宣言しています。このbox変数は<input>要素それ自身に対する参照で、これで我々はinput要素の変数を捕まえ、それを<p>タグ間の内挿で表示できるということを意味します。

このテンプレートは 完全に自己充足になっています。これはこのコンポネントにバインドしていないし、このコンポネント自身は何もしていません。

このボックス内になにか入力してみて下さい。各キーストロークごとにこの表示が更新されます。


これはイベントにバインドしない限り全く動作しません。

Angularはキーストロークのような非同期のイベントに反応して何かをしたときのみこのバインディング(そして従ってスクリーン)を更新します。

だからキーアップ・イベントを何もしない文にバインドしていています。0番をバインドしていますが、これは考えうる最も短い文です。Angularをハッピーな状態にし続けるのはこれで十分です。

このテンプレート参照変数は魅力的です。$event オブジェクトをやりとおすよりは変数でテキストボックスを得るほうがずっと簡単です。以前のキーアップのサンプルを書き換えてユーザの入力を取得するのに変数を使うようにできると思います。やってみてください。

lib/keyup_components.dart (v2)

@Component(
    selector: 'key-up2',
    template: '''
      <input #box (keyup)="onKey(box.value)">
      <p>{{values}}</p>
    ''')
class KeyUpComponentV2 {
  String values = '';
  onKey(value) {
    values += '$value | ';
  }
}

このほうがより容易です。このアプローチが特に良い側面は、我々のコンポネントのコードがビューからクリーンな値を取得するということです。もはや$eventやその構造に関する知識は不要です。



キー・イベントのフィルタリングkey.enter使用で)

各キーストロークが必要になることはあまり無いかもしれません。我々は多分ユーザがEnterキーを押したときの入力ボックスの値にのみ関心があり、他のすべてのキーは無視するかもしれません。(keyup)ベントにバインドしたときは、イベント処理分は各キーストロークを聞きます。このキーストロークにフィルタをかけ、$event.keyCodeを調べ、そのキーがEnterである時に限りその値を更新するようにできます。

Angularはキー・イベントにフィルタをかけることができます。Angularはキーボード・イベントに対する特別な構文を用意しています。Angularkeyup.enter 疑似イベントにバインドすることで、Enterキーに対してのみ聞くことができます。

その時にのみこのコンポネントのvaluesプロパティが更新されます。(この例では、イベント・バインディング文の内側で更新が発生します。)

lib/keyup_components.dart (v3)

@Component(
    selector: 'key-up3',
    template: '''
      <input #box (keyup.enter)="values=box.value">
      <p>{{values}}</p>
    ''')
class KeyUpComponentV3 {
  String values = '';
}




フォーカス外時(On blur)

前のサンプルでは、ユーザがマウスをこの入力ボックスから離れこのページのどこかをクリックしたときに、この入力ボックスの現行状態を移しません。フォーカスがこの入力ボックス内にある際はユーザがEnterを押した時にのみこのコンポネントのvaluesプロパティを更新しています。

この入力ボックスのblurイベントも聞くことでこれを直しましょう。

lib/keyup_components.dart (v4)

@Component(
    selector: 'key-up4',
    template: '''
      <input #box
        (keyup.enter)="values=box.value"
        (blur)="values=box.value">
      <p>{{values}}</p>
    ''')
class KeyUpComponentV4 {
  String values = '';
}



一緒にしてみる

前の章ではどのようにデータを表示するかを学びました。この章ではイベント・バインディング技術という武器を取得しました。

これをマイクロ・アプリに一緒に含め、heroesのリストを表示し、このリストに新しいヒーロたちを追加できるようにしましょう。ユーザは最初に入力ボックスにタイプし次にEnterを押す、Addボタンを押す、あるいはこのページのボックスの外の箇所をクリックすることでheroを追加できます。



lib/little_tour_component.dart

@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>
    ''')
class LittleTourComponent {
  List<String> heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  void addHero(String newHero) {
    if (newHero?.length > 0) {
      heroes.add(newHero);
    }
  }
}

ここではこれまでに解説したものの殆どが含まれていますが、幾つかの新しい事項や人前で話せない事項があります:

要素を参照するのにテンプレート変数を使う

newHeroテンプレート変数は<input>要素を参照しています。<input>要素のどの兄弟あるいは子供たちから newHeroを使うことができます。

テンプレート変数からこの要素を取得することでボタン・クリックのハンドラが簡素化されます。この変数を使わないとすると、このinput要素を見つけるのにファンシー(個人的嗜好のある)なCSSセレクタを使わざるを得なくなります。

要素ではなくて値を渡す

このコンポネントの addHeroメソッドにnewHeroを渡すこともできたでしょう。

しかしそれにはaddHeroに対し<input> DOM要素を介して進めることを要求することになり、これは最初のkeyupコンポネントでやってみたように好ましからぬものです。

そうではなくて我々は入力ボックスの値を取得し、それをaddHeroに渡しています。このコンポネントはHTMLまたはDOMに関しては何も知りません。これが我々が好きなやり方です。

テンプレート文はシンプルに

これまで2つのDart文に(blur) をバインドしました。

最初はaddHeroを呼ぶもので、これが我々が好ましいとするものです。2番目の方法は好ましくないもので、入力ボックスの空の文字列を代入するものです。

2番目の文はそれなりの理由で存在しています。このリストに新しいheroを追加した後でこの入力ボックスをクリアしなければなりません。このコンポネント入力ボックスにアクセスする手段を持っていないので、それ自身でそうする手段を持っていません(我々の設計の選択です)。

このサンプルは動作するものの、HTMLのなかのDartDart in HTML)における正しい道を選んでいます。テンプレート文は強力です。我々はこれらを責任あるかたちで使うべきです。複雑なDart in HTMLは無責任です。

入力ボックスをコンポネントの中に渡すことへの不本意性を考え直すべきでしょうか?

3のもっと良い手段があります。これはFormの章で NgModel を学ぶときに出てきます。



ソース・コード

この章で使ったすべてのソース・コードはGitHubにあります。筆者による「プログラミング言語Dartの基礎」の添付資料のdart_code_samplesappsにも同じものがangular2_user-inputとして入っています。



まとめ

ユーザの入力や操作に対応するための基本的な要素たちをマスターしました。これらのプリミティブたちは強力であるので、大量のユーザ入力を処理するには少しばかりぎごちないものです。データ入力フィールドとモデル間の双方向性バインディングを書かねばならなくなったときは、イベントの低レベルにおいて対処しています。

AngularNgModelと呼ばれる双方向バインディングを持っています。これはFormsの章で学習します。



前のページ

次のページ