Làm thế nào để theo dõi sự thay đổi hình thức trong Angular


151

Trong Angular, tôi có thể có một hình dạng như thế này:

<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>

Trong bộ điều khiển tương ứng, tôi có thể dễ dàng theo dõi các thay đổi đối với nội dung của biểu mẫu đó như sau:

function($scope) {

    $scope.model = {};

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

}

Đây là một ví dụ Angular trên JSFiddle .

Tôi gặp khó khăn khi tìm cách hoàn thành điều tương tự trong Angular. Rõ ràng, chúng ta không còn có $scope, $ rootScope. Chắc chắn có một phương pháp mà điều tương tự có thể được thực hiện?

Đây là một ví dụ Angular trên Plunker .


thay vì xem một số dữ liệu từ bộ điều khiển của bạn, tôi nghĩ bạn nên kích hoạt một sự kiện (như thay đổi ng cũ) từ biểu mẫu của bạn.
Deblaton Jean-Philippe

btw, lý do để loại bỏ phạm vi là để thoát khỏi những người theo dõi. Tôi không nghĩ có một chiếc đồng hồ được giấu ở đâu đó trong góc 2
Deblaton Jean-Philippe

4
Nếu tôi hiểu bạn một cách chính xác, bạn có gợi ý rằng tôi thêm một (ngModelChange)="onModelChange($event)"thuộc tính cho mọi hình thức nhập để thực hiện điều này không?
tambler

1
Bản thân biểu mẫu không phát ra một số loại sự kiện thay đổi? Nếu vậy, làm thế nào để bạn truy cập nó?
tambler

Nếu tôi chắc chắn về những gì phải làm, tôi sẽ trả lời thay vì bình luận. Tôi chưa sử dụng angular 2.0, nhưng từ những gì tôi đã đọc, chiếc đồng hồ đã hoàn toàn biến mất để có khung dựa trên sự kiện (thay vì đồng hồ sâu được thực hiện ở mỗi tiêu hóa)
Deblaton Jean-Philippe

Câu trả lời:


189

CẬP NHẬT. Câu trả lời và bản demo được cập nhật để phù hợp với Angular mới nhất.


Bạn có thể đăng ký thay đổi toàn bộ biểu mẫu do thực tế là Formgroup đại diện cho một biểu mẫu cung cấp thuộc valueChangestính là một thể hiện có thể quan sát được:

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

Trong trường hợp này, bạn sẽ cần xây dựng biểu mẫu bằng tay bằng cách sử dụng FormBuilder . Một cái gì đó như thế này:

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
    })
  }
}

Kiểm tra valueChangesbằng hành động trong bản demo này : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
Điều này rất gần với nhãn hiệu. Để xác nhận - bạn có nói với tôi rằng không thể đăng ký vào trình valueChangesphát sự kiện của biểu mẫu nếu biểu mẫu đó chỉ được xác định trong mẫu không? Nói cách khác - trong hàm tạo của thành phần, không thể có được tham chiếu đến một biểu mẫu chỉ được xác định trong mẫu của thành phần đó chứ không phải với sự trợ giúp của FormBuilder?
tambler

đây là câu trả lời đúng. Nếu bạn không có trình tạo biểu mẫu, bạn đang thực hiện các biểu mẫu hướng mẫu. Có lẽ có một cách để vẫn nhận được biểu mẫu được tiêm nhưng nếu bạn muốn biểu mẫu có thể quan sát được. Giá trị thay đổi, bạn nên sử dụng formBuilder và từ bỏ mô hình ng
Đại học Angular

2
@tambler, bạn có thể tham khảo NgForm bằng cách sử dụng @ViewChild(). Xem câu trả lời cập nhật của tôi.
Mark Rajcok

1
Bạn không cần hủy đăng ký phá hủy?
Bazeda

1
@galvan Tôi không nghĩ có thể bị rò rỉ. Biểu mẫu là một phần của thành phần và nó sẽ được xử lý đúng cách khi hủy với tất cả các trường và trình lắng nghe sự kiện của nó.
dfsq

107

Nếu bạn đang sử dụng FormBuilder, hãy xem câu trả lời của @ dfsq.

Nếu bạn không sử dụng FormBuilder, có hai cách để được thông báo về các thay đổi.

Phương pháp 1

Như đã thảo luận trong các ý kiến ​​về câu hỏi, sử dụng một ràng buộc sự kiện trên mỗi yếu tố đầu vào. Thêm vào mẫu của bạn:

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

Sau đó, trong thành phần của bạn:

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

Các hình thức trang có thêm một số thông tin về ngModel có liên quan ở đây:

Đây ngModelChangekhông phải là một <input>sự kiện yếu tố. Nó thực sự là một tài sản sự kiện của NgModelchỉ thị. Khi Angular nhìn thấy một mục tiêu ràng buộc trong biểu mẫu [(x)], nó hy vọng lệnh xsẽ có thuộc tính xđầu vào và thuộc xChangetính đầu ra.

