iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 30
1
Modern Web

從巨人的 Tip 看 Angular系列 第 30

[Day 30] 微探討 Angular 的 Event binding 與 Property binding

  • 分享至 

  • xImage
  •  

鐵人賽最後一天!

今天要跟各位分享的呢,是昨天與前天 Banana in a box 系列的延伸,關於 Angular 怎麼處理 event 與 property binding。

因為是延伸的內容,所以我會繼續以昨天的程式碼作為範例!

果然還是要從編譯後的內容開始

_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
      );
    }
  }
});

↑ Block 1

有關 Event binding

首先從編譯後的程式碼可以看到有 ɵɵlistener 這個 instruction,而這個 instruction 的實作內容如下:

export function ɵɵlistener(
  eventName: string,
  listenerFn: (e?: any) => any,
  useCapture = false,
  eventTargetResolver?: GlobalTargetResolver
): typeof ɵɵlistener {
  const lView = getLView();
  const tView = getTView();
  const tNode = getCurrentTNode()!;
  listenerInternal(
    tView,
    lView,
    lView[RENDERER],
    tNode,
    eventName,
    listenerFn,
    useCapture,
    eventTargetResolver
  );
  return ɵɵlistener;
}

↑ Block 2

ɵɵlistener 傳入的參數除了 eventName(本例是 sliderValueChange)、listenerFn 之外,還可以傳入 useCapture 與 eventTargetResolver,後兩者除非是有特殊需求,否則不會用到。

將 eventName 與 listenerFn 傳入之後 Angular 會透過 getLViewgetTViewgetCurrentTNode 方法來取得當前的 LView、TView 與 TNode。

最後則是呼叫 listenerInternal 函式(Link)並將相關的資訊傳遞給它,來做以下的事:

  1. 判斷 tNode 是不是 HTML Element。
  2. 判斷 renderer 是屬於 ObjectOrientedRenderer3 還是 ProceduralRenderer3,不同的 renderer 會有不同的處理方式。
  3. 依據 renderer 的種類將 event listener 綁到 element 上,如果是 ObjectOrientedRenderer3 的話。就採用 HTMLElement 的 addEventListener 方法來綁,如果是 ProceduralRenderer3 的話就以 renderer 實作的方式來綁。
  4. Subscribe directive 的 Output。

有關 Property binding

Angular 會在 update block 的時候呼叫 ɵɵproperty instruction 來處理 property 相關的事情,以下是它的實作內容(Link):

export function ɵɵproperty<T>(
    propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵproperty {
  const lView = getLView();
  const bindingIndex = nextBindingIndex();
  if (bindingUpdated(lView, bindingIndex, value)) {
    const tView = getTView();
    const tNode = getSelectedTNode();
    elementPropertyInternal(
        tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false);
    ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
  }
  return ɵɵproperty;
}

↑ Block 3

ɵɵproperty 在執行更新的操作之前,會先透過 bindingUpdated 函式來確認傳入的值跟先前在 lView 內紀錄的值是否相同,如果有變動的話才會執行 elementPropertyInternal 函式來更新 property,否則的話就直接回傳 ɵɵproperty instruction,讓 Angular 可以透過 ɵɵproperty('a', 'v1')('b', 'v2') 這樣的方法來設定 property binding。


以上就是今天的「你可以不用知道的 Angular」!簡潔扼要的說明了 Angular 是怎麼實作 event binding 與 property binding。

完賽心得

30 天,有夠累。

主要是因為自己籌備期間因為剛開始翻閱 Angular 原始碼,艱澀難懂到讓我一直發懶,所以直到正式開賽前一天才覺得應該擬定每天的內容,接著就開始了每天絞盡腦汁產出一篇的生活。其中有幾篇真的是困難重重,像是最開頭的 Dependency Injection 系列,那個原始碼我真的是遲遲無法參悟阿……

總之感謝各位讀者這 30 天的陪伴,因為比賽時間限制,所以我做了一些艱難的抉擇,像是不小心忽略部分說明之類的。

發佈在鐵人賽的這系列未來不會有其他更新,但我會在個人的 Medium(這裡)逐步將內容完善!熱烈歡迎有興趣的讀者跟我一起轉移陣地噢!

再次感謝!


上一篇
[Day 29] Banana in a box!在那糖衣的背後
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
黃升煌 Mike
iT邦研究生 5 級 ‧ 2020-10-15 21:26:31

恭喜寫出 compiler 等級文章的大大完賽!我以為會看 source code 已經很強了,能看懂 compile 後的程式碼,根本神之領域 XD
/images/emoticon/emoticon08.gif

ethan iT邦新手 5 級 ‧ 2020-10-15 21:45:48 檢舉

看到頭幾天的文章我就決定投降了呢...
這境界 高

0
隔壁的老王
iT邦新手 5 級 ‧ 2020-10-15 22:27:37

/images/emoticon/emoticon12.gif神的領域!!恭喜完賽!~~

0
老屁股
iT邦新手 5 級 ‧ 2020-10-15 22:33:51

雖然已經私下恭喜過了
但是就是要佔個樓 在恭喜一次!!太強大了!
EP 根本就是 Excellent Peter !!!!!! (硬要XDDDD)

米歐 iT邦新手 3 級 ‧ 2020-10-16 09:44:41 檢舉

/images/emoticon/emoticon01.gif

0
暐翰
iT邦大師 1 級 ‧ 2020-10-16 08:15:01

恭喜完賽!
/images/emoticon/emoticon12.gif

我要留言

立即登入留言