昨天介紹了自製 two-way binding 的方式,今天要接著看在背後 Angular 為我們做了哪些事!
今天會從以下的範例程式碼開始,逐步找出 Angular 為什麼可以做到 two-way binding。
<app-slider-wrapper [(sliderValue)]="slider"></app-slider-wrapper>
<div>
<label>Slider value: </label><label>{{ slider }}</label>
</div>
↑ Block 1
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdefineComponent"]({
// ... 略
consts: [[3, "sliderValue", "sliderValueChange"]],
template: function AppComponent_Template(rf, ctx) {
if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](
0,
"app-slider-wrapper",
0
);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵlistener"](
"sliderValueChange",
function AppComponent_Template_app_slider_wrapper_sliderValueChange_0_listener(
$event
) {
return (ctx.slider = $event);
}
);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](1, "div");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](2, "label");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](3, "Slider value: ");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](4, "label");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](5);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
}
if (rf & 2) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵproperty"](
"sliderValue",
ctx.slider
);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵadvance"](5);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtextInterpolate"](
ctx.slider
);
}
},
directives: [
_slider_wrapper_slider_wrapper_component__WEBPACK_IMPORTED_MODULE_1__[
"SliderWrapperComponent"
],
]
});
↑ Block 2:被編譯後的程式碼片段
Block 2 是 Block 1 程式碼被編譯後的一小片段。可以發現在 consts
屬性內,有一個陣列包含著 sliderValue
與 sliderValueChange
,以目前現有的結果來看,我們可以大膽的推測 Angular 在編譯時期,會將 [(property)]
翻成 property
以及對應的 propertyChange
。
而相關的證據如下:
if (bindParts[IDENT_BANANA_BOX_IDX]) {
this._bindingParser.parsePropertyBinding(
bindParts[IDENT_BANANA_BOX_IDX],
value,
false,
srcSpan,
absoluteOffset,
attr.valueSpan,
targetMatchableAttrs,
targetProps
);
this._parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX],
value,
srcSpan,
attr.valueSpan || srcSpan,
targetMatchableAttrs,
boundEvents
);
}
↑ Block 3:Angular template parser 與 two-way binding 有關的部分(Link)
在 Block 3 呈現的程式碼可以看出當 Angular parser 發現 template 中有 [( )]
的配對時,他會先呼叫 parsePropertyBinding
的方法來處理 property 本身,接著會呼叫 _praseAssignmentEvent
函式來處理對應的 event:
private _parseAssignmentEvent(
name: string,
expression: string,
sourceSpan: ParseSourceSpan,
valueSpan: ParseSourceSpan,
targetMatchableAttrs: string[][],
targetEvents: ParsedEvent[]
) {
this._bindingParser.parseEvent(
`${name}Change`,
`${expression}=$event`,
sourceSpan,
valueSpan,
targetMatchableAttrs,
targetEvents
);
}
↑ Block 4:_parseAssignmentEvent 函式內容(Link)
有看到那個熟悉的 Change
字樣嗎?這邊就是 Angular 在編譯時期將 two-way binding 從 banana in a box 的語法糖,轉換成 property 以及 propertyChange 的起始點,後續再經由 compiler 的努力,就可以看到在 Block 2 中的 consts 的內容囉!
今天先解開 [( )]
Banana in a box 的語法糖為什麼在 Angular 產生的 main.js 內會不見蹤影,以及為什麼只需要符合前一篇文章所述的兩個條件後,就能夠做到 two-way binding 的原因。
至於 Angular 是怎麼處理 event 與 property binding 的部分,就是明天的內容囉!
以下按照入團順序列出我們團隊夥伴的系列文章!
請自由參閱 ?