Avatar ·

(change) vs (ngModelChange) в Angular: в чем разница и когда что использовать

Всем привет! Я новичок в Angular и TypeScript, пытаюсь разобраться с формами. У меня есть простой инпут с двусторонним связыванием через [(ngModel)], и мне нужно отловить момент, когда пользователь меняет значение. Я вижу в документации два похожих события: (change) и (ngModelChange). На первый взгляд они делают одно и то же, но я чувствую, что это не так. Вот пример моего кода: ```typescript // component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-test', template: `

Текущее значение: {{ name }}

` }) export class TestComponent { name: string = ''; onChange(event: any) { console.log('change event:', event.target.value); } onNgModelChange(value: string) { console.log('ngModelChange:', value); } } ``` Я заметил, что (change) передает целое событие (event), а (ngModelChange) — просто новое значение. Но это не единственное отличие? Мне кажется, что они срабатывают в разное время или при разных условиях. Подскажите, пожалуйста: 1. В чем фундаментальная разница между (change) и (ngModelChange)? 2. Когда нужно использовать одно, а когда другое? 3. Есть ли какие-то подводные камни? Буду благодарен за подробное объяснение с примерами! <<>>

Основное отличие: стандартное DOM-событие против события Angular

(change) — это стандартное нативное DOM-событие HTML-элемента <input>. Оно срабатывает только когда элемент теряет фокус (blur) И значение изменилось. Другими словами, пользователь может печатать сколько угодно, но событие произойдет только после того, как он кликнет мышкой за пределами инпута или нажмет Tab.

(ngModelChange) — это событие, которое генерирует директива ngModel из Angular. Оно срабатывает при каждом изменении значения в модели, то есть практически мгновенно после каждого нажатия клавиши (если не используется debounce). Событие передает не DOM-объект, а непосредственно новое значение.

Детальный разбор на примерах

// component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-demo',
  template: `
    <h3>Демонстрация разницы</h3>
    <input 
      [(ngModel)]="value" 
      (change)="onChange($event)" 
      (ngModelChange)="onModelChange($event)"
      placeholder="Введите текст и нажмите Enter/Tab"
    />
    <p>Текущее значение: {{ value }}</p>
    <div>
      <h4>Лог событий:</h4>
      <ul>
        <li *ngFor="let log of logs">{{ log }}</li>
      </ul>
    </div>
  `
})
export class DemoComponent {
  value: string = '';
  logs: string[] = [];

  onChange(event: Event) {
    const target = event.target as HTMLInputElement;
    this.logs.unshift(`[change] Сработало! Значение: "${target.value}"`);
  }

  onModelChange(newValue: string) {
    this.logs.unshift(`[ngModelChange] Сработало! Значение: "${newValue}"`);
  }
}

Поведение при разных сценариях

  1. Пользователь печатает "Hello":
    • ngModelChange сработает 5 раз (после каждой буквы)
    • change не сработает ни разу, пока инпут не потеряет фокус
  2. Пользователь напечатал "Hello" и нажал Tab:
    • Сначала сработает ngModelChange с "Hello"
    • Затем сработает change с "Hello"
  3. Пользователь кликнул в инпут, ничего не ввел и кликнул вне:
    • ngModelChange не сработает (значение не изменилось)
    • change не сработает (значение не изменилось)

Когда что использовать?

Используйте (ngModelChange), когда:

  • Нужно реагировать на каждое изменение в реальном времени (поиск, автодополнение, валидация "на лету")
  • Вы работаете с реактивными формами и хотите подписываться на изменения значения
  • Вам нужно только значение, а не DOM-событие
// Пример: поиск при каждом вводе
<input [(ngModel)]="searchTerm" (ngModelChange)="onSearch($event)" />

onSearch(term: string) {
  this.searchService.search(term).subscribe(results => {
    this.searchResults = results;
  });
}

Используйте (change), когда:

  • Нужно среагировать только после того, как пользователь закончил ввод и ушел с поля
  • Вы хотите избежать лишних вызовов (например, при сохранении данных или отправке на сервер)
  • Вам нужен доступ к DOM-элементу (например, чтобы проверить какие-то атрибуты)
// Пример: сохранение только когда пользователь закончил редактирование
<input [(ngModel)]="user.name" (change)="saveUserName($event)" />

saveUserName(event: Event) {
  const input = event.target as HTMLInputElement;
  if (input.value.length > 0) {
    this.userService.updateName(input.value).subscribe();
  }
}

Важный нюанс с [(ngModel)]

Когда вы используете [(ngModel)] (двустороннее связывание), не пишите одновременно (ngModelChange)="handler($event)" — это приведет к двойному срабатыванию. Правильный паттерн — развернуть двустороннюю привязку:

// Вместо:
<input [(ngModel)]="value" (ngModelChange)="onChange($event)" />

// Лучше так (если нужно дополнительное действие):
<input [ngModel]="value" (ngModelChange)="onChange($event)" />

onChange(newValue: string) {
  this.value = newValue; // явно обновляем модель
  // ... делаем что-то еще
}

Или еще проще — используйте сеттер:

private _value: string = '';

get value(): string {
  return this._value;
}

set value(val: string) {
  this._value = val;
  // Здесь можно добавить дополнительную логику
  console.log('Значение изменилось:', val);
}

Итоговое резюме

  • (change) — нативное DOM-событие, срабатывает при потере фокуса
  • (ngModelChange) — событие Angular, срабатывает при каждом изменении модели
  • Выбор зависит от того, когда вам нужно реагировать: сразу или после завершения ввода
<<>>

Дополнительный взгляд: производительность и реактивные формы

Коллега выше отлично объяснил базовую разницу. Я хочу добавить про производительность и альтернативы.

Проблема производительности с (ngModelChange)

Если вы используете (ngModelChange) для вызова тяжелых операций (например, HTTP-запросов) при каждом нажатии клавиши, вы рискуете завалить сервер или повесить интерфейс. Решение — использовать debounce:

import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';

export class SearchComponent {
  private searchSubject = new Subject<string>();

  ngOnInit() {
    this.searchSubject.pipe(
      debounceTime(300), // ждем 300мс после последнего ввода
      distinctUntilChanged() // игнорируем повторяющиеся значения
    ).subscribe(term => {
      this.performSearch(term);
    });
  }

  onSearch(term: string) {
    this.searchSubject.next(term);
  }
}

Реактивные формы как альтернатива

Вместо ngModel с событиями часто удобнее использовать реактивные формы (Reactive Forms). Они дают более гибкий контроль:

import { FormControl } from '@angular/forms';

export class ReactiveDemoComponent {
  nameControl = new FormControl('');

  ngOnInit() {
    // Подписываемся на изменения значения
    this.name