Введение: почему эта тема взрывает 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); // Старое значение!