好的,昨天文章的最後,我在 HTML template 上使用了 這個 tag 來作為 AnchorDirective 的 host element,會這麼做的原因其實滿簡單的,就是 ng-template 這個元素實際上「並不會」被放進 DOM 中,所以也就不會影響 CSS 樣式,導致跑版的問題。。
也因為這個特性,所以在設計 Angular component 上有需要因為使用者操作來改變畫面版型的時候,就很適合以 ng-template 來當作 structural directives 的 host element。
然而今天要分享的內容沒有其他,就是來看一下為什麼 ng-template 會從 DOM 上消失。
<ng-template [ngIf]="true">
<p>Hello, world!</p>
</ng-template>
↑ Block 1
我將以 Block 1 的簡單程式碼作為範例,來分析 Angular 編譯後的結果,以及 Angular 會如何處理 ng-template 的 html tag。
function AppComponent_ng_template_0_Template(rf, ctx) {
if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](0, "p");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](1, "Hello, world!");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
}
}
↑ Block 2
Block 2 是一個滿關鍵的點,Angular 的 compiler 會把 ng-template 的 tag 轉換成一個 function,並在這個 function 內將 ng-template 的 element 用 ɵɵelementStart
與 ɵɵelementEnd
等 instruction 將 elements 渲染出來。所以我們可以得知,實際上並不會有一個 ng-template 的 tag 被放進 DOM 中。
結束!
然後我們來看一下 Block 1 這個範例的 AppComponet 經過編譯後的其他內容:
AppComponent.ɵfac = function AppComponent_Factory(t) {
return new (t || AppComponent)();
};
AppComponent.ɵcmp = _angular_core__WEBPACK_IMPORTED_MODULE_0__[
"ɵɵdefineComponent"
]({
type: AppComponent,
selectors: [["app-root"]],
decls: 1,
vars: 1,
consts: [[3, "ngIf"]],
template: function AppComponent_Template(rf, ctx) {
if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtemplate"](
0, // index
AppComponent_ng_template_0_Template, // templateFn
2, // decls
0, // vars
"ng-template", // tagName
0 // attrsIndex
);
}
if (rf & 2) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵproperty"]("ngIf", true);
}
},
directives: [_angular_common__WEBPACK_IMPORTED_MODULE_1__["NgIf"]],
styles: [
/ ...
],
});
/*@__PURE__*/ (function () {
// ... 略
})();
↑ Block 3
Block 3 除了工廠方法之外,也有 AppComponent 的 definition,可以看到第一個元素(ng-template)就會將 Block 2 的AppComopnent_ng_template_0_Template function 傳入 ɵɵtemplate
。
然後我們就可以翻 Angular 的原始碼囉。
當瀏覽器開始執行 main.js 的程式碼來渲染整個應用程式時,會先從 NgModule 開始處理,一路處理到 AppComponent,接著處理 AppComponent 的 HTML Template,也就是 Block 3 的 AppComponent_Template 函式,因為範例只有一個 ng-template 的 tag,所以馬上就會執行 ɵɵtemplate。
我們來看一下 ɵɵtemplate
這個函式的實作內容:
export function ɵɵtemplate(
index: number, templateFn: ComponentTemplate<any>|null, decls: number, vars: number,
tagName?: string|null, attrsIndex?: number|null, localRefsIndex?: number|null,
localRefExtractor?: LocalRefExtractor) {
const lView = getLView();
const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
const tNode = tView.firstCreatePass ?
templateFirstCreatePass(
index, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) :
tView.data[adjustedIndex] as TContainerNode;
setCurrentTNode(tNode, false);
const comment = lView[RENDERER].createComment(ngDevMode ? 'container' : '');
appendChild(tView, lView, comment, tNode);
attachPatchData(comment, lView);
addToViewTree(lView, lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode));
if (isDirectiveHost(tNode)) {
createDirectivesInstances(tView, lView, tNode);
}
if (localRefsIndex != null) {
saveResolvedLocalsInData(lView, tNode, localRefExtractor);
}
}
↑ Block 4:ɵɵtemplate 的實作內容
如同 ɵɵElementStart 方法一樣,ɵɵtemplate 也會先取得目前的 LView 與 TView 物件實體,接著會透過 templateFirstCreatePass
方法建立 TNode 物件並指派給 tNode 變數。若已經建立有相同的 TNode 物件被建立的話,Angular 會選擇將已存在的物件指派給 tNode,而非重新再建立一次。
另外,這個函式執行的時候也會產生一個 HTML 的註解,變數名稱為 comment。
建立完 tNode 與 comment 之後,Angular 會接著透過 appendChild
方法將 comment 放進 DOM Tree 內:
export function appendChild(
tView: TView, lView: LView, childEl: RNode|RNode[], childTNode: TNode): void {
const renderParent = getRenderParent(tView, childTNode, lView);
if (renderParent != null) {
const renderer = lView[RENDERER];
const parentTNode: TNode = childTNode.parent || lView[T_HOST]!;
const anchorNode = getNativeAnchorNode(parentTNode, lView);
if (Array.isArray(childEl)) {
for (let i = 0; i < childEl.length; i++) {
nativeAppendOrInsertBefore(renderer, renderParent, childEl[i], anchorNode);
}
} else {
nativeAppendOrInsertBefore(renderer, renderParent, childEl, anchorNode);
}
}
}
↑ Block 5
在瀏覽器上看的時候,就會有一個像下圖的 comment:
↑ Image 1
如此一來原先在 HTML template file 內寫的 ng-template tag 就消失不見了,只留下一些註解跟開發者說:「嘿!這裡可能會有東西喔!」。
至於其他被放進 ng-template 內的元素,瀏覽器會呼叫 executeTemplate
這個函式(link)來執行編譯後的 template function(Block 2 的 AppComponent_ng_template_0_Template 函式),再來就會依序呼叫 ɵɵElementStart、ɵɵtext、ɵɵElementEnd 來將放在 ng-template 內的元素渲染出來囉 ?
以上就是今天要跟各位分享的內容囉,明天會再接著介紹其他關於 ng-template 的進階用法!
以下按照入團順序列出我們團隊夥伴的系列文章!
請自由參閱 ?