1. Введение
Two-way data binding (двусторонняя привязка данных) — это механизм, который позволяет автоматически синхронизировать данные между моделью компонента (TypeScript-классом) и представлением (HTML-шаблоном) в оба направления.
- Если пользователь что-то вводит в поле ввода — значение в компоненте обновляется.
- Если компонент изменяет значение переменной — оно тут же отображается в поле ввода на странице.
Всё это происходит без дополнительных усилий с вашей стороны. Angular сам следит за изменениями и поддерживает синхронизацию.
Двусторонняя привязка — это как умная рация: если один абонент говорит, другой сразу слышит; если второй что-то отвечает — первый тоже мгновенно в курсе. Никто не остаётся в неведении!
Синтаксис и как это работает
В Angular двусторонняя привязка реализуется через специальную конструкцию — banana in a box (да, так это называют даже в официальной документации, и да, это выглядит забавно):
<input [(ngModel)]="userName">
- Квадратные скобки [ ] — привязка значения (property binding)
- Круглые скобки ( ) — привязка события (event binding)
- Вместе [( )] — двусторонняя привязка!
ngModel — это директива, которая реализует магию синхронизации между переменной компонента и элементом формы.
Пример
В компоненте:
export class AppComponent {
userName: string = 'Иван';
}
В шаблоне:
<input [(ngModel)]="userName">
<p>Привет, {{ userName }}!</p>
Что происходит:
— При загрузке страницы в поле будет "Иван".
— Если пользователь изменит текст в поле, значение userName в компоненте тут же обновится.
— Если вы в коде компонента присвоите новое значение userName — оно сразу появится в поле ввода.
Как это работает под капотом?
За кулисами Angular делает две вещи:
- [Property binding] — передаёт значение переменной в атрибут value поля ввода.
- (Event binding) — слушает событие ввода (input), и при каждом изменении обновляет переменную компонента.
Примерно это эквивалентно следующему коду (если бы вы делали это вручную):
<input [value]="userName" (input)="userName = $event.target.value">
Но с [(ngModel)] всё делается автоматически и лаконично.
2. Практика: Двусторонняя привязка в действии
Давайте добавим в наше учебное приложение форму, где пользователь может ввести своё имя, а мы будем приветствовать его в реальном времени.
Шаг 1. Подключаем FormsModule
Для работы ngModel нужно подключить модуль форм. В Angular 15+ для Standalone-компонентов это делается так:
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-hello',
standalone: true,
imports: [FormsModule],
template: `
<input [(ngModel)]="userName" placeholder="Введите имя">
<p>Привет, {{ userName || 'гость' }}!</p>
`
})
export class HelloComponent {
userName = '';
}
Важно! Если забыть импортировать FormsModule, Angular будет ругаться на ngModel (и правильно!).
Шаг 2. Динамика
Попробуйте ввести в поле "Алиса". Сразу же под полем появится "Привет, Алиса!". Теперь измените значение переменной в коде (например, по кнопке):
<button (click)="userName = 'Гость'">Сбросить имя</button>
Всё работает мгновенно и в обе стороны.
3. [(ngModel)] — не только для input
ngModel работает не только с <input>, но и с <select>, <textarea>, чекбоксами и радиокнопками.
Пример: Select
<select [(ngModel)]="selectedColor">
<option value="red">Красный</option>
<option value="green">Зелёный</option>
<option value="blue">Синий</option>
</select>
<p>Выбран цвет: {{ selectedColor }}</p>
Пример: Checkbox
<input type="checkbox" [(ngModel)]="isChecked">
<p>Чекбокс: {{ isChecked ? 'отмечен' : 'не отмечен' }}</p>
4. [(ngModel)] и валидация: бонус!
Через ngModel можно легко валидировать поля формы. Angular автоматически добавляет классы ошибок и состояния (например, ng-valid, ng-invalid, ng-touched).
Пример: Валидация email
<input [(ngModel)]="email" required email #emailInput="ngModel">
<div *ngIf="emailInput.invalid && emailInput.touched">
Некорректный email!
</div>
Для этого потребуется #emailInput="ngModel" на input, но это уже тема для лекций по формам.
5. Отличие [(ngModel)] в Angular от работы с формами в React
Angular: магия и лаконичность
В Angular вы просто пишете [(ngModel)]="someVar" — и всё работает. Это очень удобно для простых форм и прототипов.
React: явное управление состоянием
В React двусторонней привязки "из коробки" нет. Всё делается вручную через state и обработчики событий.
Пример: input в React
function Hello() {
const [userName, setUserName] = React.useState('');
return (
<>
<input
value={userName}
onChange={e => setUserName(e.target.value)}
placeholder="Введите имя"
/>
<p>Привет, {userName || 'гость'}!</p>
</>
);
}
- Вы явно указываете value поля.
- Вы явно пишете функцию onChange, которая обновляет состояние.
Почему так?
- React придерживается принципа "однонаправленного потока данных" (one-way data flow). Данные идут от компонента к шаблону, а изменения пользователя — только через события.
- Это делает поведение приложения более предсказуемым и прозрачным (особенно в сложных интерфейсах).
Angular: когда использовать [(ngModel)], а когда нет?
- Для простых форм, демо, небольших приложений — отлично.
- В больших формах и сложных приложениях рекомендуют использовать Reactive Forms (о них позже), где управление состоянием и валидацией ещё более явное и гибкое.
6. Типичные ошибки при работе с [(ngModel)]
Ошибка №1: забыли импортировать FormsModule.
Angular выдаст ошибку вида: Can't bind to 'ngModel' since it isn't a known property of 'input'. Проверьте, что FormsModule есть в imports компонента или модуля.
Ошибка №2: пытались использовать [(ngModel)] с неформовым элементом.
ngModel работает только с элементами формы (input, select, textarea, checkbox, radio). На <div> или <span> не сработает.
Ошибка №3: конфликт двух форматов привязки.
Не используйте одновременно [value] и [(ngModel)] на одном элементе — Angular не сможет решить, кто главный.
Ошибка №4: неверно обработан тип значения.
Например, для чекбокса переменная должна быть boolean, для select — строка или число. Следите за типами!
Ошибка №5: попытка использовать [(ngModel)] без FormsModule в Standalone-компоненте.
Да-да, повторяем ещё раз: FormsModule обязателен, даже если у вас Standalone-компоненты.
Ошибка №6: попытка использовать [(ngModel)] в Reactive Forms.
В реактивных формах ngModel не нужен, используйте FormControl и formControlName.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