Привязка событий и двусторонняя привязка

События — это способ интерфейса говорить с вашим кодом: клик, ввод, наведение превращаются в вызовы методов компонента.

«Property binding — это монолог компонента. Event binding — диалог с пользователем».

Привязка свойства гонит данные в DOM. Чтобы услышать пользователя, нужна привязка события — круглые скобки. Любое DOM-событие (click, input, submit) можно поймать и вызвать метод класса:

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Счёт: {{ count }}</p>
    <button (click)="increment()">+1</button>
    <input (input)="onType($event)" placeholder="Введите имя" />
    <p>Вы ввели: {{ typed }}</p>
  `,
})
export class CounterComponent {
  count = 0;
  typed = '';
  increment() { this.count++; }
  onType(event: Event) {
    this.typed = (event.target as HTMLInputElement).value;
  }
}

Переменная $event — это родное DOM-событие. Часто нужно не само событие, а связать поле ввода с переменной в обе стороны: изменилось поле — обновилась переменная, и наоборот. Это двусторонняя привязка, синтаксис «банан в коробке» [()]:

import { Component, model } from '@angular/core';

@Component({
  selector: 'app-search',
  standalone: true,
  template: `
    <input [(value)]="query" />
    <p>Поиск: {{ query() }}</p>
  `,
})
export class SearchComponent {
  query = model('');   // двусторонний сигнал
}

Запись [(x)] — это сахар: она разворачивается в [x] (свойство вниз) плюс (xChange) (событие вверх). model() создаёт сигнал, который умеет и читаться, и записываться извне.

Как работает под капотом

«Банан в коробке» [(ngModel)] или [(value)] — не магия, а два связанных биндинга. Angular подставляет привязку свойства и привязку события с суффиксом Change.

   [(query)]   разворачивается в:
        |
        +--->  [query]="query()"        (значение -> input)
        +--->  (queryChange)="query.set($event)"  (input -> значение)

   так замыкается двусторонний цикл

Запускаемая врезка: двусторонняя привязка на JS

Соберём мини-«банан в коробке» вручную. «Попробуй сам ▶».

// модель с двусторонней связью: get/set + подписка
function createModel(initial) {
  let value = initial;
  const subs = [];
  return {
    get: () => value,
    set: (v) => { value = v; subs.forEach(fn => fn(v)); },
    onChange: (fn) => subs.push(fn),
  };
}

const log = [];
const query = createModel('');
query.onChange(v => log.push('view показывает: ' + v));

query.set('ан');     // пользователь печатает -> view обновился
query.set('анг');
console.log('текущее:', query.get()); // текущее: анг
console.log(log);
// ["view показывает: ан", "view показывает: анг"]

Частые ошибки

  • Скобки наоборот. ([x]) вместо [(x)] — не сработает; коробка снаружи, банан внутри.
  • Тяжёлая логика в обработчике клика. Лучше вызвать метод, а не писать выражение прямо в шаблоне.
  • $event.target без приведения типа. TypeScript не знает, что это input; нужно as HTMLInputElement.

Best practices

  • Выносите логику обработчиков в методы класса — шаблон остаётся чистым.
  • Для форм с [(ngModel)] не забудьте импортировать FormsModule.
  • Используйте model() для собственных двусторонних компонентов — это современный путь.

Итоги. (событие) ловит действия пользователя, $event даёт данные события, [(x)] связывает значение в обе стороны. Дальше — управление видимостью и списками через новый control flow.

Закрепляем

Если property binding — это монолог компонента в сторону DOM, то event binding превращает связь в диалог. Круглые скобки (событие) подключают обработчик к любому DOM-событию, а двусторонняя привязка [(x)] склеивает оба направления в один удобный синтаксис. Помните мнемонику «банан в коробке»: квадратные скобки снаружи (коробка), круглые внутри (банан) — [()]. Перепутанные скобки ([]) не сработают, и это классическая ошибка новичка.

Под капотом двусторонней привязки нет никакой магии — это просто два обычных биндинга, объединённых конвенцией имён. Когда вы пишете [(value)], Angular ищет вход value и выход valueChange. Именно поэтому model() создаёт сигнал, готовый к двусторонней работе: он автоматически предоставляет и чтение, и парный выход с суффиксом Change. Понимание этого разворачивания снимает ощущение волшебства и помогает строить собственные двусторонние компоненты осознанно.

СинтаксисНаправление
(click)="fn()"Событие DOM в код
$eventОбъект события (target.value)
[(value)]="x"Двусторонняя привязка
model()Сигнал для [(...)]
Проверьте себя
1. Что означает синтаксис [(ngModel)] («банан в коробке»)?
AТолько вывод данных
BДвустороннюю привязку: значение и вниз в DOM, и вверх обратно в переменную
CИмпорт модуля
DЦикл по массиву
2. Что содержит переменная $event в обработчике (input)="onType($event)"?
AТекущий компонент
BРодное DOM-событие, из которого можно достать event.target.value
CИмя метода
DСигнал