(change) vs (ngModelChange) в Angular: что выбрать в 2024?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему эта тема взрывает Stack Overflow?

Если вы когда-нибудь писали на Angular и сталкивались с обработкой изменений в <input>, вы наверняка видели два похожих события: (change) и (ngModelChange). На первый взгляд — одно и то же. На практике — дьявол кроется в деталях. Вопрос "(change) vs (ngModelChange) in Angular" собирает тысячи просмотров на Stack Overflow, и неспроста: неправильный выбор приводит к багам, лишним запросам к серверу и бессонным ночам.

В этой статье на PythonLib мы раз и навсегда разберемся:

  • Чем отличаются (change) и (ngModelChange) в Angular?
  • Когда использовать каждый из них?
  • Как не наступить на типичные грабли?

Поехали! 🚀

Коротко: что такое (change) и (ngModelChange)?

(change) — это стандартное DOM-событие, которое срабатывает, когда элемент теряет фокус (blur) и его значение изменилось. Angular просто оборачивает нативный change.

(ngModelChange) — это событие директивы ngModel. Оно срабатывает каждый раз, когда модель меняется — буквально при каждом нажатии клавиши (если не используется ngModelOptions).

Важно: ngModelChange нельзя использовать без [(ngModel)]. Это часть двустороннего связывания.

Вот минимальный пример на TypeScript, чтобы увидеть разницу:

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

@Component({
  selector: 'app-root',
  template: `
    <input
      [ngModel]="name"
      (ngModelChange)="onNgModelChange($event)"
      (change)="onChange($event)"
      placeholder="Введите имя">
    <p>Значение: {{ name }}</p>
  `
})
export class AppComponent {
  name = '';

  onNgModelChange(value: string) {
    console.log('ngModelChange:', value);
  }

  onChange(event: Event) {
    console.log('change:', (event.target as HTMLInputElement).value);
  }
}

Попробуйте ввести "Angular" — и вы увидите: ngModelChange сработает 7 раз (по букве), а change — только один раз, когда вы кликнете вне поля.

Главные отличия: таблица сравнения

Чтобы было наглядно, вот таблица ключевых различий (change) vs (ngModelChange):

Критерий (change) (ngModelChange)
Тип события Нативное DOM-событие Событие Angular (EventEmitter)
Когда срабатывает При потере фокуса + изменение значения При каждом изменении модели (каждый ввод)
Значение в $event Объект Event (нужно извлекать value) Новое значение модели (строка, число и т.д.)
Зависимость от ngModel Не требуется (можно на любом input) Требует [(ngModel)] или [ngModel]
Производительность Высокая (срабатывает редко) Может быть частой (на каждый keypress)
Использование с формами Реактивные формы, template-driven Только template-driven формы

Когда использовать (change)?

(change) — ваш выбор, если:

  • Вам нужно среагировать только когда пользователь закончил ввод (например, при валидации после потери фокуса).
  • Вы работаете с нативными элементами без ngModel.
  • Вы используете реактивные формы (formControl).
  • Вы хотите избежать лишних вызовов (например, поисковый запрос к API — лучше делать по blur).

Пример для реактивных форм:

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

@Component({
  selector: 'app-reactive',
  template: `
    <input [formControl]="searchControl" (change)="onSearchChange($event)">
  `
})
export class ReactiveComponent {
  searchControl = new FormControl('');

  onSearchChange(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    console.log('Поиск по:', value); // сработает только при blur
    // здесь можно делать запрос к API
  }
}

Когда использовать (ngModelChange)?

(ngModelChange) незаменим, когда:

  • Нужно реагировать на каждое изменение в реальном времени (autocomplete, live preview, счетчик символов).
  • Вы используете template-driven формы и двустороннее связывание [(ngModel)].
  • Вам нужно разделить запись и чтение модели (например, форматировать ввод на лету).

Пример live preview:

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

@Component({
  selector: 'app-preview',
  template: `
    <input
      [(ngModel)]="text"
      (ngModelChange)="onTextChange($event)"
      placeholder="Введите текст">
    <div>Предпросмотр: {{ previewText }}</div>
  `
})
export class PreviewComponent {
  text = '';
  previewText = '';

  onTextChange(value: string) {
    // Мгновенно обновляем предпросмотр
    this.previewText = value.toUpperCase();
  }
}
Обратите внимание: мы используем [(ngModel)] для двустороннего связывания, но перехватываем изменения через (ngModelChange). Это позволяет добавить логику между вводом и обновлением модели.

Подводный камень: порядок выполнения

Многие разработчики удивляются, когда видят, что (ngModelChange) срабатывает до обновления модели в шаблоне. Это особенность Angular: сначала выполняется событие, потом обновляется привязка.

Пример бага:

// ❌ НЕПРАВИЛЬНО
<input [(ngModel)]="name" (ngModelChange)="logName()">

logName() {
  console.log(this.name); // Старое значение!                

Блоги

Рекомендаций по книгам