惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
量子位
腾讯CDC
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
T
The Exploit Database - CXSecurity.com
B
Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threatpost
博客园 - 聂微东
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
C
Check Point Blog
N
Netflix TechBlog - Medium
D
DataBreaches.Net
爱范儿
爱范儿
IT之家
IT之家
S
Secure Thoughts
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
有赞技术团队
有赞技术团队
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
Schneier on Security
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
雷峰网
雷峰网
Project Zero
Project Zero
博客园 - Franky
H
Heimdal Security Blog
A
About on SuperTechFans
Security Latest
Security Latest
Webroot Blog
Webroot Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как Angular v22 решил проблему дублирующихся host directives
Святослав Зайцев · 2026-06-19 · via Все публикации подряд на Хабре

Средний

7 мин

22

Привет, Хабр! Я Святослав Зайцев, занимаюсь разработкой Angular-приложений и внутренних библиотек компонентов в Т-Банке.

Недавно в Angular v22 появилось изменение, которое прошло почти незаметно на фоне более громких новинок. Тем не менее оно снимает одно из ключевых ограничений Directive Composition API.

Если вы используете hostDirectives для переиспользования поведения между директивами и компонентами, то могли сталкиваться с ситуациями, когда вполне логичная композиция внезапно заканчивалась ошибкой NG0309.

В статье разберем, почему так происходило раньше, что изменилось в Angular v22 и как новый механизм дедупликации host directives влияет на архитектуру компонентов и дизайн-систем.

Как host directives работали до Angular v22

Directive Composition API позволяет директивам и компонентам подключать другие директивы к своему host-элементу через hostDirectives. Например:

@Directive({
  host: {
    '[class.interactive]': 'true',
  },
})
export class AppearanceDirective {}

@Directive({
  selector: '[appButton]',
  hostDirectives: [AppearanceDirective],
})
export class ButtonDirective {}

@Directive({
  selector: '[appDropdownItem]',
  hostDirectives: [AppearanceDirective],
})
export class DropdownItemDirective {}

Пока все выглядит вполне логично. AppearanceDirective содержит общий визуальный функционал, а ButtonDirective и DropdownItemDirective его переиспользуют. Но реальные UI-примитивы часто комбинируются. Один и тот же элемент вполне может одновременно быть и кнопкой, и пунктом выпадающего меню:

<div appButton appDropdownItem>
  Settings
</div>

До Angular v22 фреймворк обходил обе цепочки директив и находил AppearanceDirective дважды на одном элементе:

appButton
  └─ AppearanceDirective
appDropdownItem
  └─ AppearanceDirective

В результате появлялась ошибка:

NG0309: Directive AppearanceDirective matches multiple times on the same element.

Directives can only match an element once.

Проблема заключалась не в том, что разработчик явно добавил одну и ту же директиву дважды. Дубликат возникал косвенно — как следствие переиспользуемой композиции. Из-за этого hostDirectives становились хрупким инструментом. Авторам библиотек приходилось заранее учитывать все возможные комбинации директив. Общая базовая директива могла внезапно стать непригодной для использования, если две более высокоуровневые директивы оказывались на одном элементе.

Обычно проблему решали одним из трех способов:

  • убирали общую host-директиву из одной из веток;

  • дублировали логику;

  • создавали специальные версии директив для отдельных случаев.

Все эти варианты усложняли поддержку библиотек. Это ограничение обсуждалось в Angular Issue #57846, а реализация нового поведения появилась в PR #67996.

Что изменилось в механизме обработки host directives в Angular v22

На мой взгляд, самое важное изменение состоит из двух частей.

Приоритет получает директива из шаблона. Если Angular находит одну и ту же директиву одновременно через template и через hostDirectives, приоритет получает директива, явно указанная в шаблоне.

Рассмотрим пример:

@Directive({
  selector: '[appTooltip]',
})
export class TooltipDirective {
  text = input('');
}

@Directive({
  selector: '[appIconButton]',
  hostDirectives: [
    {
      directive: TooltipDirective,
      inputs: ['text: tooltipText'],
    },
  ],
})
export class IconButtonDirective {}

Теперь повесим обе директивы на один элемент:

<button
  appIconButton
  appTooltip
  text="Delete item"
>
  🗑️
</button>

Angular обнаружит TooltipDirective дважды:

Template match:
  appTooltip → TooltipDirective

Host directive match:
  appIconButton → IconButtonDirective → TooltipDirective

С новым алгоритмом Angular оставляет директиву, найденную через шаблон, и отбрасывает host directive match. В результате остается исходный API директивы, а не алиас из конфигурации host directive:

<button
  appIconButton
  appTooltip
  text="Delete item <!-- ✓ доступно -->" 
  tooltipText="Delete item <!-- ✗ недоступно -->"
>
  🗑️
</button>

Такое поведение сделано намеренно. Директива, найденная через шаблон, считается полноценным экземпляром директивы со всем ее API. В свою очередь, host directive может публиковать только часть API или переименовывать входные и выходные параметры через алиасы.

