Angular
Сигнальные input() и output()
Карточка товара должна показывать переданный ей продукт и сообщать наверх о клике Купить. Родитель передаёт данные вниз, ребёнок шлёт события вверх - это базовый диалог компонентов. Годами его писали через декораторы @Input и @Output, но входное значение было обычным полем, и чтобы среагировать на его изменение, приходилось городить ngOnChanges. Сигнальные input() и output() сделали вход реактивным значением: он сам уведомляет computed и effect, когда родитель прислал новое.
- Карточки товаров: родитель-список передаёт каждый товар в дочернюю карточку через input
- Кнопки действий: дочерний компонент шлёт наверх событие через output по клику пользователя
- Поля форм: кастомный компонент-поле через model двусторонне связывается со значением родителя
- Таблицы: строка-компонент получает данные через input и сообщает о выборе через output
- Дизайн-системы: переиспользуемые компоненты принимают конфигурацию через input.required, гарантируя обязательность
Предварительные знания
- Урок ng-03: компоненты, их вложение и selector
- Понимание привязки свойств и событий из урока ng-04
- Идея сигнала из урока ng-01: значение, читаемое как функция
- Базовый TypeScript: дженерики и типы на уровне идеи
input() и input.required()
Вход компонента объявляется функцией input(). Она возвращает сигнал только для чтения: значение читается как функция, и когда родитель присылает новое, сигнал уведомляет всех, кто от него зависит. В декоратор @Component добавлять ничего не нужно - функция сама регистрирует вход.
Функция input(значение) задаёт значение по умолчанию, если родитель ничего не передал. input.required() делает вход обязательным: если родитель забудет его указать, компилятор шаблона выдаст ошибку. Это убирает целый класс багов с undefined-значениями в обязательных данных.
Поскольку вход - сигнал, на нём напрямую строится производное состояние через computed. Раньше для реакции на изменение входа писали хук жизненного цикла ngOnChanges, теперь достаточно computed, который сам пересчитается при новом значении.
Вход-сигнал доступен только для чтения внутри компонента: значение задаёт родитель, а ребёнок его лишь читает. Это сохраняет односторонний поток данных вниз и делает источник правды очевидным - им владеет родитель.
Что возвращает функция input() при объявлении входа компонента?
output() и события наверх
Если вход передаёт данные вниз, то выход шлёт события вверх. Функция output() создаёт эмиттер: дочерний компонент вызывает на нём emit с полезной нагрузкой, а родитель слушает это как обычное событие через круглые скобки. Так ребёнок сообщает родителю о клике, выборе или завершении действия, не зная, кто его слушает.
Переменная event в обработчике несёт значение, переданное в emit. Тип события задаётся дженериком output<Product>, поэтому родитель получает строго типизированную нагрузку. Симметрия с шаблоном сохраняется: вход через [prop], выход через (event), как у обычных DOM-привязок.
Функция output() пришла на смену декоратору @Output с EventEmitter. Под капотом она проще и не привязана к RxJS, хотя при необходимости выход можно создать и из observable через outputFromObservable.
Как дочерний компонент сообщает родителю о событии через output()?
model() для двусторонней связи
Иногда компоненту нужен и вход, и обратное уведомление об изменении одного значения - классический случай кастомного поля формы. Функция model() создаёт двусторонний вход-сигнал: внутри компонента он изменяемый через set, а снаружи родитель связывает его синтаксисом банан-в-коробке, как у ngModel.
Отличие model от input в том, что model изменяемый внутри компонента и при изменении автоматически уведомляет родителя. Это удобно именно для обёрток над элементами ввода, где значение по природе двунаправленное. Для данных, которые идут только сверху вниз, по-прежнему используют обычный input.
| Функция | Направление | Заменила |
|---|---|---|
| input() | Сверху вниз, только чтение в ребёнке | @Input |
| input.required() | Сверху вниз, обязателен | @Input({ required: true }) |
| output() | Снизу вверх, событие | @Output + EventEmitter |
| model() | В обе стороны, банан-в-коробке | @Input + @Output пара |
Чем model() отличается от обычного input()?
Связь с другими темами
Входы и выходы - канал общения между компонентами:
- Связь компонентов и проекция — На input и output строится взаимодействие родителя и ребёнка
- Шаблоны и привязки — Родитель передаёт input через [prop] и слушает output через (event)
- Зачем Angular — Сигнальные входы - часть сигнальной модели всего фреймворка
Итог
- input() объявляет вход компонента как сигнал, который читается как функция и реагирует на новые значения от родителя
- input.required() делает вход обязательным: без него компонент не скомпилируется
- output() создаёт событие, которое дочерний компонент эмитит вверх через emit, а родитель слушает через привязку события
- model() даёт двусторонний вход-сигнал для синтаксиса банан-в-коробке, как у ngModel
- Сигнальные функции заменили декораторы @Input и @Output, стали production-ready с v19 и интегрированы с computed и effect
Связанные уроки
- ng-03-components — Входы и выходы соединяют родительский и дочерний компоненты, разобранные ранее
- ng-01-why-angular — Сигнальные входы развивают сигнальную модель Angular из первого урока
- ng-07-component-communication — На входах и выходах строится вся связь компонентов и проекция контента