この記事はAngular Advent Calendar 2016 17日目の記事です。(遅刻しました)
この記事を書いている人
- jQueryを使って、基本的な使い方でホームページのナビゲーション開閉やタブのアニメーションを書ける人
- jQueryを使って、動的なFormを作ったり、サーバへリクエスト飛ばしたりする人
- jQueryを使って、5000行ぐらいのシミュレーターみたいなアプリケーションを書いたことがある人
仕事で使っているというだけで、jQuery、JavaScriptを深くは理解していません。大好き!ってわけでもありません。「javascript やりたいこと」でググって解決するレベルです。Googleがないと仕事できない。
そんな人がAngular2を試したら
- 「AngularJS1.x触ったことあるしへーきへーき」 -> (°ω° オレノシッテイルアンギュラハドコ?
- 「Typescript?ES6?なにそれ?」
- 「webpack?gulpとかnpmじゃだめなの?」
↑の状態で挑んだら簡単に死亡いたしました。本当にありg(ry
で、そんな人がググり続けたとしても、古い情報なのか新しい情報なのかわからるはずもなく、地雷を踏みづづけるだけで、一向に闇から抜け出せません。マジで挫折一歩手前。
近くに簡単に質問できる人もコミュニティもないため、洋書ですが本を購入することにしました。
- ng-book2
記事の内容のほとんどが↑の本から学習したことなので、この記事読まなくて↑の本読んだ方が早かったりするかもです(^^ゞ ※英語の本です。 ※私は英語はほとんど理解してませんが、Google翻訳を使って読むとびっくりするほど内容が理解できます。ありがとうGoogle。
この本購入してからAngular楽しいお(^ω^ = ^ω^)
本記事は ng-book2 を読んで、Angular Formで学習したこと、Angular Formで試したことのログをまとめた記事です。
Form in Angular2
この記事で試したソースコードはgithubに上がっています。実際にコードを動かしたほうが理解すると思いますー。
はじめの環境構築
必要なアプリケーションのインストール
この記事は、Typescriptとangular-cliを使います。また、OSはOSXです。windowsの方はすみません(´・ω・`)
自分の環境は以下。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G1108 $ node -v v6.6.0 $ npm -v 3.10.9
それでは環境構築します。
$ npm install -g typescript $ npm install -g angular-cli@1.0.0-beta.18 # angular-cliの監視オプションに使うツールらしい # linuxの人はどうすればいいの? # -> https://ember-cli.com/user-guide/#watchman # windowsの人はどうすればいいの? # -> なんか必要ないんだって。nodea.jsに実装されているwatcherを使うんだって $ brew install watchman
無事インストールできたら、確認。
$ ng version angular-cli: 1.0.0-beta.18 node: 6.6.0 os: darwin x64
おk
プロジェクトを作りましょう
$ ng new angular-hello-form-application
これだけ。That's it. 感動。
動作確認してみましょう
angular-cliはApacheとかwebアプリケーション的なものを用意しなくても、すぐに動作を確認できるオプションが用意されています。
$ cd angular-hello-form-application/
$ ng serve
** NG Live Development Server is running on http://localhost:4200. **
...
ng serve
実行後、すぐにURLが表示されるので、それをコピってブラウザで確認してみてください。
この画面がでてきたら、環境構築完了です!
それでは本題に進みます。
FormControlとFormGroup
Angular2でFormを使いたいなら、この2つのモジュールを利用します。
FormControlは、1つのinputフィールドを意味します。
let firstName = new FormControl("Aipa"); console.log(firstName.value); // "Aipa" console.log(firstName.errors); // 型はStringMap<string, any> of errors console.log(firstName.dirty); // 値は変わったか検知。boolean console.log(firstName.valid); // validateの結果が登録されます。boolean
templateで使いたい場合はこう書きます。
<!-- formの中で書く --> <input type="text" [formControl]="name">
んで、これらをまとめてくれるのが、FormGroupです。
let yourName = new FormGroup({ firstName: new FormControl('Aipa'), lastName: new FormControl('Commander') }); console.log(yourName.value); // -> { // firstName: "Aipa", // lastName: "Commander" // } console.log(yourName.errors); // StringMap<string, any> of errors console.log(yourName.dirty); // false console.log(yourName.valid); // true
というふうに、まとめることができます。
FormControlは1つのフィールド、FormGroupはフィールド全体を見てくれます。
例えば、firstName
のFormControlのバリデーションがfalseだった場合、yourName
のバリデーションもfalseになります。部分的にチェックしてエラーを表示したり、全体のバリデーションがうまくいかないとsubmitさせないなどができると思います。
yourName.formControl['firstName'].valid; // false yourName.formControl['lasttName'].valid; // true yourName.valid; // false
簡易フォームの実装
それでは、実際に実装してみましょー。
と、その前に、ちゃんと説明しようと書いていましたが、説明の仕方に悩んだときにこんな記事を見つけました。
;°ω°)モウコレデイイジャン
もっと早く会いたかったです。
まぁ自分は自分なりに説明書いてログにします。
本題に戻ります。先ほど作ったプロジェクトのディレクトリで、componentをgenerateします。
$ ng generate component first_form
まずは、generateしたcomponentに実装していきます。
おっと、その前にFormControlやFormGroupを使えるようにするために、~/angular-hello-form-application/src/app/app.module.ts
で、FormsModuleとReactiveFormsModuleをimportします。
~/angular-hello-form-application/src/app/app.module.ts
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; // <- add @NgModule({ declarations: [ AppComponent, FirstFormComponent ], imports: [ BrowserModule, FormsModule, HttpModule FormsModule, // <- add ReactiveFormsModule // <- add
これでおkです。
ちなみに、FormsModuleのみimportすると
- ngModel
- NgForm
を組み合わせて、Formを扱うことができます。ReactiveFormsModuleも一緒にimportすると
- formControl
- ngFormGroup
を含め、色々使えるようになります。
ふーん。Reactive リアクティブってなに?
ぐぐったらこの記事がでてきた。ふむ。
いまいちピンときていません。分かる人いたら教えて下さい。
まぁいまはなんだっていいでしょう。実装に話を戻します。
~/angular-hello-form-application/src/app/app.component.html
にタグを追加します。先ほどgenerateしたcomponentをrenderできるように追記します。
~/angular-hello-form-application/src/app/app.component.html
<app-first-form></app-first-form>
formの実装に入ります。
~/angular-hello-form/application/src/app/first-form/first-form.componet.html
に、formを追加します。
<form #f="ngForm" (ngSubmit)="onSubmit(f)"> <div class="form-group"> <label>お名前は?</label> <input type="text" placeholder="名前を入力してください" name="yourName" ngModel> </div> <div class="form-group"> <button type="submit">送信</button> </div> </form>
↑のように追加すればおkです。
突然あらわれる#f=ngForm
のngForm
にびっくりしましたが、これはFormsModuleをimportするだけで、NgForm(<form #f="ngForm" ...>
)がviewで使えるようになり、また自動的に<form>
にアタッチされます。
それでは、component.tsへメソッドを追加します。
~/angular-hello-form/application/src/app/first-form/first-form.componet.ts
export class FirstFormComponent implements OnInit { // ... onSubmit(form: any): void { console.log(form.value); } }
追加したフォームに"アイパー隊長"と入力し、送信ボタンをクリックすると、consoleから、Object {yourName: "アイパー隊長"}
がでてきました。これでよし。
#f=ngForm
の#f
はテンプレートで使用できるようするローカル変数です。つまりこの箇所はローカル変数を宣言している&値をセットしているのです。
#f
がフォームの値となります。「テンプレートで使える変数」という理解でおkだと思います。
また、NgForm
の型はFormGroup
です。なので、変数f
は、FormGroup
として扱うことができます。
最初のFormGroupとFormControlの関係を説明した通り、FormControlはFormGroupに追加されて扱えるようになります。
このフォームではngModel
がセットされているinputタグの値は、自動的にFormControlを作成して、属性であるname=***
で関連付けられます。そして、FormGroupである変数f
にセットされています。
このフォームでsubmitボタンをクリックすると、onSubmit
のイベントが発生します。その時実行されるのが、<form>
の属性にある(ngSubmit)="onSubmit(f.value)"
です。
Angularで()
は、イベントをセットするときに使う属性と理解しています。んで、実行されるのが右側のコードです。onSubmit(f.value)
は、Componentに実装してあげます。
上ですでに動きは確認しました。もちろんconsole.log()
だけではなく、フォームにセットされた値を確認したり、バリデーションしたり、サーバへ送信したりなど、やりたいことを実装してあげれば良いです。
よっしゃ!理解したぞ!(多分)
ここまでが、単純にフォームを使うだけのチュートリアルです。
FormBuilderでFormのカスタマイズ
ngForm、ngModelを使って、暗黙的にFormControl、FormGroupを使ってみました。
ただ、このままだとカスタマイズができません。
実践では、もっと複雑に、詳細にカスタマイズするはずですので、今度はそれができるFormBuilderを使ってみましょう。
今、自分もこのFormBuilderを実践で使っています。
ng-book2をFormBuilderはどういうふうに使えばいいの? 語るよりはまずは実際に実装してみました。
別コンポーネントをジェネレートします。
$ ng generate component use-form-builder
~/angular-hello-form/application/src/app/app.component.html
<h1> {{title}} </h1> <app-first-form></app-first-form> <app-use-form-builder></app-use-form-builder> <!-- これを追加 -->
んじゃ、使用するComponentで、FormBuilderをimportします。
~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.ts
import { FormBuilder, FormGroup } from '@angular/forms';
importしただけじゃ使えないので、DIしましょう。 ※"DI (dependency injection)"って何?の人。自分もよくわかっていないので、近くのエロい人たちに聞いてください。
export class UseFormBuilderComponent { myForm: FormGroup; constructor(formBuilder: FormBuilder) { // <- これがDI(らしい) this.myForm = formBuilder.group({ 'myName': ['Aipa'] }); } onSubmit(value: string): void { console.log('わたしの名前は' + value); } }
これで、FormBuilderが使えるようになりました。constructor()
の引数にセットすることで、このComponentで利用できるようになりました。
FormBuilderでよく使うメソッドは2つ
- control - FormControlを新規で作成します
- group - FormGroupを新規で作成します
この2つです。
formBuilder.group({})
は、FormGroupを返します。
また、formBuilder.group({})
の中に、KeyValueにセットされているmyForm: ['Aipa']
はFormConotrolです。また、['Aipa']
は初期値です。
ちなみに、array
ってなんてのもありますが、この記事の最後あたりで試したログがあります。
説明がだるくなってきたので、まずは動かしてみてみましょう。templateを用意して、ビルドしてみてください。
~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html
<h2>ふぉーむびるだーずふぁいたーずです。</h2> <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>お名前は?</label> <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']"> </div> <div class="form-group"> <button type="submit">送信</button> </div> </form>
実行するとこんな画面が出力されるはず。初期値が入っていますね。
また、submitしてみると、consoleに"わたしの名前はAipa"とでるはず。これでおkです。
少し説明します。
<form>
タグの属性に、[formGroup]="myForm"
とあります。この[formGroup]
は、ディレクティブとして動作しており、myForm
をこのフォームのFormGroup
として利用することを宣言しています。
次に、<input>
タグの属性に[formControl]="myForm.controls['myName']"
とあります。これは既存のFormControlにFormBuilderで作成したformControlをバインドしています。これでComponent側で細かく設定する情報を付与することができます。
Validation
次はValidationを試します。
~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @Component({ selector: 'app-use-form-builder', templateUrl: './use-form-builder.component.html', styleUrls: ['./use-form-builder.component.css'] }) export class UseFormBuilderComponent implements OnInit { myForm: FormGroup; constructor(formBuilder: FormBuilder) { // <- これがDI(らしい) this.myForm = formBuilder.group({ 'myName': ['Aipa', Validators.required] }); } ngOnInit() { } onSubmit(form: any): void { console.log(form); console.log('わたしの名前は' + form.controls['myName'].value); } }
~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html
<h2>ふぉーむびるだーずふぁいたーずです。</h2> <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group" [class.error]="!myForm.controls['myName'].valid && myForm.controls['myName'].touched"> <label>お名前は?</label> <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']"> <div *ngIf="!myForm.controls['myName'].valid">myName is invalid</div> <div *ngIf="myForm.controls['myName'].hasError('required')">myName is required</div> </div> <div class="form-group" *ngIf="!myForm.valid">Form is invalid</div> <div class="form-group"> <button type="submit">送信</button> </div> </form>
ValidationはValidators
をimportして利用します。
先ほど、key valueでformControlの設定をFormBuilder.group
へ渡しました、valueの値は配列であり、要素の0番目が初期値でした、Validationは要素の1番目にセットします。今回はValidators.required
(空文字NG)をセットしました。
templateには、エラーを表示するようにngIf
を用意しています。最初のほうで説明したformControlやformGroupにある、valid
を使っています。また、セットした特定のValidationのエラーを表示したい場合は、
myForm.controls['myName'].hasError('required')
を使って出力します。
次は複数のValidationをセットします。4文字以上入力しないとエラーを出力するようにしてみます。Validators.minLenght
を使います。
「複数あるから・・・」って考えたら、要素の2番めに配列でValidatorsを渡したくなると思います。それでも動きました。が、本ではそんな説明がありませんでした。(ネットにはちらほらありますが)
配列で渡すのは正規な方法ではないのかな?
この記事ではValidators.compose([])
を使います。
~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts
constructor(formBuilder: FormBuilder) { this.myForm = formBuilder.group({ 'myName': ['Aipa', Validators.compose([ // <- これを追加 Validators.required, Validators.minLength(4) ]) ] }); }
~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html
<h2>ふぉーむびるだーずふぁいたーずです。</h2> <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group" [class.error]="!myForm.controls['myName'].alid && myForm.controls['myName'].touched"> <label>お名前は?</label> <input type="text" placeholder="名前を入力してください" [formControl]="myForm.controls['myName']"> <div *ngIf="!myForm.controls['myName'].valid">myName is invalid</div> <div *ngIf="myForm.controls['myName'].hasError('required')">myName is required</div> <div *ngIf="myForm.controls['myName'].hasError('minlength')">myName must be at least 4 characters long.</div> <!-- ここを追加 --> </div> <div class="form-group" *ngIf="!myForm.valid">Form is invalid</div> <div class="form-group"> <button type="submit">送信</button> </div> <pre>{{myForm.controls['myName'].errors | json}}</pre> </form>
全然関係ないですが、このValidators.minLength(4)
、templateでエラーを出力する属性を用意したら何故か出力されませんでした。
最初書いていたコードはこちら。
<div *ngIf="myForm.controls['myName'].hasError('minLength')">myName must be at least 4 characters long.</div> <!-- ここを追加 -->
hasError('minLength') // "minLength" => ダメ、"minlenght" => おk
(°ω° キャメルケースじゃダメなのね。。。
<form>
の閉じタグ1行前で、<pre>{{myForm.controls['myName'].errors | json}}</pre>
で確認したら発覚しました。わかりづらい。
ちょっとハマったが、Validationを複数扱うときは、Validators.compose()
を使えばおk.
Custom Validation
Validationの続きですが、自前でValidationも用意することができます。
返り値はStringMap<string, boolean>
で返せば良いです。クラスがあるとわかりにくいのでようは
{ [s: string]: boolean }
これを返せばよい。
~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts
function fullMatchStringAipa(control: FormControl): { [s: string]: boolean } { // 初期値がない場合はエラーになったので // nullチェックもする if (control.value) { if (!control.value.match(/^Aipa/)) { return {invalidAipa: true}; } } } // ... 省略 constructor(formBuilder: FormBuilder) { this.myForm = formBuilder.group({ 'myName': ['Aipa', Validators.compose([ Validators.required, Validators.minLength(4), fullMatchStringAipa // <- これを追加 ]) ] }); }
~/angular-hello-form/application/src/app/use-form-builder/use-form-builder.component.html
<div *ngIf="myForm.controls['myName'].hasError('invalidAipa')">myName is string "Aipa"</div>
hasError()
で渡す値は、自前で用意したValidationの返り値{ [s: string]: boolean }
のs: string
の値を渡してください。
Watching for changes
formControlの値の変更を検知することができます。AngularJS1.xではすごく苦労した記憶があります(物覚え悪いからだと思いますが)。今回はどうでしょう。
~/angular-hello-form/application/src/app/use-form/builder/use-form-builder.component.ts
constructor(formBuilder: FormBuilder) { this.myForm = formBuilder.group({ 'myName': ['Aipa', Validators.compose([ Validators.required, Validators.minLength(4), fullMatchStringAipa ]) ] }); // 追加した記述 this.myForm.controls['myName'].valueChanges.subscribe( (value: string) => { console.log('aipa value change: ', value); } ); }
個人的にはすごくわかりやすかったです! こんなすぐ使えるのねー。
次は試したかったこと。です。
Form in Angular2 で、試したこと
Enter keyの無効化
フォームで入力中にEnter keyを2度押してしまって、ページが遷移するってことありません? わたしはよくあります。
あれが嫌で、作成するフォームはだいたい<form onsubmit="return false;">
をしています。
これが良いか悪いかの判断はできませんが(できる方、教えてくださいm(_ _ )m )、とりあえずAngular2でもやりたかったので、試してみました。
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm.value)" (keydown.enter)="keyDown($event)" <!-- 追加 --> class="ui form"> </form>
keydown.enter
で、エンターキーが押されたときのイベントが発火したときに、メソッドをコールすることができる。Component側に下記を実装。
keyDown(event: any): void { console.log('You just clicked entry.'); return event.preventDefault(); // enterを無効化 }
これでできました。
Input form bind date picker
日付入力のフォームには、date pickerをよく使いますよね。jQueryだったらすぐ使えます。
が、Angular2ではどう扱ったらいいのはわからず、ググり方もわからなかったので、時間を多く浪費しました。
pluginも色々試しましたが、何故かうまくいかなかったんですよねー。
色々学んできて、やっとできたので、これもメモしておきます。
3rd Party Library Installation
まず、必要なライブラリをインストールします。
$ npm install pikaday moment --save-dev
今回はpikadayを使います。また、moment.js
も依存しているため、これもインストール。
んで、ビルドツールである、angular cli のjsonファイルに追記します。
~/angular-hello-form-application/angular-cli.json
"app": [{ "styles": [ "styles.css", "../node_modules/pikaday/scss/pikaday.scss" ], "scripts": [ "../node_modules/pikaday/pikaday.js", "../node_modules/moment/moment.js" ] }]
次に使いやすいようにdirectiveをを用意します。
$ ng generate directive date-picker installing directive create src/app/date-picker.directive.spec.ts create src/app/date-picker.directive.ts
import { Directive, ElementRef, Input } from '@angular/core'; // 宣言? declare let Pikaday: any; @Directive({ selector: '[appDatePicker]' }) export class DatePickerDirective { // この変数は呼び元コンポーネントから値をもらうので // @Input()の値をselectorと同じにする @Input('appDatePicker') datePickerField: any; constructor( // pikadayのクラスのbindにはフィールドの情報が必要なため // ElementRefを使って自分のフィールドを渡す private elementRef: ElementRef ) {} ngOnInit() { var picker = new Pikaday({ field: this.elementRef.nativeElement, format: 'YYYY/MM/DD', i18n: { previousMonth : '先月', nextMonth : '来月', months : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], weekdays : ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'], weekdaysShort : ['日','月','火','水','木','金','土'] }, onSelect: (date) => { // 何故かpikadayをbindしただけじゃ、formControlの値を変更することができない // onSelect時に、直接formControlへ選択した文字列を渡すようにする this.datePickerField.setValue(picker.toString()); } }); } }
ちなみに、悩みとしては、angular cliでビルドしていますが、pikadayのcssなどが、htmlへinlineで追加されてしまい、他のcssに影響がでています。
上書きしてもいいですが、あとあと絶対面倒になるので、どうにか必要なページだけpikaday、pikadayのcssをロードできないか方法を探し中です。知っている人いましたら教えてください(><
追記 2016/12/26
必要なページだけ読み出す方法がわかりました。
まず、angular-cliに追加した情報を削除します。
~/angular-hello-form-application/angular-cli.json
"app": [{ "styles": [ "styles.css", "../node_modules/pikaday/scss/pikaday.scss" // 削除 ], "scripts": [ "../node_modules/pikaday/pikaday.js", // 削除 "../node_modules/moment/moment.js" // 削除 ] }]
んで、directiveをcomponentへ変更して、デコレータのパラメータを変更します。というか、Componentとして作り直したほうがいいかもしれません。
~/src/app/date-picker/date-picker.component.ts
import { Component, ElementRef, Input, ViewEncapsulation } from '@angular/core'; // 宣言? const Pikaday = require('../../../node_modules/pikaday/pikaday'); const PikadayStyle = require('../../../node_modules/pikaday/scss/pikaday.scss'); @Component({ selector: '[appDatePicker]', template: '', styleUrls: [PikadayStyle], encapsulation: ViewEncapsulation.None }) export class DatePickerComponent {
個別のページだけモジュールを使うって方法がわからなかったので、汚い英語ですが、stackoverflowに投稿して回答を待ちましたところ、「require
を使えばいけるよ」的な回答を頂いたので、試してみたらまじでうまくいきました(°ω°
自分の実装はDirectiveで試していましたが、styleをあてたかったのと、Directiveのデコレータでstyleを指定する方法がわからなかったので、Componentへ変更しました。
また、Componentが吐き出すstyleは、cssのセレクターに動的の属性が当てられます。しかし、pikadayのdomはComponentの属性が付与されないため、単純にComponentを用意するだけではstyleはあたりません。
なので、encapsulation
の値に、ViewEncapsulation.None
を渡してあげると、(よろしくないとは思いますが)属性を付与しなくなるので、pikadayのdomにstyleがあたるようになります。これでおkです。
これで必要なComponentだけstyleとmoduleをロードすることができるようになりました。githubの方も修正しています。
動的なフォーム
これが一番やりたかった。
ng-book2 にはのっていなかったので、できるか心配でしたが、色々ググって試してみたらできたのでメモしておきます。
$ ng generate component dynamic-form installing component create src/app/dynamic-form/dynamic-form.component.css create src/app/dynamic-form/dynamic-form.component.html create src/app/dynamic-form/dynamic-form.component.spec.ts create src/app/dynamic-form/dynamic-form.component.ts
~/angular/hello-form/application/src/app/dynamic-form/dynamic-form.component.html
<h2>動的フォーム</h2> <form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <div class="form-group"> <label>お名前は?</label> <input type="text" [formControl]="myForm.controls['yourName']"> </div> <!-- この下のタグを渡せないと動作しなかった。group -> arrayにも名前を用意しないといけないらしい --> <div formArrayName="whatAnimationDoYouLike"> <div *ngFor="let testDatetime of myForm.controls.whatAnimationDoYouLike.controls; let i=index"> <div [formGroupName]="i"> <div class="delete-button"> <span>Address {{i + 1}}</span> <button type="button" *ngIf="myForm.controls.whatAnimationDoYouLike.controls.length > 1" (click)="removeGroup(i)">フォームから削除</button> </div> <div class="form-group"> <label>アニメの名前は?(必須)</label> <input type="text" [formControl]="myForm.controls['whatAnimationDoYouLike'].controls[i].controls['answer']"> </div> <div class="form-group"> <label>好きな理由は?(任意)</label> <input type="text" [formControl]="myForm.controls['whatAnimationDoYouLike'].controls[i].controls['reason']"> </div> </div> </div> </div> <div class="margin-20"> <button type="button" (click)="addGroup()">フォームを追加</button> </div> <pre>{{myForm.value | json}}</pre> <div class="form-group"> <button type="submit">送信</button> </div> </form>
属性formArrayName="whatAnimationDoYouLike"
の箇所。これも必須です。Cannot find control with unspecified name attribute
のエラーが発生します。group -> array
にも名前を用意しないといけないらしい。
下はComponentです。動的なフォームを組みたい場合、FormArrayが必要になりますので、importしてください。
~/angular/hello-form/application/src/app/dynamic-form/dynamic-form.component.ts
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; @Component({ selector: 'app-dynamic-form', templateUrl: './dynamic-form.component.html', styleUrls: ['./dynamic-form.component.css'] }) export class DynamicFormComponent implements OnInit { myForm: FormGroup; constructor( private formBuilder: FormBuilder ) { } ngOnInit() { this.myForm = this.formBuilder.group({ 'yourName': ['', Validators.required], 'whatAnimationDoYouLike': this.formBuilder.array([ this.initGroup() ]) }); } initGroup() { return this.formBuilder.group({ 'answer': ['', Validators.required], 'reason': [''] }); } addGroup() { const arrayControls = this.getFormArrayControls(); arrayControls.push(this.initGroup()); } removeGroup(i: number) { const arrayControls = this.getFormArrayControls(); arrayControls.removeAt(i); } getFormArrayControls() { return <FormArray>this.myForm.controls['whatAnimationDoYouLike']; } onSubmit(form: any) { console.log(form); } }
initGroup()
で、複数のformControlをgroupingして値を返します。
addGroup()
は、今現在のFormArray
を取得して、initGroup()
から追加データをpush()
して追加しています。
removeGroup
はその逆です。現在のFormArray
を取得して、指定された要素番号でFormArray
を、removeAt(i)
しています。i: number
はテンプレートからもらう情報です。
これでおkです。もしFormArray
の値をwatchしたいとかあれば、this.myForm.controls['whatAnimationDoYouLike'].controls[i].controls['answer']
とかでvalueChanges
を実行すればおkです。ながいねー。
まとめ(?)
1ヶ月前は「なんでこのフレームワークを選んだんだ(´;ω;`)ブワッ」と泣きそうになりましたが、理解してくると楽しくなってきますねー。
近くに教えてくれる人がいると手っ取り早いですが、そうじゃない場合はやっぱり本がいいのではと思います。
引き続き、Angular2を学んで、使えるようになったらまた色々と記事を上げていきたいです。