Як слідкувати за зміною форми у 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. Невже існує метод, за допомогою якого можна виконати те саме?

Ось кутовий приклад на Plunker .


замість того, щоб переглядати деякі дані з вашого контролера, я думаю, ви повинні запустити подію (наприклад, стару зміну ng) зі своєї форми.
Деблатон Жан-Філіп

btw, причина для видалення сфери полягає в тому, щоб позбутися цих спостерігачів. Я не думаю, що годинник ховається десь у кутовій 2
Деблатон Жан-Філіп

4
Якщо я вас правильно зрозумів, ви пропонуєте мені додати (ngModelChange)="onModelChange($event)"атрибут до кожного вводу форми, щоб досягти цього?
тамблер

1
Чи сам екземпляр форми не випромінює якусь подію зміни? Якщо так, то як ви отримуєте доступ до нього?
тамблер

Якби я був впевнений, що робити, я відповів би замість коментарів. Я ще не використовував кутовий 2.0, але з того, що я прочитав, годинник повністю зник, щоб мати основу подій (замість глибоких годин, що виконуються на кожному дайджесті)
Деблатон Жан-Філіп

Відповіді:


189

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


2
Це дуже близько до позначки. Для підтвердження - чи ти мені кажеш, що не можна підписатися на valueChangesемітер події форми, якщо ця форма визначена виключно в шаблоні? Іншими словами - в конструкторі компонента не можна отримати посилання на форму, яка була визначена виключно в шаблоні цього компонента, а не за допомогою FormBuilder?
тамблер

це правильна відповідь. Якщо у вас немає конструктора форм, ви робите форми, керовані шаблонами. Ймовірно, є спосіб все-таки ввести форму, але якщо ви хочете спостерігати 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)
}

На сторінці " Форми " є додаткова інформація про ngModel, яка є актуальною тут:

ngModelChangeЧи не є <input>подією елемента. Це фактично властивість події NgModelдирективи. Коли Angular бачить обов'язкову ціль у формі [(x)], він очікує, що xдиректива матиме xвластивість введення та xChangeвластивість виводу.

Інша дивина цього вираз шаблону, model.name = $event. Ми звикли бачити $eventоб’єкт, що надходить із події DOM. Властивість ngModelChange не створює події DOM; це EventEmitterвластивість Angular, яка повертає значення поля введення під час його запуску ..

Ми майже завжди віддаємо перевагу [(ngModel)]. Ми можемо розколоти обов'язковість, якби нам довелося зробити щось особливе в подіях, таких як дебютація або придушення ключових ударів.

У вашому випадку, я думаю, ви хочете зробити щось особливе.

Спосіб 2

Визначте локальну змінну шаблону та встановіть її ngForm.
Використовуйте ngControl для вхідних елементів.
Отримайте посилання на директиву NgForm форми за допомогою @ViewChild, а потім підпишіться на Controlgroug 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 для отримання додаткової інформації про те, що ви можете зробити із 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' не існує для типу 'string'
Інструментарій

Я застряг у файлі 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 кількість мілісекунд, щоб імітувати дебютацію.

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.