Поэтому, если Angular приходится выбирать между двумя вариантами, выигрывает явное использование директивы в шаблоне. Другими словами, если разработчик специально добавил директиву в шаблон, Angular предполагает, что он хочет работать с ее оригинальным API, а не с оберткой поверх нее.

Дубликаты host directives объединяются. Если дубликат возникает только через hostDirectives, Angular создает единственный экземпляр директивы.

@Directive()
export class SharedTriggerState {
  id = trigger-${crypto.randomUUID()};
}

@Directive({
  selector: '[appPopoverTrigger]',
  hostDirectives: [SharedTriggerState],
})
export class PopoverTriggerDirective {
  state = inject(SharedTriggerState);
}

@Directive({
  selector: '[appDropdownTrigger]',
  hostDirectives: [SharedTriggerState],
})
export class DropdownTriggerDirective {
  state = inject(SharedTriggerState);
}

Использование двух директив, которые подключают одну и ту же host directive:

<button appPopoverTrigger appDropdownTrigger>
  Actions
</button>

До Angular v22:

NG0309: SharedTriggerState matches multiple times

Начиная с Angular v22: SharedTriggerState создается один раз, PopoverTriggerDirective получает этот экземпляр через inject(), а  DropdownTriggerDirective получает тот же самый экземпляр.

Именно дедупликация host directive и создание одного общего экземпляра является главным улучшением. Теперь Angular рассматривает композицию директив как граф, в котором разные ветки могут сходиться в одной общей директиве, а не как ошибочную ситуацию с дубликатами.

Inputs и Outputs тоже объединяются

Есть еще один важный нюанс нового механизма — объединение публичного API дублирующихся host directives. По умолчанию host directives не публикуют свои inputs и outputs. Это нужно делать явно.

@Directive()
export class SharedTriggerState {
  triggerId = input.required<string>();
}

Две разные директивы могут публиковать один и тот же input общей host-директивы.

@Directive({
  selector: '[appPopoverTrigger]',
  hostDirectives: [
    {
      directive: SharedTriggerState,
      inputs: ['triggerId'],
    },
  ],
})
export class PopoverTriggerDirective {}

@Directive({
  selector: '[appDropdownTrigger]',
  hostDirectives: [
    {
      directive: SharedTriggerState,
      inputs: ['triggerId'],
    },
  ],
})
export class DropdownTriggerDirective {}

Теперь есть возможность использовать обе директивы на одном элементе:

<button
  appPopoverTrigger
  appDropdownTrigger
  triggerId="main-actions"
>
  Actions
</button>

Angular объединяет дублирующиеся host directives и оставляет единственный mapping для triggerId.

Конфликтующие алиасы по-прежнему приводят к ошибке

Дедупликация не означает, что Angular пытается угадывать, какой API выбрать.

Код считается некорректным, потому что две разные цепочки host directives публикуют один и тот же input triggerId под разными публичными именами:

@Directive({
  selector: '[appPopoverTrigger]',
  hostDirectives: [
    {
      directive: SharedTriggerState,
      inputs: ['triggerId: popoverTriggerId'],
    },
  ],
})
export class PopoverTriggerDirective {}

@Directive({
  selector: '[appDropdownTrigger]',
  hostDirectives: [
    {
      directive: SharedTriggerState,
      inputs: ['triggerId: dropdownTriggerId'],
    },
  ],
})
export class DropdownTriggerDirective {}

При использовании двух таких директив на одном элементе <button appPopoverTrigger appDropdownTrigger></button>, Angular не может безопасно представить один и тот же input одновременно как popoverTriggerId и dropdownTriggerId. В таком случае ошибка останется.

Исправить ситуацию можно двумя способами:

  • использовать одинаковый алиас в обеих ветках;

  • публиковать input только через одну из цепочек композиции.

Что дедупликация host directives означает для Angular-разработчиков

Для большинства разработчиков приложений дедупликация, скорее всего, будет проявляться просто как исчезновение некоторых ошибок NG0309. Для авторов библиотек и дизайн-систем влияние гораздо серьезнее. Теперь можно свободнее строить композицию поведения из небольших специализированных директив, не опасаясь, что через несколько уровней hostDirectives Angular внезапно обнаружит дубликат.

Directive Composition API стала ближе к тому, какой ее изначально представляли разработчики: механизмом сборки сложного поведения из небольших независимых блоков. Это не громкая новая фича и не очередной синтаксический сахар для шаблонов. Но для дизайн-систем, библиотек компонентов и сложных UI-примитивов это изменение снимает серьезное архитектурное ограничение.

Итоги

До Angular v22 одна и та же директива не могла попасть на элемент через несколько путей композиции. Даже если разработчик явно не добавлял ее дважды, Angular завершал работу с ошибкой NG0309.

Теперь фреймворк умеет дедуплицировать такие директивы, объединять их публичный API и создавать единый экземпляр там, где это безопасно.

Используете ли вы hostDirectives в своих проектах? Сталкивались ли с ошибкой NG0309 или похожими ограничениями композиции? Будет интересно обсудить это в комментариях.