Angularでフォームの変更を監視する方法


151

Angularでは、次のようなフォームがあるかもしれません:

<ng-form>
    <label>First Name</label>
    <input type="text" ng-model="model.first_name">

    <label>Last Name</label>
    <input type="text" ng-model="model.last_name">
</ng-form>

対応するコントローラー内で、そのフォームの内容の変更を次のように簡単に監視できます。

function($scope) {

    $scope.model = {};

    $scope.$watch('model', () => {
        // Model has updated
    }, true);

}

ここでJSFiddle上の角度例が

Angularで同じことを達成する方法を理解するのに苦労しています。明らかに$scope、$ rootScope はもうありません。きっと同じことを成し遂げる方法はありますか?

これがPlunkerのAngularの例です


コントローラからのデータを監視する代わりに、フォームからイベント(古いng-changeなど)を起動する必要があると思います。
Deblaton Jean-Philippe、2016

ところで、スコープを削除する理由は、それらのウォッチャーを取り除くためです。角度2のどこかに隠されている時計はないと思います
Deblaton Jean-Philippe

4
私があなたを正しく理解しているなら、これを達成するためにすべてのフォーム入力に(ngModelChange)="onModelChange($event)"属性を追加することを提案していますか?
タンブラー2016年

1
フォームインスタンス自体は、何らかの変更イベントを発行しませんか?もしそうなら、どのようにアクセスしますか?
タンブラー2016年

私が何をすべきかについて確信していれば、コメントする代わりに答えたでしょう。私はまだAngular 2.0を使用していませんが、私が読んだことから、時計は完全に消え、イベントベースのフレームワークを持っています(各ダイジェストで実行される深い時計の代わりに)
Deblaton Jean-Philippe

回答:


189

UPD。回答とデモは最新のAngularに合わせて更新されています。


フォームを表すFormGroupvalueChangesがObserverableインスタンスであるプロパティを提供するため、フォーム全体の変更をサブスクライブできます。

this.form.valueChanges.subscribe(data => console.log('Form changes', data));

この場合、FormBuilderを使用して手動でフォームを作成する必要があります。このようなもの:

export class App {
  constructor(private formBuilder: FormBuilder) {
    this.form = formBuilder.group({
      firstName: 'Thomas',
      lastName: 'Mann'
    })

    this.form.valueChanges.subscribe(data => {
      console.log('Form changes', data)
      this.output = data
    })
  }
}

valueChangesこのデモで実際にチェックしてください:http : //plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
これはマークに非常に近いです。確認するには- フォームがテンプレート内でのみ定義されている場合、フォームのイベントエミッターをサブスクライブすることはできないと言っていますか?つまり、コンポーネントのコンストラクタ内では、FormBuilderを使用せずに、そのコンポーネントのテンプレート内でのみ定義されたフォームへの参照を取得することはできませんか?valueChanges
タンブラー2016年

これが正解です。フォームビルダーがない場合は、テンプレート駆動型フォームを使用しています。おそらくフォームを挿入する方法はおそらくありますが、Observable form.valueChangesが必要な場合は、必ずformBuilderを使用してng-modelを放棄する必要があります
Angular University

2
@tambler、を使用してNgFormへの参照を取得できます@ViewChild()。私の更新された答えを見てください。
Mark Rajcok、2016年

1
destroyで退会する必要はありませんか?
Bazinga

1
@galvanリークが発生することはないと思います。フォームはコンポーネントの一部であり、すべてのフィールドとイベントリスナーとともに破棄時に適切に破棄されます。
dfsq 2017

107

を使用している場合はFormBuilder、@ dfsqの回答を参照してください。

を使用していない場合FormBuilder、変更を通知するには2つの方法があります。

方法1

質問のコメントで説明したように、各入力要素でイベントバインディングを使用します。テンプレートに追加:

<input type="text" class="form-control" required [ngModel]="model.first_name"
         (ngModelChange)="doSomething($event)">

次に、コンポーネントで:

doSomething(newValue) {
  model.first_name = newValue;
  console.log(newValue)
}

フォームのページはここでは関係ありngModelに関するいくつかの追加情報があります。

ngModelChangeありません<input>要素イベント。これは実際にはNgModelディレクティブのイベントプロパティです。Angularはフォーム[(x)]でバインディングターゲットを見つけると、xディレクティブがx入力プロパティとxChange出力プロパティを持つことを期待します。

もう1つの奇妙な点は、テンプレート式ですmodel.name = $event$eventDOMイベントからのオブジェクトを見るのに慣れています。ngModelChangeプロパティはDOMイベントを生成しません。これEventEmitterは、起動時に入力ボックスの値を返すAngular プロパティです。

私たちはほとんど常に好み[(ngModel)]ます。キーストロークのデバウンスやスロットルなどのイベント処理で特別なことをする必要がある場合は、バインディングを分割することがあります。

あなたの場合、何か特別なことをしたいと思います。

方法2

ローカルテンプレート変数を定義し、それをに設定しngFormます。
入力要素でngControlを使用します。
@ViewChildを使用してフォームのNgFormディレクティブへの参照を取得し、変更のためにNgFormのControlGroupをサブスクライブします。

