【问题标题】:How to watch for form changes in Angular如何在 Angular 中观察表单变化
【发布时间】:2016-04-09 11:57:55
【问题描述】:

在 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);

}

这是Angular example on JSFiddle

我无法弄清楚如何在 Angular 中完成同样的事情。显然,我们不再有$scope、$rootScope。肯定有一种方法可以完成同样的事情吗?

这是Angular example on Plunker

【问题讨论】:

  • 我认为您应该从您的表单中触发一个事件(例如旧的 ng-change),而不是从您的控制器中查看一些数据。
  • 顺便说一句,删除范围的原因是摆脱那些观察者。我不认为在 angular 2 的某个地方隐藏了一块手表
  • 如果我对您的理解正确,您是在建议我为 每个 表单输入添加一个 (ngModelChange)="onModelChange($event)" 属性以完成此操作?
  • 表单实例本身不会发出某种更改事件吗?如果是这样,您如何访问它?
  • 如果我确定要做什么,我会回答而不是评论。我还没有使用过 Angular 2.0,但从我读到的内容来看,手表已经完全消失,拥有一个基于事件的框架(而不是在每个摘要中执行的深度手表)

标签: angular


【解决方案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)
        })
}

【讨论】:

    【解决方案2】:

    我考虑过使用 (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));
        }
    }
    

    【讨论】:

      【解决方案3】:

      UPD。答案和演示已更新以与最新的 Angular 保持一致。


      您可以订阅整个表单更改,因为代表表单的FormGroup 提供了一个可观察实例的valueChanges 属性:

      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

      【讨论】:

      • 这非常接近标记。确认一下 - 您是否告诉我不可能订阅表单的valueChanges 事件发射器如果该表单仅在模板中定义?换句话说 - 在组件的构造函数中,是否不可能获得对仅在该组件的模板中定义的表单的引用,而不是在 FormBuilder 的帮助下?
      • 这是正确的答案。如果您没有表单生成器,则您正在制作模板驱动的表单。可能有一种方法仍然可以注入表单,但如果你想要 Observable form.valueChanges 你应该明确地使用 formBuilder 并放弃 ng-model
      • @tambler,您可以使用@ViewChild() 获得对 NgForm 的引用。请参阅我的更新答案。
      • 销毁时不需要退订?
      • @galvan 我认为不会有泄漏。表单是组件的一部分,它会在销毁时与其所有字段和事件侦听器一起正确处理。
      【解决方案4】:

      扩展 Mark 的建议...

      方法3

      对模型实施“深度”变更检测。优点主要涉及避免将用户界面方面合并到组件中;这也捕获了对模型所做的编程更改。也就是说,按照 Thierry 的建议来实现诸如去抖动之类的东西需要额外的工作,而且这也会捕获您自己的程序更改,因此请谨慎使用。

      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];
            }
          }
        }
      

      Try in Plunker

      【讨论】:

        【解决方案5】:

        如果您使用的是FormBuilder,请参阅@dfsq 的回答。

        如果您不使用FormBuilder,有两种方法可以收到更改通知。

        方法一

        正如问题的 cmets 中所讨论的,在每个输入元素上使用 event binding。添加到您的模板:

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

        然后在你的组件中:

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

        Forms 页面包含一些与此处相关的 ngModel 的附加信息:

        ngModelChange 不是 &lt;input&gt; 元素事件。它实际上是NgModel 指令的事件属性。当 Angular 看到[(x)] 形式的绑定目标时,它期望x 指令具有x 输入属性和xChange 输出属性。

        另一个奇怪的是模板表达式model.name = $event。我们习惯于看到来自 DOM 事件的 $event 对象。 ngModelChange 属性不会产生 DOM 事件;它是一个 Angular EventEmitter 属性,它在触发时返回输入框值..

        我们几乎总是更喜欢[(ngModel)]。如果我们必须在事件处理中做一些特殊的事情,例如去抖动或限制击键,我们可能会拆分绑定。

        在你的情况下,我想你想做一些特别的事情。

        方法二

        定义一个本地模板变量并将其设置为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's video

        另请参阅@Thierry 的回答,了解有关您可以使用 valueChanges 可观察对象做什么的更多信息(例如在处理更改之前去抖动/稍等片刻)。

        【讨论】:

          【解决方案6】:

          要完成更多以前的精彩答案,您需要注意表单利用可观察对象来检测和处理值变化。这是非常重要和强大的东西。 Mark 和 dfsq 在他们的回答中都描述了这方面。

          Observables 不仅允许使用 subscribe 方法(类似于 Angular 1 中 Promise 的 then 方法)。如果需要,您可以更进一步,为表单中的更新数据实现一些处理链。

          我的意思是你可以在这个级别使用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));
          

          您甚至可以通过将返回的 observable 直接链接到组件的属性来更进一步:

          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”
          • 卡住了,在TS文件中,check form change方法应该放在哪里?如果我们将它存储在 ngAfterViewInit() {} 中,如果我们需要为表单中的更新数据实现一些处理链,似乎总是调用 this.form.valueChanges。
          猜你喜欢
          • 1970-01-01
          • 2018-03-01
          • 2018-06-02
          • 1970-01-01
          • 2017-01-28
          • 1970-01-01
          • 1970-01-01
          • 2013-01-23
          • 1970-01-01
          相关资源
          最近更新 更多