如何在Angular中观察表单更改

时间:2016-01-05 15:33:36

标签: angular

在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

6 个答案:

答案 0 :(得分:174)

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

答案 1 :(得分:107)

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

如果您未使用FormBuilder,则有两种方式可以通知更改。

方法1

正如对问题的评论中所讨论的,在每个输入元素上使用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不是<input>元素事件。它实际上是NgModel指令的事件属性。当Angular看到[(x)]形式的绑定目标时,它期望x指令具有x输入属性和xChange输出属性。

     

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

     

我们几乎总是喜欢[(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's video

另请参阅@ Thierry的答案,了解有关使用valueChanges可观察对象的更多信息(例如在处理更改之前进行去抖/等待)。

答案 2 :(得分:60)

要完成更多以前的好答案,您需要注意表单利用observable来检测和处理值更改。它是非常重要和强大的东西。 Mark和dfsq都在他们的答案中描述了这一方面。

Observable不仅允许使用subscribe方法(与Angular 1中的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中思考处理表单的方式(一种更强大的方式; - ))。

希望它可以帮到你, 亨利

答案 3 :(得分:1)

扩展马克的建议......

方法3

实施&#34;深度&#34;改变模型的检测。优点主要涉及避免将用户界面方面结合到组件中;这也捕获了对模型进行的程序化更改。也就是说,需要额外的工作来实现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

答案 4 :(得分: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));
    }
}

答案 5 :(得分:0)

对于有角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)
        })
}