<form #myForm="ngForm" (ngSubmit)="onSubmit()">
  ....
  <input type="text" ngControl="firstName" class="form-control" 
   required [(ngModel)]="model.first_name">
  ...
  <input type="text" ngControl="lastName" class="form-control" 
   required [(ngModel)]="model.last_name">

class MyForm {
  @ViewChild('myForm') form;
  ...
  ngAfterViewInit() {
    console.log(this.form)
    this.form.control.valueChanges
      .subscribe(values => this.doSomething(values));
  }
  doSomething(values) {
    console.log(values);
  }
}

plunker

方法2の詳細については、Savkinのビデオを参照してください

valueChangesオブザーバブルで何ができるかについての詳細は、@ Thierryの回答も参照してください(変更を処理する前にデバウンス/ビットを待機するなど)。


61

以前のすばらしい回答をもう少し完了するには、フォームがオブザーバブルを利用して値の変化を検出して処理することを認識する必要があります。それは本当に重要で強力なものです。Markとdfsqはどちらも、この点について回答で説明しています。

オブザーバブルでは、subscribeメソッドの使用だけでなく、then Angular 1のpromiseのメソッドにます。必要に応じて、更新されたデータの処理チェーンをフォームに実装するために、さらに先へ進むことができます。

debounceTimeメソッドでデバウンス時間をこのレベルで指定できることを意味します。これにより、変更を処理する前に一定の時間待機し、いくつかの入力を正しく処理できます。

this.form.valueChanges
    .debounceTime(500)
    .subscribe(data => console.log('form changes', data));

値が更新されたときにトリガーする処理(非同期など)を直接接続することもできます。たとえば、AJAXリクエストに基づいてリストをフィルタリングするためにテキスト値を処理する場合は、switchMapメソッドを活用できます。

this.textValue.valueChanges
    .debounceTime(500)
    .switchMap(data => this.httpService.getListValues(data))
    .subscribe(data => console.log('new list values', data));

返されたオブザーバブルをコンポーネントのプロパティに直接リンクすることで、さらに先に進みます。

this.list = this.textValue.valueChanges
    .debounceTime(500)
    .switchMap(data => this.httpService.getListValues(data))
    .subscribe(data => console.log('new list values', data));

asyncパイプを使用して表示します。

<ul>
  <li *ngFor="#elt of (list | async)">{{elt.name}}</li>
</ul>

Angular2でフォームを異なる方法で処理する方法を考える必要があると言うだけです(はるかに強力な方法;-))。

それがあなたを助けることを願って、ティエリー


プロパティ 'valueChanges'はタイプ 'string'に存在しません
Toolkit

チェックフォームの変更方法を配置する必要があるTSファイル内でスタックしましたか?ngAfterViewInit(){}に保存すると、フォーム内の更新されたデータの処理チェーンを実装する必要がある場合、this.form.valueChangesが常に呼び出されるようです。
TAIグエン

1

マークの提案を拡張しています...

方法3

モデルに「深い」変更検出を実装します。利点は主に、ユーザーインターフェイスの側面をコンポーネントに組み込むことを回避することです。これは、モデルに加えられたプログラムによる変更もキャッチします。とは言っても、ティエリーの提案によるデバウンスなどの実装には追加の作業が必要であり、これも独自のプログラムによる変更をキャッチするため、注意して使用してください。

export class App implements DoCheck {
  person = { first: "Sally", last: "Jones" };
  oldPerson = { ...this.person }; // ES6 shallow clone. Use lodash or something for deep cloning

  ngDoCheck() {
    // Simple shallow property comparison - use fancy recursive deep comparison for more complex needs
    for (let prop in this.person) {
      if (this.oldPerson[prop] !==  this.person[prop]) {
        console.log(`person.${prop} changed: ${this.person[prop]}`);
        this.oldPerson[prop] = this.person[prop];
      }
    }
  }

プランカーで試す


1

角度5+バージョン用。角度を付けることで多くの変更が行われるため、バージョンを置くと役立ちます。

ngOnInit() {

 this.myForm = formBuilder.group({
      firstName: 'Thomas',
      lastName: 'Mann'
    })
this.formControlValueChanged() // Note if you are doing an edit/fetching data from an observer this must be called only after your form is properly initialized otherwise you will get error.
}

formControlValueChanged(): void {       
        this.myForm.valueChanges.subscribe(value => {
            console.log('value changed', value)
        })
}

0

(ngModelChange)メソッドの使用を検討し、次にFormBuilderメソッドを検討し、最後にメソッド3のバリエーションに着手しました。これにより、追加の属性でテンプレートを装飾する手間が省け、モデルへの変更が自動的に取得されるため、何かを忘れる可能性が少なくなります方法1または2を使用します。

方法3を少し簡略化...

oldPerson = JSON.parse(JSON.stringify(this.person));

ngDoCheck(): void {
    if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) {
        this.doSomething();
        this.oldPerson = JSON.parse(JSON.stringify(this.person));
    }
}

タイムアウトを追加して、デバウンスをシミュレートするためにxミリ秒後にdoSomething()を呼び出すだけにすることができます。

oldPerson = JSON.parse(JSON.stringify(this.person));

ngDoCheck(): void {
    if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) {
        if (timeOut) clearTimeout(timeOut);
        let timeOut = setTimeout(this.doSomething(), 2000);
        this.oldPerson = JSON.parse(JSON.stringify(this.person));
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.