Angular
Связь компонентов и проекция контента
Компонент-модалка должна показывать любое содержимое: форму, текст, картинку. Если описать всё внутри неё, она перестанет быть переиспользуемой. Нужен механизм, который позволит родителю задать содержимое снаружи, а модалке - лишь обернуть его в свою рамку с заголовком и кнопкой закрытия. Это проекция контента: компонент-обёртка резервирует слоты, а наполняет их вызывающий код. Так строятся карточки, диалоги, панели и вкладки в любой дизайн-системе.
- Модальные окна: обёртка задаёт рамку и крестик, а содержимое приходит снаружи через проекцию
- Карточки: компонент Card проецирует заголовок, тело и подвал в отдельные слоты
- Вкладки и аккордеоны: контейнер управляет переключением, а контент каждой вкладки задаёт родитель
- Кнопки дизайн-системы: проекция позволяет вставить текст и иконку внутрь стилизованной кнопки
- Layout-компоненты: шапка, боковая панель и основная область заполняются проекцией из страницы
Предварительные знания
- Урок ng-06: сигнальные input и output
- Урок ng-03: вложение компонентов и selector
- Понимание привязок свойств и событий из урока ng-04
- Идея переиспользуемого компонента-обёртки
Диалог родителя и ребёнка
Базовая связь компонентов однонаправленна по данным: родитель передаёт значения вниз через сигнальные input, ребёнок сообщает о событиях вверх через output. Родитель остаётся владельцем состояния, ребёнок лишь отображает данные и эмитит события. Это делает поток предсказуемым: чтобы понять, откуда у ребёнка данные, достаточно посмотреть на привязки в шаблоне родителя.
Родитель хранит список и метод markDone, ребёнок не знает, как именно обрабатывается событие. Такая изоляция позволяет переиспользовать TodoRow в любом контексте: важно лишь, что он принимает todo и эмитит toggle. Это контракт компонента, выраженный его входами и выходами.
Если данные надо разделить между далёкими компонентами, не связанными родством, цепочка input и output становится громоздкой. Тогда применяют общий сервис через внедрение зависимостей или сигнальный store. Но для прямой связи родитель-ребёнок входов и выходов достаточно.
Кто владеет состоянием в типичной паре родитель-ребёнок на входах и выходах?
Проекция через ng-content
Входы передают данные, но иногда родителю нужно вложить целый кусок разметки внутрь компонента-обёртки. Для этого служит ng-content: компонент-обёртка ставит этот тег как слот, и всё, что родитель вложил между открывающим и закрывающим тегами компонента, проецируется в это место. Обёртка не знает заранее, что именно туда попадёт.
В отличие от входа, который передаёт данные, проекция передаёт готовую разметку. Это разные инструменты: input хорош для значений (имя, число, объект), а ng-content - для произвольного содержимого, которое рисует сам родитель. Часто их сочетают: данные через input, оформление через проекцию.
Спроецированное содержимое остаётся в контексте родителя: его привязки и обработчики событий работают так, как будто оно лежит в шаблоне родителя, а не обёртки. Обёртка лишь определяет, где это содержимое окажется визуально.
Что делает тег ng-content в шаблоне компонента-обёртки?
Несколько слотов и ngProjectAs
Когда обёртке нужно разложить содержимое по местам - например, заголовок в шапку, текст в тело, кнопки в подвал - применяют несколько ng-content с атрибутом select. select это CSS-селектор: содержимое, подходящее под него, попадает именно в этот слот. Один ng-content без select служит слотом по умолчанию для всего остального.
Атрибут panel-title на элементе сопоставляется с select="[panel-title]" и направляет его в шапку. Содержимое без помеченных атрибутов уходит в слот по умолчанию. Иногда элемент нельзя пометить нужным селектором напрямую, например когда это компонент с фиксированным тегом. Тогда применяют ngProjectAs: он перенаправляет элемент в слот так, будто у него другой селектор.
Если содержимое не подходит ни под один select и слота по умолчанию нет, оно просто не отрисуется. При нескольких слотах стоит либо предусмотреть слот по умолчанию, либо чётко документировать ожидаемые селекторы для каждого слота.
Зачем нужен атрибут select у ng-content?
Связь с другими темами
Эта тема объединяет передачу данных и вложение разметки:
- Сигнальные input и output — Данные между родителем и ребёнком ходят через входы и выходы
- Компоненты — Проекция вкладывает содержимое внутрь компонента-обёртки
- Директивы — hostDirectives и атрибутивные директивы дополняют поведение проецируемых элементов
Итог
- Родитель и ребёнок общаются через сигнальные input (данные вниз) и output (события вверх) - это основной канал связи
- ng-content резервирует слот, куда попадает содержимое, вложенное родителем в тег компонента
- Несколько слотов разделяются атрибутом select, который выбирает содержимое по CSS-селектору
- Содержимое без подходящего select попадает в слот по умолчанию, если он есть
- ngProjectAs перенаправляет элемент в нужный слот, когда его собственный селектор не совпадает с select
Связанные уроки
- ng-06-inputs-outputs — Связь родителя и ребёнка строится на сигнальных входах и выходах из прошлого урока
- ng-03-components — Проекция вкладывает содержимое в компонент, чьё устройство разобрано ранее