この記事はAngular 2 Advent Calendar 2015の16日目の記事です。
皆さんはじめまして、@pastelIncと申します。よろしくお願いいたします。
はじめに
私は普段AngularJSを使ってFormを作成することが多いです。
今日は初めてAngular2のFormを実装しながら疑問に思ったtemplate syntaxについてお話します。
Formの実装はAngular2 - DEVELOPER GUIDESを参照します。ページ下部にコードが掲載されていますので御覧ください。
またAngular2の検証バージョンはAngular2 alpha.53です。
それでは実際にDEVELOPER GUIDES(以下DG)で紹介されているコードを見ながら順に読み進めていきましょう。
双方向バインディング
AngularJSを触ったことがある方ならお馴染みの双方向バインディングですがtemplateを見ると少し見た目が変わったことに気づくと思います。
ngModel
に注目してください。ngModel
の周りに大括弧と小括弧があります。
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
これについては、先日、@shinsukeimaiさんの記事で取り上げられていた通りです。詳しくは記事の方をご覧ください。
モデルからビューへの流れをプロパティバインディング、その逆をイベントバインディングと呼ぶようです。
プロパティバインディングとイベントバインディングを分けて書くときの用途ですが、DGに例書かれています。
ユーザーが値を変更した時に何かする必要がある場合の実装に役立ちそうです。
<input
[ngModel]="currentHero.firstName"
(ngModelChange)="setUpperCaseFirstName($event)">
NgControlでFormの状態を知る
NgControlはForm内の状態を知りたい時に便利です。
Form内でコントロールしたい要素、<input>
や<select>
などに添えます。添えると要素のclassに特別な値がセットされます。
またNgControlは状態を更新します。更新されるとコントロール中の要素のclassの値が変化します。今回、更新される状態とその値は次の3つです。
状態 | trueの時 | falseの時 |
---|---|---|
コントロールした | ng-touched | ng-untouched |
valueが変化した | ng-dirty | ng-pristine |
valueが不正 | ng-valid | ng-invalid |
これはViewにコントロール中の要素の状態を反映させるのに役立ちます。実装例ではcssで色の装飾をしています。
ng-valid[required] {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
border-left: 5px solid #a94442; /* red */
}
Local template variables
templateを見ると#name
という属性を見かけます。
これをlocal template variable(以下ltv)といいます。
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" #name="ngForm" >
#
というプレフィックスに抵抗がある人はvar-
も使えます。
<input type="text" class="form-control" required
[(ngModel)]="model.name"
ngControl="name" var-name="ngForm" >
ltvは要素の行にまたがってデータを移す手段です。NgForを使うときによく出てきます。
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
<option *ngFor="#p of powers" [value]="p">{{p}}</option>
</select>
ltvに値がセットされるのはngRepeatを思い浮かべれば直感的に理解できると思います。
ですが次のような書き方もあります。#phone
には何がセットされるでしょう。
<input type="text" #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>
ltvは兄弟要素上、またはその子要素のいずれかで、同じ要素のltvを参照することができます。
この場合#phone
にはHTMLInputElementがセットされます。ltvは<form>
にも添えられます。
<div [hidden]="submitted">
<h1>Hero Form</h1>
<form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<!-- ... all of the form ... -->
</form>
</div>
この場合#heroForm
はNgFormディレクティブへの参照です。
試しに="ngForm"
部分を省いてみるとltvにはHTMLFormElement
がセットされました。
<form>
にセットしたltvはForm内のすべての値の妥当性をバインドしているのでsubmitボタンのdisabled判定の際に利用できます。
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.form.valid">Submit</button>
どことなくAngularJSのname属性で使っていたFormControllerに似ています。
* と <template>
NgForディレクティブを用いるときに * が出てきました。これも見慣れないsyntaxです。
<select class="form-control" required
[(ngModel)]="model.power"
ngControl="power" #power="ngForm" >
<option *ngFor="#p of powers" [value]="p">{{p}}</option>
</select>
これはsyntax sugarです。違いを見てみます。
まずはsyntax sugarありの場合
<hero-detail *ngFor="#hero of heroes" [hero]="hero"></hero-detail>
続いてsyntax sugarなしの場合
<hero-detail template="ngFor #hero of heroes" [hero]="hero"></hero-detail>
さらにこれも等価です。
<template ngFor #hero [ngForOf]="heroes">
<hero-detail [hero]="hero"></hero-detail>
</template>
この記法はNgIf,NgForとNgSwitchで用いることができます。
さいごに
ざっくりとですがDGにそってFormの実装を進める上で気になったtemplate syntaxをまとめました。いかがだったでしょうか?
FormのHTMLはシンプルに書くことがなかなか難しかったりするのですが見た感じなかなかすっきりと書けそうな予感(希望的観測)がしたように思います。
途中実装を進めていくうえで何回かBreakingChangesにぶち当ったりもしましたが
数時間後にはDGが更新されておりサポート体制バッチリだなあという印象を受けました。
TypeScriptもしばらく離れていたうちにかなり書きやすくなっていました。楽しかったです。
また、本稿執筆時にめでたくAngular2 beta.0がリリースされました!
この機会に皆様も是非一度Angular2を触ってください。
文中に出てくる用語や説明、間違ってるよーという場合には教えていただけると助かります。