本系列文已出版成書「NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式」,感謝 iT 邦幫忙與博碩文化的協助。如果對 NestJS 有興趣、覺得這個系列文對你有幫助的話,歡迎前往購書,你的支持是我最大的寫作動力!
前面有提過,注入 Provider 的方式只需要在 constructor
設計參數並附上對應的型別,或使用 @Inject
裝飾器來取得對應的實例,以 app.controller.ts
為例,在 constructor
中直接填入參數並附上型別為 AppService
就可以取得 AppService
的實例:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
事實上,Nest 還有提供另一種不同的方式來取得內部 Provider 的實例,它叫 模組參照 (Module Reference)。
它是一個名叫 ModuleRef
的 class
,可以對內部 Provider 做一些存取,可以說是該 Module 的 Provider 管理器。
使用上與 Provider 注入的方式相同,在 constructor
注入即可,以 app.controller.ts
為例:
import { Controller } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
@Controller()
export class AppController {
constructor(
private readonly moduleRef: ModuleRef
) {}
}
注入 ModuleRef
以後,可以透過 get
方法來取得 當前 Module 下的 任何元件,如:Controller、Service、Guard 等。
注意:此方法無法在非預設作用域的配置下使用。
這裡以 app.controller.ts
為例,我們透過 ModuleRef
來取得 AppService
的實例:
import { Controller, Get } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { AppService } from './app.service';
@Controller()
export class AppController {
private readonly appService: AppService;
constructor(
private readonly moduleRef: ModuleRef
) {
this.appService = this.moduleRef.get(AppService);
}
@Get()
getHello() {
return this.appService.getHello();
}
}
透過瀏覽器查看 http://localhost:3000 是能夠正常存取的,表示該方法是正常運作的:
如果要取得 全域 的實例,需要給定參數 strict
為 false
即可取得全域範圍的實例,這裡先產生一個 StorageModule
與 StorageService
,並將該 Module 設置為全域:
$ nest generate module common/storage
$ nest generate service common/storage
修改 storage.module.ts
的內容:
import { Global, Module } from '@nestjs/common';
import { StorageService } from './storage.service';
@Global()
@Module({
providers: [
StorageService
],
exports: [
StorageService
]
})
export class StorageModule {}
修改 storage.service.ts
的內容:
import { Injectable } from '@nestjs/common';
@Injectable()
export class StorageService {
private list: any[] = [];
public addData(data: any): void {
this.list.push(data);
}
public getList(): any[] {
return this.list;
}
}
接著,調整 app.controller.ts
的內容,透過 ModuleRef
來取得全域實例 - StorageService
:
import { Controller, Get } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { StorageService } from './common/storage/storage.service';
@Controller()
export class AppController {
private readonly storageService: StorageService;
constructor(
private readonly moduleRef: ModuleRef
) {
this.storageService = this.moduleRef.get(StorageService, { strict: false });
this.storageService.addData({ name: 'HAO' });
}
@Get()
getHello() {
return this.storageService.getList();
}
}
透過瀏覽器查看 http://localhost:3000 能夠正常存取,表示該方法是正常運作的:
既然在非預設作用域的配置下無法使用 get
來取得實例,那該如何處理呢?這時候可以透過 resolve
來解決,resolve
會從自身的 依賴注入容器子樹 (DI container sub-tree) 返回實例,而每個子樹都有一個獨一無二的 識別碼 (Context Identifier),因此每次 resolve
都會是 不同的實例。
來做個簡單的實驗,先將 AppService
轉化成請求作用域:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
接著,我們在 AppController
中使用兩次 resolve
並比對他們是否為相同的實例,啟動 Nest 之後,會在終端機看到比對結果為 false
:
import { Controller, OnModuleInit } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { AppService } from './app.service';
@Controller()
export class AppController implements OnModuleInit {
constructor(
private readonly moduleRef: ModuleRef
) {}
async onModuleInit() {
const [instance1, instance2] = await Promise.all([
this.moduleRef.resolve(AppService),
this.moduleRef.resolve(AppService)
]);
console.log(instance1 === instance2); // false
}
}
如果要將多個 resolve
回傳相同的實例,可以透過指定識別碼讓它們使用相同的子樹,進而取得相同的實例。在指定識別碼之前,可以透過 ContextIdFactory
這個 class
的 create
方法來產生識別碼。這裡同樣以 AppController
為例,在 resolve
之前先產生識別碼並帶入 resolve
中,啟動 Nest 之後,會在終端機看到結果為 true
:
import { Controller, OnModuleInit } from '@nestjs/common';
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
import { AppService } from './app.service';
@Controller()
export class AppController implements OnModuleInit {
constructor(
private readonly moduleRef: ModuleRef
) {}
async onModuleInit() {
const identifier = ContextIdFactory.create();
const [instance1, instance2] = await Promise.all([
this.moduleRef.resolve(AppService, identifier),
this.moduleRef.resolve(AppService, identifier)
]);
console.log(instance1 === instance2); // true
}
}
在請求作用域下,可以透過 ContextIdFactory
的 getByRequest
來基於請求物件建立識別碼,進而達到共享子樹的效果。
我們這裡做個實驗,在 AppController
中注入 REQUEST
並透過 getByRequest
取得識別碼,根據該識別碼來執行兩次 resolve
以及比對實例:
import { Controller, Get, Inject } from '@nestjs/common';
import { ContextIdFactory, ModuleRef, REQUEST } from '@nestjs/core';
import { Request } from 'express';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
private readonly moduleRef: ModuleRef,
@Inject(REQUEST) private readonly request: Request
) {}
@Get()
async getTruth() {
const identifier = ContextIdFactory.getByRequest(this.request);
const [instance1, instance2] = await Promise.all([
this.moduleRef.resolve(AppService, identifier),
this.moduleRef.resolve(AppService, identifier)
]);
return instance1 === instance2;
}
}
透過瀏覽器查看 http//:localhost:3000 會得到結果為 true
。
這裡附上今天的懶人包:
ModuleRef
可以對內部 Provider 做一些存取。ModuleRef
預設只能取得當前模組下的實例,透過調整 strict
才能取得全域實例。resolve
來取得實例。resolve
回傳的實例都不同,需要透過指定識別碼來配置成相同實例。ModuleRef
來綁定識別碼。ContextIdFactory
可以產生識別碼與從請求物件中取得識別碼。進階功能的部分到這邊告一段落,下一篇開始將會進入到 多元化功能 單元,敬請期待!