Two-way Data Binding: [(ngModel)], отличие от React

Модуль 4: Node.js, Next.js и Angular
13 уровень , 8 лекция
Открыта

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 делает две вещи:

  1. [Property binding] — передаёт значение переменной в атрибут value поля ввода.
  2. (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.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