iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Modern Web

從巨人的 Tip 看 Angular系列 第 29

[Day 29] Banana in a box!在那糖衣的背後

  • 分享至 

  • twitterImage
  •  

昨天介紹了自製 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 屬性內,有一個陣列包含著 sliderValuesliderValueChange,以目前現有的結果來看,我們可以大膽的推測 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 的部分,就是明天的內容囉!

明天最後一天啦 ?

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

請自由參閱 ?


上一篇
[Day 28] banana in a box!關於雙向繫結功能的語法糖
下一篇
[Day 30] 微探討 Angular 的 Event binding 與 Property binding
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言