前のページ

次のページ


フォーム

最初に「クイック・スタート」の章の「IntelliJでこのアプリを開いて見てみよう」の節に従って、このアプリケーションIDE上で開いてみましょう。IDE上でlib及びwebフォルダに入っているファイルを見ながら以下の説明を読むと理解が早いと思います。

この章はIDE上でこのアプリのプロジェクトを最初から作ってゆくシナリオで説明していますので、この章の手順で試してみることもお勧めします。



********



我々はログインする、ヘルプ要求をする、注文を発注する、フライトを予約する、会合のスケジュールをたてる、及びその他の無数のデータ・エントリのタスクの為にフォームを使っています。

熟練ウェブ・デベロッパはHTMLフォームを正しいタグたちを使ってぴったりはめ込むことができます。そのフォームの背後のワークフローを通して効果的かつ効率的にユーザを導くような密着したデータ・エントリの経験を作るのはもっと挑戦的なものです。

はっきり言ってそれは本章の範囲を超えた設計スキルが必要になります。

これはまた双方向データ・バインディング、追跡変更、認証、エラー処理...といったフレームワークのサポートが必要であり、これはAngularフォームに関する本章で我々がカバーすべきものです。

我々は最初からシンプルなAngularのフォームを作ることから、一歩ずつ以下の事項を学習していきます:

  • どうやってコンポネントとテンプレートを使ってAngularのフォームを構築するか

  • 読み込みと入力コントロールへの値の書き込みのためのngModel双方向データ・バインディングの構文

  • フォーム・コントロールたちから変更状態(change state)と有効性(validity)を追跡するためのngControlディレクティブ

  • フォーム・コントロールたちにngControlが追加する特別のCSSクラスたちと、強力なビジュアルなフィードバックを提供するためこれらをどのように使うか

  • ユーザにどのように検証エラーを表示し、フォーム・コントロールをどのようにイネーブル/ディセーブルするか

  • テンプレート参照変数をつかってコントロールたち間でどのように情報を共有するか

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



テンプレート駆動のフォーム

我々の多くはこの章で書かれているフォーム固有のディレクティブたちと技術でAngularのテンプレート構文を用いてテンプレートを書くことでフォームを構築すると思います。

これはフォームを作るための唯一の手段ではありませんが、これは我々が本章でカバーしてゆくものです。

我々はログイン・フォーム、コンタクト・フォーム、ほとんどすべてのビジネスのフォームにわたる我々が必要とする殆どどんなフォームもAngularのテンプレートで構築できます。我々はコントロールたちをクリエーティブにレイアウトし、それらをデータにバインドし、検証ルールを指定し検証エラーを表示し、特定のコントロールを有効化または無効化し、組み込みのビジュアルのフィードバックを起動させ、そして更に多くのことができます。

Angularがそうでなければ自分自身で格闘しなければならない多くの繰り返しでお決まりのタスクたちを扱ってくれるので、このことはかなり簡単なものとなっています。

我々は下図に示すテンプレート起動型のフォームを構築することを議論し学びます


ここHero職業紹介所においては我々はこのヒーロたちに関する個人情報を安定なものとして保持するのにこのフォームを使っています。各ヒーロは仕事を探しています。正しいヒーロと正しい危機をマッチさせるのが我々の会社のミッションです!

このフォーム上の3つのフィールドのうち2つが入力が要求されているもの(即ち必須項目)です。入力が必須であるフィールドは左側に緑色のバーがついていて、入力する場所がここだとわかるようにしています。

このヒーロの名前を削除するとこのフォームは次のように注目しやすいスタイルで有効性検査(validation)エラーを表示します:


submitボタンが無効化されていて、この入力コントロールの左にある「必須」バーが緑色から赤に変わっていることに注目してください。

我々はこの「必須」バーの色と場所を標準のCSSを使ってカスタマイズします。