Sự kỳ lạ khác là biểu thức mẫu , model.name = $event. Chúng ta thường thấy một $eventđối tượng đến từ một sự kiện DOM. Thuộc tính ngModelChange không tạo ra sự kiện DOM; đó là một thuộc tính Angular EventEmittertrả về giá trị hộp đầu vào khi nó kích hoạt ..

Chúng tôi hầu như luôn luôn thích [(ngModel)]. Chúng tôi có thể phân chia ràng buộc nếu chúng tôi phải làm một điều gì đó đặc biệt trong việc xử lý sự kiện như gỡ lỗi hoặc điều tiết các nét chính.

Trong trường hợp của bạn, tôi cho rằng bạn muốn làm một cái gì đó đặc biệt.

Cách 2

Xác định một biến mẫu cục bộ và đặt nó thành ngForm.
Sử dụng ngControl trên các yếu tố đầu vào.
Nhận tham chiếu đến chỉ thị NgForm của biểu mẫu bằng cách sử dụng @ViewChild, sau đó đăng ký Nhóm điều khiển của NgForm để biết các thay đổi:

<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

Để biết thêm thông tin về Phương pháp 2, hãy xem video của Savkin .

Xem thêm câu trả lời của @ Thierry để biết thêm thông tin về những gì bạn có thể làm với valueChangeskhả năng quan sát được (chẳng hạn như thảo luận / chờ đợi một chút trước khi xử lý các thay đổi).


61

Để hoàn thành thêm một chút câu trả lời tuyệt vời trước đó, bạn cần lưu ý rằng các hình thức đòn bẩy quan sát để phát hiện và xử lý các thay đổi giá trị. Đó là một cái gì đó thực sự quan trọng và mạnh mẽ. Cả Mark và dfsq đều mô tả khía cạnh này trong câu trả lời của họ.

Các đài quan sát cho phép không chỉ sử dụng subscribephương thức (một cái gì đó tương tự như thenphương pháp hứa hẹn trong Angular 1). Bạn có thể đi xa hơn nếu cần để thực hiện một số chuỗi xử lý dữ liệu được cập nhật trong các biểu mẫu.

Ý tôi là bạn có thể chỉ định ở cấp độ này thời gian gỡ lỗi với debounceTimephương thức. Điều này cho phép bạn đợi một khoảng thời gian trước khi xử lý thay đổi và xử lý chính xác một số đầu vào:

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

Bạn cũng có thể trực tiếp cắm quá trình xử lý mà bạn muốn kích hoạt (ví dụ như một số không đồng bộ) khi các giá trị được cập nhật. Ví dụ: nếu bạn muốn xử lý một giá trị văn bản để lọc danh sách dựa trên yêu cầu AJAX, bạn có thể tận dụng switchMapphương thức:

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

Bạn thậm chí còn đi xa hơn bằng cách liên kết trực tiếp có thể quan sát được với một thuộc tính của thành phần của bạn:

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

và hiển thị nó bằng asyncđường ống:

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

Chỉ cần nói rằng bạn cần nghĩ cách xử lý các biểu mẫu khác nhau trong Angular2 (một cách mạnh mẽ hơn nhiều ;-)).

Hy vọng nó sẽ giúp bạn, Thierry


Thuộc tính 'valueChanges' không tồn tại trên loại 'chuỗi'
Bộ công cụ

Tôi đã bị kẹt, trong tệp TS, nên đặt phương thức thay đổi mẫu kiểm tra ở đâu? Nếu chúng tôi lưu trữ nó trong ngAfterViewInit () {}, có vẻ như this.form.valueChanges luôn gọi nếu chúng tôi cần triển khai một số chuỗi xử lý cho dữ liệu được cập nhật trong các biểu mẫu.
Tài Nguyễn

1

Mở rộng về các đề xuất của Mark ...

Phương pháp 3

Thực hiện phát hiện thay đổi "sâu" trên mô hình. Những ưu điểm chủ yếu liên quan đến việc tránh kết hợp các khía cạnh giao diện người dùng vào thành phần; điều này cũng nắm bắt những thay đổi theo chương trình được thực hiện cho mô hình. Điều đó nói rằng, nó sẽ đòi hỏi thêm công việc để thực hiện những việc như thảo luận theo đề xuất của Thierry, và điều này cũng sẽ bắt được chính bạn những thay đổi theo chương trình , vì vậy hãy thận trọng.

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

Thử trong Plunker


1

Đối với 5+phiên bản góc cạnh . Đưa phiên bản giúp góc cạnh tạo ra nhiều thay đổi.

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

Tôi đã nghĩ về việc sử dụng phương thức (ngModelChange), sau đó nghĩ về phương thức FormBuilder và cuối cùng giải quyết một biến thể của Phương thức 3. Điều này giúp trang trí mẫu với các thuộc tính bổ sung và tự động chọn các thay đổi cho mô hình - giảm khả năng quên một cái gì đó với Phương pháp 1 hoặc 2.

Đơn giản hóa phương pháp 3 một chút ...

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

Bạn có thể thêm thời gian chờ để chỉ gọi doS Something () sau x số mili giây để mô phỏng gỡ lỗi.

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));
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.