07 — Как Angular находит селекторы
Цель: развеять главный миф. Точная версия — 03-selector-matching в соседнем vault'е.
Миф, который надо выбросить из головы
«Angular в браузере сканирует DOM, ищет элементы по селекторам и навешивает на них компоненты.»
Это неправда. Так работает jQuery, но не Angular. Поиск по живому DOM
(querySelector) случается ровно один раз — на старте, чтобы найти <app-root>
(см. главу 03).
Всё остальное Angular знает заранее — с компиляции. Разберём, как.
Аналогия: сборка мебели по инструкции
Когда ты собираешь шкаф из IKEA, ты не ищешь по всей квартире, куда вкрутить винт. В инструкции уже нарисовано: «винт A → отверстие 5». Тебе не надо искать — тебе сказали.
Так и Angular: компилятор, видя твой шаблон и список imports, заранее знает,
какой компонент/директива куда встаёт, и записывает это в инструкцию. В рантайме не
надо «искать» — надо просто следовать записанному.
Три уровня — где что происходит
Уровень 1 — компиляция (тут вся работа по селекторам)
Компилятор смотрит на imports твоего компонента и составляет список кандидатов —
всех директив/компонентов, которые тут могут встретиться. Этот список зашивается в
скомпилированный компонент. Сами селекторы (app-foo, [myDir], .cls) он уже
разобрал в удобную форму.
Уровень 2 — старт (единственный реальный DOM-поиск)
bootstrapApplication(App) → Angular делает document.querySelector('app-root'),
находит место на странице, ставит туда корневой компонент. Один раз. Всё.
Уровень 3 — построение шаблона (сопоставление без DOM)
Когда строится компонент, для каждого элемента шаблона Angular берёт список кандидатов (с уровня 1) и проверяет: подходит ли селектор каждого кандидата к этому элементу. Но проверяет он не живой DOM, а статическое описание узла (TNode из главы 06) — его тег и атрибуты.
И главное: это сопоставление делается один раз на класс шаблона (в первый
проход), результат запоминается в бланке TView. Создаёшь 100 одинаковых
компонентов — селекторы матчатся один раз, остальные 99 берут готовое.
Как выглядит сопоставление
Допустим, в шаблоне есть <my-button primary>, а среди кандидатов — компонент с
селектором my-button[primary]. Angular проверяет по кусочкам:
селектор: my-button [primary]
элемент: <my-button primary>
───────── ────────
проверка: тег совпал? да → атрибут primary есть? да → МАТЧ ✅
Если хоть один кусочек не сошёлся — кандидат отбрасывается, берётся следующий.
Полезный факт: селектор по атрибуту [primary] срабатывает и на обычном
primary="...", и на биндинге [primary]="x", и на событии — потому что Angular
сравнивает имена атрибутов из статического описания, а не из DOM.
Что происходит после матча
Нашли подходящую директиву/компонент → Angular:
- создаёт её экземпляр (через DI — склад из главы 03);
- прокидывает начальные
@Inputиз атрибутов; - навешивает host-биндинги — это
(click)/[class]/[attr], объявленные в самой директиве черезhost: {...}; - у компонента дополнительно создаётся свой LView (своя «копия бланка»).
Частые вопросы
«Почему моя директива не подхватилась?» — Скорее всего, ты не добавил её в
imports компонента. Тогда её нет в списке кандидатов, и матчить нечего. Это ошибка
компиляции/конфигурации, а не «селектор не нашёлся в браузере».
«Можно два компонента на один элемент?» — Нет. Один компонент-хозяин на элемент (плюс сколько угодно обычных директив). Второй компонент — ошибка компиляции.
Короткий итог
- Селекторы — это работа компилятора, не браузера.
- Живой
querySelector— один раз, на<app-root>. - Внутри шаблонов идёт сопоставление кандидатов со статическим описанием узлов, один раз на класс, результат кешируется.
Дальше: как контент создаётся и переезжает динамически → 08 — Динамика и порталы.