Как следить за изменениями формы в 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. Конечно, есть метод, с помощью которого можно сделать то же самое?

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


вместо просмотра некоторых данных с вашего контроллера, я думаю, что вы должны запустить событие (например, старое ng-change) из вашей формы.
Deblaton Жан-Филипп

Кстати, причина удаления прицела состоит в том, чтобы избавиться от этих наблюдателей. Я не думаю, что где-то под углом
находятся

4
Если я вас правильно понимаю, вы предлагаете добавить (ngModelChange)="onModelChange($event)"атрибут для каждого ввода формы, чтобы выполнить это?
Тамблер

1
Разве сам экземпляр формы не генерирует какое-либо событие изменения? Если так, как вы получаете к нему доступ?
Tambler

Если бы я был уверен, что делать, я бы ответил вместо того, чтобы комментировать. Я еще не использовал angular 2.0, но из того, что я прочитал, часы полностью исчезли, чтобы иметь основанную на событиях структуру (вместо глубоких часов, выполняемых при каждом дайджесте)
Deblaton Jean-Philippe

Ответы:


189

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
Это очень близко к отметке. Для того, чтобы подтвердить - ты говоришь мне , что это не возможно , чтобы подписаться на форму в valueChangesэмиттер событий , если эта форма определяются исключительно в шаблоне? Другими словами - в конструкторе компонента невозможно получить ссылку на форму, которая была определена исключительно в шаблоне этого компонента, а не с помощью FormBuilder?
Tambler

это правильный ответ. Если у вас нет конструктора форм, вы создаете формы на основе шаблонов. вероятно, есть способ по-прежнему вводить форму, но если вы хотите, чтобы Observable form.valueChanges, вы должны окончательно использовать formBuilder и отказаться от ng-модели
Angular University

2
@tambler, вы можете получить ссылку на NgForm, используя @ViewChild(). Смотрите мой обновленный ответ.
Марк Райкок

1
Вам не нужно отписаться на уничтожить?
Базинга

1
@ Galvan Я не думаю, что может быть утечка. Форма является частью компонента, и она будет должным образом расположена при уничтожении со всеми ее полями и прослушивателями событий.
dfsq

107

Если вы используете 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свойство input и свойство xChangeoutput.

Другая странность - это выражение шаблона model.name = $event. Мы привыкли видеть $eventобъект, поступающий из события DOM. Свойство ngModelChange не создает событие DOM; это EventEmitterсвойство Angular, которое возвращает значение поля ввода при запуске ..

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

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

Способ 2

Определите локальную переменную шаблона и установите для нее значение ngForm.
Используйте ngControl для элементов ввода.
Получите ссылку на директиву NgForm формы, используя @ViewChild, затем подпишитесь на ControlGroup NgForm для изменений:

<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 см . Видео Савкина .

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


61

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

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

Надеюсь, это поможет тебе, Тьерри


Свойство 'valueChanges' не существует для типа 'строка'
Инструментарий

Я застрял в файле TS, где метод размещения формы проверки должен быть размещен? Если мы храним его в ngAfterViewInit () {}, кажется, что this.form.valueChanges всегда вызывается, если нам нужно было реализовать некоторые цепочки обработки для обновленных данных в формах.
Tài Nguyễn

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

Попробуй в Plunker


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

Вы можете добавить тайм-аут, чтобы вызывать 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));
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.