06 — Что такое view, LView и TView

Цель: понять, из чего «сделан» компонент в памяти, когда он работает. Без этого непонятно, как устроены @for, порталы и change detection. Точная версия — 02-view-data-structures в соседнем vault'е.

Слово «view»

View — это один кусок экрана, которым управляет Angular: компонент, или тело @if, или одна итерация @for. У каждого <li> из нашего @for — свой view.

Аналогия: бланк и заполненный бланк

Представь бумажный бланк анкеты: пустые поля «Имя: ___», «Возраст: ___».

  • Сам бланк (шаблон) — один на всех. Его печатают один раз.
  • Каждый человек заполняет свою копию бланка своими данными.

В Angular это ровно две структуры:

  • TView («T» = Template, шаблон) — это бланк. Один на класс компонента. Хранит «где какое поле», какие тут есть директивы, какие хуки. Не зависит от данных.
  • LView («L» = Logical, экземпляр) — это заполненная копия. По одной на каждый экземпляр. Хранит реальные DOM-узлы и текущие значения.
TView (бланк, 1 шт)              LView (копии, много)
  поле 0: <p>                ┌─→  копия для App:   [<p>Счётчик:1</p>, 1, ...]
  поле 1: число             │    копия для <li>1:  [<li>1</li>, ...]
  поле 2: <button>          │    копия для <li>2:  [<li>2</li>, ...]
  ...                       └─   копия для <li>3:  [<li>3</li>, ...]

Один бланк TView, три заполненные копии LView — по одной на каждый <li>.

LView — это просто массив

Чтобы было быстро, LView — обычный JavaScript-массив. В ячейках лежат:

  • ссылка на свой бланк (TView);
  • флаги состояния (грязный/чистый, OnPush/Default);
  • реальные DOM-узлы;
  • значения биндингов (чтобы сравнивать старое с новым);
  • инстанс самого класса компонента;
  • ссылки на детей.

Первые 27 ячеек — служебные («шапка»). Дальше идут слоты под узлы шаблона. Когда компилятор в главе 01 выдал element('p') со слотом 0 — этот <p> ляжет в ячейку 27 + 0. Вот и вся «магия адресации».

TNode — описание одного узла

В бланке TView каждый узел описан объектом TNode (Template Node). Это «паспорт узла»: какого он типа (элемент / текст / контейнер), как называется тег, какие у него статические атрибуты, какие директивы на нём висят.

Важно: TNode — статика, общая для всех копий. У всех трёх <li> один и тот же TNode-паспорт «это <li> с текстом внутри», а конкретное число (1, 2, 3) лежит уже в LView каждой копии.

LContainer — «карман» для динамики

Там, где контент может появляться и исчезать (@if, @for, <ng-template>), Angular ставит LContainer — это «карман-якорь». Технически это невидимый комментарий в DOM (<!-- container -->), рядом с которым Angular вставляет и удаляет копии view.

<ul>
  <!-- LContainer (якорь @for) -->
  <li>1</li>   ← LView-копия 1, вставлена в карман
  <li>2</li>   ← LView-копия 2
  <li>3</li>   ← LView-копия 3
</ul>

Добавить элемент в @for = создать новую LView-копию и положить её в этот карман. Удалить = вынуть и уничтожить. Как это работает в деталях — 07 и 08.

Как DOM-элемент находит свой компонент

Когда ты в DevTools делаешь ng.getComponent($0) — как Angular по <p> понимает, к какому компоненту он относится?

Очень просто: при создании Angular приклеивает к DOM-элементу скрытое свойство __ngContext__, в котором лежит ссылка на его LView. По ней и находится и компонент, и директивы, и #ссылки.

Зачем тебе это знать

  • Понятно, почему @for дёшев: добавляется одна копия LView, а не перерисовывается весь список.
  • Понятно, почему change detection точечный: каждый LView помечается грязным отдельно.
  • Понятно, что «компонент» в рантайме — это не класс, а класс + его LView + его DOM, связанные вместе.

Дальше: как Angular понимает, какой компонент/директиву ставить на какой элемент → 07 — Селекторы.