iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 11
0
Modern Web

從巨人的 Tip 看 Angular系列 第 11

[Day 11] 關於 @Self、@Optional、@SkipSelf 的二三事與 @Host 的陷阱

  • 分享至 

  • twitterImage
  •  

在 Angular DI framework 的加持下,開發人員可以很輕易的就透過在 constructor 上加入 parameter 的方式告知 Angular 要注入哪些相依性的物件。在預設的行為下,Angular 的 DI framework 會從當下的 injector 開始尋找是否有合適的物件可以回傳,若有需要的話會一直往上找到 root injector 為止。

而今天要介紹的不算是 Tip,而是可以修改 DI framework 搜尋 token 行為的 parameter decorator!

關於這些 decorator 的官方說法

  • @Optional:Parameter decorator to be used on constructor parameters, which marks the parameter as being an optional dependency. The DI framework provides null if the dependency is not found.

    翻譯:找不到就給 null,不報錯。

  • @Self:Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the local injector.

    翻譯:只找自己。

  • @SkipSelf:Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the parent injector. Resolution works upward through the injector hierarchy, so the local injector is not checked for a provider.

    翻譯:跳過自己從上一層的 injector 開始找。

  • @Host:Parameter decorator on a view-provider parameter of a class constructor that tells the DI framework to resolve the view by checking injectors of child elements, and stop when reaching the host element of the current component.

    翻譯:到 host element 之後就停下來,不要再找了。

關於 @Host 的陷阱

這四個 parameter decorator 就字面上的意思來看,其實都還滿好理解每個 decorator 所扮演的角色與功用,唯獨那個 @Host decorator 以直覺來腦補的時候會跟官方的說明完全對不起來!

一般可能會認為這個 decorator 就是先找自己的 injector 後再往上一層的 injector 後就停下來,但事實上不是這樣!這個 @Host decorator 看的不是 injector 的階層,而是以當下 component 的 HTML 元素階層來尋找,什麼意思呢?

我們看以下範例:

// HTML Template
<app-b>
  <app-b2></app-b2>
</app-b>

↑ Block 1:AppComponent

// HTML Template
<p>b works!</p>
<ng-content></ng-content>
<app-b2 id="bad"></app-b2>

// TypeScript
@Component({
  selector: 'app-b',
  templateUrl: './b.component.html',
  styleUrls: ['./b.component.scss'],
  providers: [
    {
      provide: CosService,
      useClass: BCosService
    }
  ]
})
export class BComponent {}

↑ Block 2:BComponent

// HTML Template
<p>b2 works!: {{ content }}</p>

// TypeScript
@Component({
  selector: 'app-b2',
  templateUrl: './b2.component.html',
  styleUrls: ['./b2.component.scss']
})
export class B2Component implements OnInit {
  content = 'undefined';
  constructor(@Host() @Optional() public cos: CosService) { }

  ngOnInit(): void {
    if (this.cos) {
      this.content = this.cos.print();
    }
  }

↑ Block 3:B2Component

測試的方式如下:

  1. BComponent 新增了一個 CosService 的 provider
  2. AppComonent 的 內加入 B2Component
  3. BComponent HTML template 加入 B2Component

結果就如下圖所示,只有在 AppComponent 透過 ng-content 做 projection 的 B2Component 能夠取得 CosService。

https://ithelp.ithome.com.tw/upload/images/20200926/20129148xpUKB59fS0.png

開始爬 code?

B2Component.ɵfac = function B2Component_Factory(t) { return new (t || B2Component)(
  _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdirectiveInject"](
      _cos_service__WEBPACK_IMPORTED_MODULE_1__["CosService"], 9
    )
  ); 
};

↑ Block 4

乍看下其實還看不出來加的 @Host 與 @Option 這兩個 decorator 是跑去哪,但如果你跟 ɵɵdirectiveInject 這個函式混得夠久,就知道放在第二的參數那個 9 其實就是屬於 InjectFlags 這個 enum 與上面 4 個 decorator 在經過 compile 後的結果,而 InjectFlags 的內容是:

export enum InjectFlags {
  // TODO(alxhub): make this 'const' when ngc no longer writes exports of it into ngfactory files.

  /** Check self and check parent injector if needed */
  Default = 0b0000,
  /**
   * Specifies that an injector should retrieve a dependency from any injector until reaching the
   * host element of the current component. (Only used with Element Injector)
   */
  Host = 0b0001,
  /** Don't ascend to ancestors of the node requesting injection. */
  Self = 0b0010,
  /** Skip the node that is requesting injection. */
  SkipSelf = 0b0100,
  /** Inject `defaultValue` instead if token not found. */
  Optional = 0b1000,
}

getOrCreateInjectable 這個函式內就多次運用到 &| 這兩種 bitwise operator 與 InjectFlags 做計算,下方的 shouldSearchParent 函式也是:

function shouldSearchParent(flags: InjectFlags, isFirstHostTNode: boolean): boolean|number {
  return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
}

↑ Block 5

結論

Angular 設計了 4 種不同的 parameter decorator 來協助我們能夠更彈性的使用 DI framework,每個 parameter decorator 分別對應到 4 個 bit 的其中一個位元,在經過 compile 之後,就可以轉換出 0 ~ 15 的十進位數字,當 Angular 要解析 token 的時候,就只需要用這個十進位數字,分別按照不同的情況與上面的 4 種 decorator 做計算,就能夠知道當下搜尋 token 的行為要做到哪種程度。

以上就是今天要分享給大家的一點小研究成果,各位在開發時也要小心不要掉進 @Host decorator 的陷阱囉!

補班,是為了放更多的假 ?

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


上一篇
[Day 10] 深度看一下 Angular 建立 multi provider 的機制
下一篇
[Day 12] 深度探討 Angular 解析 Attribute directive 的背後流程
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言