このフォームを我々は少しずつステップを踏んで作ってゆきます。

  1. Heroというモデル・クラスを作る

  2. このフォームをコントロールするコンポネントを作る

  3. 初期フォーム・レイアウトでテンプレートを作る

  4. 各フォーム入力コントロールに対しngModelディレクティブを付加する

  5. 各フォーム入力コントロールに対し ngControlディレクティブを付加する

  6. ビジュアルなフィードバックを提供するためにカスタムのCSS を追加する

  7. 有効性検査エラーメッセージを表示したり隠したりする

  8. ngSubmitでフォーム提出を処理する

  9. 提出後にこのフォームの表示を変える



セットアップ

自分のIDE上でangular_formsという名前の新規のプロジェクト・フォルダを作り、その中に以下のpubspec.yaml, web/index.html,及びweb/main.dart3ファイルを作成します(これらはクイック・スタートで馴染みのものとなります)。訳者注:これらはダウンロードしたファイルをそのまま使っても構いません。

pubspec.yaml

name: hero_form
description: Form example
version: 0.0.1
environment:
  sdk: '>=1.19.0 <2.0.0'
dependencies:
  angular2: ^2.2.0
dev_dependencies:
  browser: ^0.10.0
  dart_to_js_script_rewriter: ^1.0.1
transformers:
- angular2:
    platform_directives:
    - 'package:angular2/common.dart#COMMON_DIRECTIVES'
    platform_pipes:
    - 'package:angular2/common.dart#COMMON_PIPES'
    entry_points: web/main.dart
- dart_to_js_script_rewriter



web/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Hero Form</title>
    <link rel="stylesheet" href="bootstrap.min.css">
    <link rel="stylesheet" href="styles.css">
    <link rel="stylesheet" href="forms.css">    
    <script defer src="main.dart" type="application/dart"></script>
    <script defer src="packages/browser/dart.js"></script>
  </head>
  <body>
    <hero-form>Loading...</hero-form>
  </body>
</html>



web/main.dart

import 'package:angular2/platform/browser.dart';
import 'package:hero_form/hero_form_component.dart';
main() {
  bootstrap(HeroFormComponent);
}

これでこのコードは走れますので、<hero-form>コンポネントへの代用ファイル(stub)を作りましょう。

libと呼ぶ新しいディレクトリを作り、その中に以下のコードを持ったhero_form_component.dartという名前のファイルを置きます(訳者注:ここからはIDE上でダウンロードしたlib/hero_form_component.dartの中身を以下のコードで置き換えてください):

lib/hero_form_component.dart

import 'package:angular2/core.dart';
@Component(selector: 'hero-form', template: 'Hero form will go here')
class HeroFormComponent {}

このアプリは走りますがなにも面白いことはしません。そこで幾つかのデータを付加しましょう:



Heroというモデル・クラスを作る

ユーザたちがフォーム・データを入力するなかで、我々はそれらの変更を捕捉し、その変更に基づきあるモデルのインスタンスを更新することにしましょう。我々はこのモデルがどのようなものかが判らないとこのフォームをレイアウトできません。

モデルは「プロパティ・バッグ」(property bag:プロパティたちの集まり)なみのシンプルなもので、アプリケーションの重要性に関する事実(facts)を保持します。それが3つの必要とされるフィールド(idnamepower)とひとつのオプショナルなフィールド(alterEgo)で、我々のHeroクラスを良く記述させることになります。

libディレクトリの中に以下のコードを持った hero.dartと呼ばれるファイルを追加しましょう:

lib/hero.dart

class Hero {
  int number;
  String name;
  String power;
  String alterEgo;
  Hero(this.number, this.name, this.power, [this.alterEgo]);
  String toString() => '$number: $name ($alterEgo). Super power: $power';
}

これは幾つかの要求事項(name, power, alterEgo)を持っているが振舞を持っていないあまり面白くないモデルです。でも我々のデモには完全なものです。

alterEgoはオプショナルなものですので、このコンストラクタはオミット可能になっています:[this.alterEgo][]はその意味です。

