iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 22
1
Modern Web

從巨人的 Tip 看 Angular系列 第 22

[Day 22] 深度探討 ng-content 背後的機制 (1)

OK!昨天介紹了一個強而有力的 tag:ng-content,今天不免俗的就要來爬一下 source code,了解在 content projection 的機制背後,Angular 做了那些不為人知的事情。

萬事都從被編譯後的結果開始

<div style="border: 2px solid blue; padding: 1rem;">
  <p>project works!</p>
  <p>This is the view of PrjectComponent</p>
  <ng-content></ng-content>
</div>

↑ Block 1

function ProjectComponent_Template(rf, ctx) {
  if (rf & 1) {
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵprojectionDef"]();
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](0, "div", 0);
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](1, "p");
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](2, "project works!");
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](3, "p");
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](
      4,
      "This is the view of PrjectComponent"
    );
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵprojection"](5);
    _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
  }
}

↑ Block 2

Block 2 是 Block 1 被編譯之後的部分結果,我想前幾天我們已經看過夠多被編譯過的程式碼,我這次只需要把重要的部分貼上來就好。

ProjectComponent_Template 這個函式是 Angular 在 runtime 時會拿來建立 DOM 元素的依據,其中由 ɵɵ 開頭的函式,都是被稱作 instruction 的操作,今天的重點有兩個,一個是最開頭的 ɵɵprojectionDef instruction,第二個是倒數第二個被呼叫的 instruction:ɵɵprjection

來看個原始碼

export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void {
  const componentNode = getLView()[DECLARATION_COMPONENT_VIEW][T_HOST] as TElementNode;

  if (!componentNode.projection) {
    // If no explicit projection slots are defined, fall back to a single
    // projection slot with the wildcard selector.
    const numProjectionSlots = projectionSlots ? projectionSlots.length : 1;
    const projectionHeads: (TNode|null)[] = componentNode.projection =
        newArray(numProjectionSlots, null! as TNode);
    const tails: (TNode|null)[] = projectionHeads.slice();

		// 下接 Block 4
  }
}

↑ Block 3:ɵɵprojectionDef

在進到 ɵɵprojectionDef 函式之後,Angular 會先依照 projection 屬性來判斷當前的 TNode 有沒有被處理過,有的話就跳過!

如果 projection 這個屬性沒有東西的話就會依照 template 上有的 projectionSlots 數量來初始化一個長度一樣的陣列。如果 projectionSlots 沒有東西,就會給一個 1,使用 * 作為 selector,也就是預設將所有元素投影到這個 slot。

let componentChild: TNode|null = componentNode.child;
while (componentChild !== null) {
      const slotIndex =
          projectionSlots ? matchingProjectionSlotIndex(componentChild, projectionSlots) : 0;

      if (slotIndex !== null) {
        if (tails[slotIndex]) {
          tails[slotIndex]!.projectionNext = componentChild;
        } else {
          projectionHeads[slotIndex] = componentChild;
        }
        tails[slotIndex] = componentChild;
      }

      componentChild = componentChild.next;
    }

↑ Block 4

一旦完成 Block 3 的前置作業,就可以開始來做配對囉!

Block 4 在進入 while 迴圈的時候,會先呼叫一個 matchingProjectionSlotIndex 方法,這個方法就如同它的名稱一樣,就是拿來比對給定的 componentChild 身上的 attributes 有沒有符合 projectionSlots 所要求的,若有符合,就回傳該 slot 的 index,藉以辨識要將 componentChild 投影到哪一個 slot。

以今天最開始的 Block 1 為範例的話,我們會直接得到 0 因為根本沒有傳 projectionSlots 進來。

接著就會來設定 projection 這個最一開始被設定的屬性了!

最後就是透過 ɵɵprojection 這個 instruction 來把剛剛排序好的 TNode 們放進 DOM 囉:

export function ɵɵprojection(
    nodeIndex: number, selectorIndex: number = 0, attrs?: TAttributes): void {
  const lView = getLView();
  const tView = getTView();
  const tProjectionNode =
      getOrCreateTNode(tView, nodeIndex, TNodeType.Projection, null, attrs || null);

  // We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
  if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex;

  // `<ng-content>` has no content
  setCurrentTNodeAsNotParent();

  // We might need to delay the projection of nodes if they are in the middle of an i18n block
  if (!delayProjection) {
    // re-distribution of projectable nodes is stored on a component's view level
    applyProjection(tView, lView, tProjectionNode);
  }
}

以上就是單一個 ng-content 時的投影流程,因為只有單一個 ng-content 所以看不太出來 tails 與 projectionHeads 的優美設計,也沒有特別說明 Angular 比對 attribute 的作法,我們明天用另一個更複雜的範例再來詳細說明!

倒數 8 天啦 ?

以下按照入團順序列出我們團隊夥伴的系列文章!

請自由參閱 ?


上一篇
[Day 21] 關於 Content Projection
下一篇
[Day 23] 深度探討 ng-content 背後的機制 (2)
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言