Как следить за изменениями формы в Angular - программирование

Как следить за изменениями формы в 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);

}

Вот угловой пример на JSFiddle.

У меня проблемы с выяснением, как сделать то же самое в Angular. Очевидно, у нас больше нет $scope, $ rootScope. Конечно, есть метод, с помощью которого можно сделать то же самое?

Вот угловой пример на Плункер.

4b9b3361

Ответ 1

UPD.Ответ и демо обновлены, чтобы соответствовать последнему Angular.


Вы можете подписаться на все изменения формы в связи с тем, что FormGroup, представляющая форму, предоставляет свойство valueChanges которое является экземпляром 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, см. ответ @dfsq.

Если вы не используете FormBuilder, есть два способа уведомления об изменениях.

Метод 1

Как обсуждалось в комментариях к вопросу, используйте привязку событий к каждому элементу ввода. Добавьте к своему шаблону:

<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. Мы привыкли видеть объект $event, исходящий из события DOM. Свойство ngModelChange не создает событие DOM; это свойство Angular EventEmitter, которое возвращает значение поля ввода при его возникновении.

Мы почти всегда предпочитаем [(ngModel)]. Мы могли бы разделить привязку, если бы нам пришлось делать что-то особенное в обработке событий, например, debounce или дросселировать нажатия клавиш.

В вашем случае, я полагаю, вы хотите сделать что-то особенное.

Метод 2

Определите локальную переменную шаблона и установите ее на ngForm.
Используйте ngControl для элементов ввода.
Получить ссылку на форму NgForm директиву с помощью @ViewChild, а затем подписаться на 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.

См. также @Thierry answer для получения дополнительной информации о том, что вы можете сделать с наблюдаемым valueChanges (например, debouncing/waiting a bit перед обработкой изменений).

Ответ 3

Чтобы выполнить несколько более старых замечательных ответов, вам нужно знать, что формы используют наблюдаемые для обнаружения и обработки изменений значений. Это что-то действительно важное и сильное. И Марк, и dfsq описали этот аспект в своих ответах.

Наблюдения позволяют не только использовать метод subscribe (что-то похожее на метод then promises в Angular 1). Вы можете пойти дальше, если необходимо, для реализации некоторых цепей обработки для обновленных данных в формах.

Я имею в виду, что вы можете указать на этом уровне время отладки с помощью метода 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 (гораздо более мощный способ; -)).

Надеюсь, это поможет вам, Thierry

Ответ 4

Расширение предложений Mark...

Метод 3

Внедрить "глубокое" обнаружение изменений в модели. Преимущества в первую очередь включают в себя предотвращение включения аспектов пользовательского интерфейса в компонент; это также улавливает программные изменения, внесенные в модель. Тем не менее, это потребует дополнительной работы для реализации таких вещей, как debouncing, как предложено 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];
      }
    }
  }

Попробуйте в Plunker

Ответ 5

Я подумал об использовании метода (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));
    }
}

Вы можете добавить тайм-аут только для вызова doSomething() через x миллисекунд, чтобы имитировать debounce.

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

Ответ 6

Для угловой версии 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)
        })
}