新規のheroの生成は次のようになります:

var myHero = new Hero(
    42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
print('My hero is ${myHero.name}.'); // "My hero is SkyDog."



フォーム・コンポネントをつくる

Angularのフォームは2つのパーツでできています:データとユーザの関わり合いを取り扱うためのHTMLベースのテンプレートとコード・ベースのコンポネントです

このコンポネントが、手短に言えば、Heroエディタができることを記述しているので、これから始めました。

hero_form_component.dartに手を入れ、その中身をそっくり以下のコードで置き換えます:

lib/hero_form_component.dart

import 'package:angular2/core.dart';
import 'hero.dart';
const List<String> _powers = const [
  'Really Smart',
  'Super Flexible',
  'Super Hot',
  'Weather Changer'
];
@Component(
    selector: 'hero-form',
    templateUrl: 'hero_form_component.html')
class HeroFormComponent {
  List<String> get powers => _powers;
  bool submitted = false;
  Hero model = new Hero(18, 'Dr IQ', _powers[0], 'Chuck Overstreet');
  // TODO: Remove this when we're done
  String get diagnostic => 'DIAGNOSTIC: $model';
  onSubmit() {
    submitted = true;
  }
}

このコンポネントには特別なものはありません、フォーム固有のものはなく、これまで書いてきたコンポネントたちと違うものはありません。

このコンポネントを理解するにはこれまでの章でカバーしているAngularのコンセプトのみが必要です。

  1. このコードはAngularのライブラリから標準のシンボルたちのセットをインポートしています。

  2. @Componentセレクタ値の"hero-form"は、我々は親のテンプレート(即ちweb/index.html)の<hero-form>タグのなかにこのフォームを置けるということを意味します。

  3. templateUrlプロパティは別のhero_form_component.htmlというテンプレートHTMLのファイルを指します。

  4. 我々は modelpowersにダミー・データをデモ用に定義しました。このさきでは、実際のデータの取得と保持のためのデータ・サービスを注入する、あるいは多分これらの属性たちを親のコンポネントにバインドするために入力と出力として外から見えるようにすることも可能です。現在はこれを気にかける必要はなく、これらの今後の変更は我々のフォームには影響を与えません。

  5. 我々のモデルを記述した文字列を返すためにdiagnosticプロパティ(ゲッタ) を用意しました。これは開発中に我々がやったことを見るのに寄与します;あとでこれを捨てやすいように// TODO:としてクリーンアップ・ノートを付してあります。

どうしてテンプレートをこのコンポネント・ファイルの中にインラインで記述していないのでしょうか?

インラインのテンプレートはそれらが短いものの場合は有用になり得ますが、殆どのテンプレートは短くはありません。Dartのファイルは一般的には長いHTMLを書く(あるいは読む)為に最適な場所ではなく、またHTMLとコードをミックスさせたファイルに対し役に立つエディタは殆どありません。また明確で明らかな目的を持った短いファイルたちにしたほうが良いと思います。

我々はHTMLテンプレートをどこか他に置くという良い選択をしています。それではそれを書きましょう。



初期のHTMLフォーム・テンプレートを作る

libディレクトリの中に以下のコードを持ったhero_form_component.htmlと呼ばれる新規ファイルを追加し、以下のテンプレート・コードをその中に置きましょう(訳者注:ここからはIDE上でダウンロードしたlib/hero_form_component.htmlの中身を以下のコードで置き換えてください。以下のコードはlib/hero_form_component_initial.htmlに相当します):

lib/hero_form_component.html

<div class="container">
  <h1>Hero Form</h1>
  <form>
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" required>
    </div>
    <div class="form-group">
      <label for="alterEgo">Alter Ego</label>
      <input type="text" class="form-control">
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
  </form>
</div>

これは通常のHTML 5のコードです。我々は2つのHeroフィールドであるnamealterEgoを提示し、入力ボックスのなかにユーザが入力すようそれらを開いています。

Name<input>コントロールにはHTML5 required属性がついています。Alter Ego <input>コントロールにはそれはついていませんが、それはalterEgoがオプショナルだからです。(訳者注:この属性はこのフィールドに入力されていないとsubmitが効かないことを指定しています)

最後にSubmitボタンがあり、それにはclassがついています。

我々は未だAngularは使っていません。バインディングはありません。ディレクティブも付加されていません。単なるレイアウトです。

container,form-group, form-control,及び btn クラスたちはBootstrap CSSになります。純粋にそとづらだけです。我々は我々のフォームに魅力的な服を着せるのに Bootstrapを使っています。

Angularのフォームはスタイル・ライブラリを必要としません

Angularcontainer, form-group, form-control,及びbtnクラスたちを使っていませんし、なんの外部ライブラリのスタイルたちも全く使っていません。AngularのアプリはCSSライブラリを使えます...が全く使わなくても良いのです

スタイルシートを追加しましょう。

https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.cssから Bootstrapスタイルシートをダウンロードし、それをwebディレクトリの中に置きます。

web/index.htmlを編集してbootstrap.min.cssへのリンクを追加します(訳者注:IDE上でダウンロードしたweb/index.htmlにはすでに含まれていることを確認してください):

web/index.html(抜き出し)

    <link rel="stylesheet" href="bootstrap.min.css">
    <script defer src="main.dart" type="application/dart"></script>
    <script defer src="packages/browser/dart.js"></script>



*ngForpowersを付加する

我々のheroはこの職業紹介所が承認したpowersの固定リストからひとつのスーパー・パワーを選択しなければなりません。我々はそのリストを内部で保持しています(HeroFormComponent.dartのなかで)。

我々は自分たちのフォームにselectを追加し、ngForを使ってpowersリストのoptionsをバインドします。ngForは「データを表示する」の章で以前使った技術です。

以下のHTMLlib/hero_form_component.htmlAlter Egoの直下に追加します(訳者注:<div class="form-group"><label for="alterEgo">.....</div>のブロックの後です)。

lib/hero_form_component.html(抜き出し)

<div class="form-group">
  <label for="power">Hero Power</label>
  <select class="form-control" required>
    <option *ngFor="let p of powers" [value]="p">{{p}}</option>
  </select>
</div>

このコードはpowersのリストのなかの各powerに対する<option>タグを繰り返します。pテンプレート入力変数は各繰り返しで異なるpowerです;我々は二重波括弧による内挿構文を使ってその値を表示します。



*ngModelによる双方向データ・バインディング

この時点でこのアプリを走らせるとがっかりします。(訳者注:この状態でこのアプリをDartiumで開くと下図のような画面になります)


我々はまだHeroへのバインディングをしていないのでheroのデータが表示されていません。これまでの章でどうしたら良いかはわかっています。「データを表示する」の章ではプロパティ・バインディングが示されています。「ユーザ入力」ではイベント・バインディングによりDOMイベントの聴き方と、コンポネントのプロパティを表示されている値で更新する方法が示されています。

ここでは表示、(イベントを)聴く、及び(値の)抽出を同時に行う必要があります。

これまでに知っている技術を使うことも可能ですが、ちょっと新しいものを導入します:即ちNgModelディレクティブで、フォームからモデルへのバインディングを超簡単にします。

Name<input>タグを探し、以下のようにアップデートします:

lib/hero_form_component.html(抜き出し)

<input type="text" class="form-control" required
       [(ngModel)]="model.name" >
TODO: remove this: {{model.name}}


我々はinputタグの後に診断用の内挿を付加し、何をやっているかが判るようにしています。終わったらあとで削除できるようにTODO:と注記しています。

バインディング構文[(ngModel)]="m...."に注目願います。

このアプリを今すぐ走らせNameの入力ボックス内にタイプし、文字を追加や削除をしたら、内挿テキストからそれらの文字が出現または消えるのが判るはずです。例えば次のようになります。


診断部分は値が実際に入力ボックスからモデルへそしてその逆へ流れていることの証明です。それが双方向バインディングなのです!

同じような[(ngModel)]バインディングをAlter EgoHero Powerにも追加しましょう。入力ボックスのバインディング・メッセージはそのままにしてこのコンポネントのdiagnostic(診断)プロパティ へのバインディングを一番上に追加します。そうするとHeroモデル全体で双方向データ・バインディングが機能していることが確認できます。

加工した結果、このフォームのコアは3つの[(ngModel)]バインディングを持ったものになり、以下のようなコードとなります:

lib/hero_form_component.html

<div class="container">
  <h1>Hero Form</h1>
  <form>
    {{diagnostic}}
    <div class="form-group">
      <label for="name">Name</label>
      <input type="text" class="form-control" required
             [(ngModel)]="model.name" >
    </div>
    <div class="form-group">
      <label for="alterEgo">Alter Ego</label>
      <input type="text" class="form-control"
             [(ngModel)]="model.alterEgo">
    </div>
    <div class="form-group">
      <label for="power">Hero Power</label>
      <select class="form-control" required
              [(ngModel)]="model.power">
        <option *ngFor="let p of powers" [value]="p">{{p}}</option>
      </select>
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
  </form>
</div>

このアプリを今走らせて各Heroモデルのプロパティを変えてみると、このフォームは以下のようになります(訳者注:下図はDartiumで試した例です):


このフォームのトップ近くに置いたdiagnostic(診断)は我々の値たちの変更がこのモデルに反映されていることを確認させています。

この診断は削除してください。もうその目的は達しました。

[(ngModel)]の内部

* この節はオプショナルな[(ngModel)]に深入りしたものです。関心がなければ飛ばしてください *

バインディング構文[()]の句読点表記は何が起きているかの良き手がかりです。

プロパティ・バインディングはそのモデルからスクリーン上のターゲット・プロパティ へのへのデータの流れを起こします。我々はそのターゲット・プロパティを各括弧[]でその名前を囲むことで特定します。これがモデルからビューへの一方向データ・バインディングです。

イベント・バインディングはスクリーン上のターゲット・プロパティからモデルへのデータの流れを起こします。我々はそのターゲット・プロパティ を丸括弧()でその名前を囲むことで特定します。これはビューからモデルへの反対方向の一方向データ・バインディングです。

Angularが組み合わせ句読点表記[()]を双方向データ・バインディングで双方向のデータの流れを示すのに使っているのに納得されるでしょう。

実際我々はNgModelバインディングを2つの分離したモードとして分割して、以下のようにName <input>のバインディングを書き換えることは可能です(訳者注:IDE上で試してみてください):

lib/hero_form_component.html(抜き出し)

<div class="form-group">
  <label for="name">Name</label>
  <input type="text" class="form-control" required
         [ngModel]="model.name"
         (ngModelChange)="model.name = $event" >
  TODO: remove this: {{model.name}}
</div>

プロパティ・バインディングは馴染みに感じると思います。イベント・バインディングはおかしく感じるかもしれません。

ngModelChangeという名前は NgModelディレクティブのイベント・プロパティを指定します。Angularが書式[(x)]のなかにバインディング・ターゲットを見つけたら、xディレクティブはx入力プロパティxChange出力プロパティ があるとみなします。

もう一つの風変わりな点はmodel.name = $eventというテンプレート式です。我々はDOMイベントからくる$eventオブジェクトには馴染んでいます。ngModelChangeプロパティDOMイベントを起こすものではありません;これはAngularEventEmitterプロパティ で、入力ボックスがファイヤしたらその値を返します。これがまさしくこのモデルのnameプロパティに割り当てるべきものなのです。

知ってよかったけど実用性は?[(ngModel)]は通常使いたいものですが、イベント処理がキーストロークのデバウンスやスロットル(接点のチャッタの影響回避)といった何か特別なことをしなければならないときにこのバインディングを分割したくなることもあるでしょう。



ngControlで変化状態と有効性を追跡する

フォームで必要なのは単なるデータ・バインディングだけではありません。我々のフォーム上でそのコントロールたちの状態を知る必要もあります。 NgControlディレクティブはそのコントロールの状態を追跡し続けてくれます。

NGCONTROLFORMが必要

NgControlNgFormディレクティブたちのファミリのひとつで、<form> タグ内部のこのコントロールのみに対し適用可能です。

我々のアプリはNgControlのインスタンスに対しそのユーザがそのコントロールに触れたか、値が変化したか、あるいは値が有効かを問いあわせることが可能です。

NgControlは単に状態を追跡するだけではなく、特別なAngularCSSクラスたち、例えばng-valid あるいは ng-invalidといったクラスでコントロールを更新します。我々はこれらのクラス名を使ってそのコントロールの見かけを変え、メッセージを出したり消したりできます。

我々はこれらの効果をそのうち探求します。現在はまず3つのフォーム・コントロールのすべてにName入力ボックスから順にngControlを付加して行きます。

lib/hero_form_component.html(抜き出し)

<input type="text" class="form-control" required
       [(ngModel)]="model.name"
       ngControl="name" >

ngControlディレクティブには重複しない名前を割り当てていないよう確認してください。

AngularngControlの名前たちの下にあるコントロールたちをNgFormディレクティブに登録します。我々はNgFormディレクティブを明示的に付加していませんでしたが、ここでは存在しています;これに関してはこの章の後で説明します。



ビジュアルなフィードバックのためにカスタムのCSSを付加する

NgControlは単に状態追跡のものではありません。我々は自分たちのフォーム上でのコントロールの状態も知りたいのです。NgControlディレクティブは我々のためにコントロールの状態を追跡し続けてくれます。

状態

trueのときのクラス

falseのときのクラス

コントロールが訪問された

ng-touched

ng-untouched

コントロールの値が変わった

ng-dirty

ng-pristine

コントロールの値が有効

ng-valid

ng-invalid

暫定的なspyという名前のテンプレート参照変数を"Name" <input> タグに付加し、このspyをこれらのクラスたちを表示するのに使ってみましょう。

lib/hero_form_component.html(抜き出し)

<input type="text" class="form-control" id="name"
       required
       [(ngModel)]="model.name" name="name"
       #spy >
<br>TODO: remove this: {{spy.className}}

それではIDE上で上記の変更を行ったらDartium上で試してみましょう。そしてName入力ボックスに注目し、以下の4つのステップを細かく追ってみてください:

  1. 見るだけでタッチしない

  2. Nameボックス内をクリックし、次にその外側をクリックする

  3. 名前の後にスラッシュ'/'を数回追加する

  4. 名前をクリアする

クラスたちは以下のように表示されます(訳者注:現在このコードはクラスたちの表示をしないので調査中)

  1. form-control ng-untouched ng-valid ng-pristine(初期状態)

  2. form-control ng-valid ng-pristine ng-touched(クリック後)

  3. form-control ng-valid ng-touched ng-dirty (変更後)

  4. form-control ng-touched ng-dirty ng-invalid(消去後)


(ng-valid | ng-invalid)のペアが我々にとって最も関心があるものですので、値が無効な時強力なビジュアルなシグナルを送りたいと思います。。我々はまた要求フィールドにマークをつけたいと思います。

その双方とも入力ボックスの左側に色つきのバーを使って実施できます:


この色つきバー効果は新しいweb/forms.cssファイルに2つのスタイルを追加することで達成されます。

web/forms.css

.ng-valid[required] {
  border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
  border-left: 5px solid #a94442; /* red */
}

これらのスタイルは2つのAngular有効性クラスたちとHTML5"required"属性をセレクトします。

このスタイルをこのアプリに追加するには、index.html<head>を変えてforms.cssにリンクさせます。

web/index.html(抜粋)

<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="forms.css">    



有効性エラー・メッセージを表示したり隠したりする

ベターな方法があります。

Name入力ボックスが必要事項のものです。それをクリアするとバーが赤になります。それは何かまずいということを示しているだけで、どこがいけないのか、あるいはそれに対してどうすべきかが判っていません。 ng-invalidクラスを活用して役に立つメッセージでそれを示してやることが可能です。

以下はユーザが名前を削除したときに表示されるやりかたです:


この効果を実現するには<input>タグを以下のように拡張します。

  1. テンプレート参照変数

  2. <div> の近くに"is required"メッセージをおき、そのコントロールが無効のときにのみ表示します。

以下は"name"入力ボックスにエラー・メッセージを追加した例です:

lib/hero_form_component.html (抜粋)

<input type="text" class="form-control" required
       [(ngModel)]="model.name"
       ngControl="name" #name="ngForm"
<div [hidden]="name.valid" class="alert alert-danger">
  Name is required
</div>

このテンプレートのなかからこの入力ボックスのAngulerコントロールにアクセスするためには、テンプレート参照変数が必要になります。そこでnemeという変数を作り、それに"ngForm"の値を持たせます。

Angularはこの構文を認識しname変数を ngControlで特定されたControlオブジェクトにセットします。そのオブジェクトはたまたまではなく我々が"name"と呼んだものです。

我々はこのControlオブジェクトの有効性プロパティ をこの要素のhiddenプロパティ にバインドします。このコントロールが有効な間はこのメッセージはhiddenとなります。無効になったらこのメッセージが出現します。

NgFormディレクティブ

ngControlで変化状態と有効性を追跡する」の節でngControlがこの入力ボックスをNgFormディレクティブが"name"として登録したことを思い出してください。

我々はこのNgFormディレクティブを明示的には付加しませんでした。Angularは内密にこれを付加し、それを<form>要素に巻き込みます。

NgFormディレクティブは<form>要素を付加的機能たちで補足します。これはコントロールたち(ngControlによって特定されるプロパティたち)を集め、それらの有効性を含むプロパティたちを監視します。それ自身の有効プロパティ を持っており、含まれている各コントロールが有効な時にtrueとなります。

この例では、それらのコントロールたちのコレクションから"name" コントロールをとりだし、それをテンプレート参照変数に代入しており、これにより我々はそのコントロール自身の有効性プロパティといったこのコントロールのプロパティたちにアクセスできるようになります。

Alter Egoはオプショナルで、これはそのままにしておけます。

Hero Power区間は要求となっています。もししたいなら<select>に同じ類のエラー処理を付加できますが、この選択ボックスは既にpowerを有効な値にしてしまっているので、これは必須ではありません。



ngSubmitでこのフォームをサブミットする

ユーザはこのフォームを埋めたらサブミット(提出)出来るようにしなければなりません。一番下にあるサブミット・ボタンはそれ自身何もしませんが、そのtype(type="submit")となっているので、サブミットを起動します。

"form submit"は今は意味がありません。これが意味あるようにするには、我々は<form>タグ内を別のAngularのディレクティブであるNgSubmitで書き換え、それをHeroFormComponent.onSubmit()メソッドにバインドします:

lib/hero_form_component.html (抜粋)

<form (ngSubmit)="onSubmit()" #heroForm="ngForm">

我々は最後に何か余計なものを差し込みました!我々はテンプレート参照変数#heroFormを定義し、値"ngForm"で初期化しました。

変数heroFormは以前説明したようにngControlに関する NgFormへのハンドルです。(今回はコントロールではなくてフォームへの参照ですが)

我々はこのフォーム全体の有効性をheroForm変数を介してこのボタンのdisabledプロパティにイベント・バインディングを使ってバインドしたいと思います。以下はそのコードです:

lib/hero_form_component.html (抜粋)

<button type="submit" class="btn btn-default"
        [disabled]="!heroForm.form.valid">Submit</button>

このアプリをこの状態で走らせると、このボタンはイネーブル状態になります。これは未だ何も有用なことはしませんが、生きてはいます。

Nameを削除すると我々は必須("required")ルールに反したので、エラー・メッセージできちんと指摘してくれます。Submitボタンも無効化されています。

面白くない?ちょっと考えてみてください。Angularがなかったらこのボタンのenable/disabled状態とこのフォームの有効性をどうやって結びつけることができるでしょうか?

我々の場合は以下のようにシンプルなものです:

  1. (強化された)フォーム要素上でテンプレート参照変数を定義する。

  2. ずっと先の行のなかでボタン内のその参照変数を参照する。



2つのフォーム領域間を切り替える(課外)

このフォームのサブミットは現時点では劇的なものではありません。

デモのためとしては普通の感想です。正直に言ってもっと面白くすることはフォームに関して何も新しいことを教えてくれることにはなりません。しかしこれは新しく獲得したバインディングのスキルの練習の機会ではあります。もし関心がなければ、このは飛ばして「まとめ」に進んでください。

もっと著しくビジュアルなことをやってみましょう。データ・エントリ領域を隠し何か別のものを表示してみましょう。

このフォームを<div>のなかにラップして、そのhiddenプロパティをHeroFormComponent.submittedプロパティにバインドしてみましょう。

lib/hero_form_component.html (抜粋)

<div [hidden]="submitted">
  <h1>Hero Form</h1>
  <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
    <!--....-->
  </form>
</div>

メインのフォームは、hero_form_component.dartからの以下のコードがが示すように、我々はこのフォームをサブミットするまでsubmittedプロパティがfalseなので最初は可視になっています:

bool submitted = false;
onSubmit() {
  submitted = true;
}

Submitボタンをクリックすると、submittedフラグがtrueになり、このフォームは予定通り消えます。

そこでこのアプリはこのフォームがsubmitted状態にあるときに何か別のものを表示する必要があります。以下のHTMLのブロックをさっきの<div>ラッパのあとに追加します:

lib/hero_form_component.html (抜粋)

<div [hidden]="!submitted">
  <h2>You submitted the following:</h2>
  <div class="row">
    <div class="col-xs-3">Name</div>
    <div class="col-xs-9  pull-left">{{ model.name }}</div>
  </div>
  <div class="row">
    <div class="col-xs-3">Alter Ego</div>
    <div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
  </div>
  <div class="row">
    <div class="col-xs-3">Power</div>
    <div class="col-xs-9 pull-left">{{ model.power }}</div>
  </div>
  <br>
  <button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>

ここではまた我々のheroがあり、内挿バインディングでのリード・オンリーで表示されます。HTML部分はこのコンポネントがsubmitted状態にある間にのみ表示されます。

このHTMLEditボタンがあり、そのクリック・イベントはsubmittedフラグをクリアする式にバインドされています。

Editボタンをクリックすると、このブロックは消え、編集可能なフォームが表示されます。

これが現在集められるドラマです。



まとめ

本章で述べたAngularのフォームはデータ修正、有効性検証、その他のサポートを提供するためのフレームワーク機能の有用性を活用したものです。

  • Angular HTMLフォーム・テンプレート

  • Componentデコレータ(アノテーション)をもったフォーム・コンポネント・クラス

  • フォームのサブミッションを処理するngSubmitディレクティブ

  • #heroForm, #name, #p, 及び#spyといったテンプレート参照変数

  • 双方向データ・バインディングのためのngModelディレクティブ

  • 有効性検証とエレメント変更追跡のためのngControlディレクティブ

  • コントロールが有効化どうかのチェック及びエラー・メッセージの表示/非表示のためのインプット・コントロールたち上の参照変数のvalidプロパティ

  • そのフォームが無効の時にサブミット・ボタンをディセーブルにするためのプロパティ・バインディング

  • 要求されている無効コントロールたちに関しビジュアルなフィードバックをユーザに提供するためのカスタムのCSSクラス



ソース・コード

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




前のページ

次のページ