リアクティブフォームでの valueChanges の利用方法

テキスト入力ボックスと Angular コンポーネントのプロパティを関連付ける方法としては、まずひとつには ツーウェイ・バインディング を利用する方法があります。 [(ngModel)] にプロパティを指定するだけで使えるので非常に簡単です。

ただ、この場合変更がダイレクトに反映されるので、変更した時に何かの処理をしたいといった場合には、 テンプレート参照変数イベントバインディングを組み合わせ使うなどしました。

こうなると、だんだんややこしくなってきます。

さらに、「ユーザーのテキスト入力が何文字以上の時に処理を行う」とか「ユーザーが一連の文字を打ち込み終わってから」処理をするなどが組み合わされてくると、 単純なデータバインディングよりも、ここで紹介する FormControl を使う方が簡単になります。

FormControl を使う具体例

まずはテキストボックスに入力した文字を、直ちにコンポーネントのプロパティにセットする例です。

テキストボックスの値が変わったところで、 コンソールにログを書き出すようにすると、以下のように一文字一文字、文字を入力する度にイベントハンドラがコールバックされることがわかります。

上でイベントハンドラと書きましたが、実は Angular の体系では FormControl オブジェクトの valueChanges イベントを subscribe するといいます。 valueChanges は string 型の Observable なので、subscribe することができ string を受けとることができるのです。

src/app/myform.component.ts は次の通り。

import {Component, OnInit} from '@angular/core';
import {FormControl} from '@angular/forms';

@Component({
  selector: 'myform',
  templateUrl: './myform.component.html'
})
export class MyFormComponent implements OnInit{
    text1 = new FormControl();
    s: string;

    ngOnInit(){
      this.text1.valueChanges
        .subscribe(v=>{
          console.log(v);
          this.s = v;
        })
    }
}

テンプレート src/app/myform.component.html は次の通り。

<input [formControl]="text1" type="text">
<div>{{s}}</div>

formControl でコンポーネントのプロパティと繋ぎます。

ルートモジュールに上で作った MyFormComponent と ReactiveFormsModule をインポートします。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { MyFormComponent } from './myform.component';

@NgModule({
  declarations: [
    AppComponent,
    MyFormComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

次のエラー (Template parse errors Can't bind to 'formControl' since it isn't a known property of 'input') が出る場合があります。

この時はルートモジュールにて ReactiveFormsModule をインポートするのを忘れている可能性があります。

ReactiveFormsModule をインポートすることで、 input 要素に必要なプロパティが追加されます。

Observable のフィルタ

上の例では単にイベントを拾って値をセットしているだけなので、わざわざ、やれ Observable だのと、なにやら面倒くさそうなモノを引っ張り出してくる利点は明確ではありません。

次の例では Observable の良いところがでてきます。

下のスクリーンショットでは、テキストボックスで入力した文字をプロパティにセットするのは同じですが、 コンソールに出力されているのは、よりまとまった単位になっています。

入力文字を評価する場合、あるまとまった単位で評価したい場合があります。

例えば、"Hello" という言葉の意味を調べるために、Hello と文字を入力しているとしたら、最後まで打ち込むまでの途中過程である、He とか Hel、Hell などの文字を評価することに意味がありません。無駄です。

そこで、入力の手が止まったことを合図に文字を評価するなどできれば、無駄を省くことができます。

Observable を使うと、そうしたフィルタリングなどを行うことが容易にできます。

入力が止まるまで少し待つ、というのはObservable を実装するリアクティブエクステンション (RxJS) の debounceTime で実現できます。

3 文字以上のみ評価 (filter)、変更があったときのみ評価 (distinctUntilChanged) などもあります。

利用例は以下の通りです。

import {Component, OnInit} from '@angular/core';
import {FormControl} from '@angular/forms';

import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
  selector: 'myform',
  templateUrl: './myform.component.html'
})
export class MyFormComponent implements OnInit{
    text1 = new FormControl();
    s: string;

    ngOnInit(){
      this.text1.valueChanges
        .debounceTime(400)
        .filter(v=> v.length >= 3)
        .distinctUntilChanged()
        .subscribe(v=>{
          console.log(v);
          this.s = v;
        })
    }
}

ここでは debounceTime オペレータに 400 を指定しているので、以前のイベントから 400ms 以内に発生したイベントを無視できます。

これらのオペレータは明示的に import する必要があります。

以上で、FormControl の valueChanges の利用方法を示しました。