網站搜尋

typescript - 我可以直接訪問 DI 注入器來使用 NestJS 實例化類別嗎


我想知道是否有方法可以在 NestJS 中訪問 DI 注入器,並直接使用它來實例化類。這類似於 Angular 或 Inversify 直接暴露注入器以便在特殊情況下使用的方式。

多提供者可以解決我的使用情境。但在 NestJS 支援多提供者之前,我正在嘗試創建一個組合守衛,將多個守衛結合成一個,這樣我就可以全局應用需要存取 DI 的多個守衛。我希望組合守衛只接收一個類型陣列,然後使用注入器為這些類型生成對象。

我嘗試將 Injector 指定為工廠依賴,但 Nest 無法解析它。如果它可以解析,我也不知道該如何使用這個注入器。

@Module({
    providers: [{
        provide: APP_GUARD,
        inject: [Injector],
        useFactory: (injector: Injector) => {
            return new MultiGuard([AuthGuard, PermissionGuard], injector);
        }
    }]
})
class AppModule {}
import { CanActivate, ExecutionContext, Type } from '@nestjs/common';

export class MultiGuard implements CanActivate {
    constructor(
        private readonly guards: Type<CanActivate>[],
        private readonly injector: Injector
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const guards = this.guards.map(guard => {
            return this.injector.get(guard);
        });

        const results = await Promise.all(guards);

        return results.every(r => !!r);
    }
}

答案#1

我最近做了類似的事情,但我不是將各種類型的守衛傳給我的「多重守衛」,而是直接在工廠中實例化守衛,然後傳入一個守衛實例的陣列。

這和 NestJS 執行守衛的方式非常相似。看看guard-consumer.ts檔案。

https://github.com/nestjs/nest/blob/master/packages/core/guards/guards-consumer.ts

@Injectable()
export class GuardsCollection implements CanActivate {
  public constructor(private readonly guards: CanActivate[]) {}

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    for (const guard of this.guards) {
      const result = guard.canActivate(context);
      if (!(await this.pickResult(result))) {
        return false;
      }
    }

    return true;
  }

  private async pickResult(result: boolean | Promise<boolean> | Observable<boolean>): Promise<boolean> {
    if (result instanceof Observable) {
      return result.toPromise();
    }
    return result;
  }
}

在工廠中,我會實例化我的守衛。如果我的守衛有任何依賴項,我可以在那裡解決它們,方法是在 Inject 陣列中指定依賴項。可選的 inject 屬性接受一個提供者陣列,NestJS 會在實例化過程中解析這些提供者並將其作為參數傳遞給工廠函數。

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useFactory: (reflector: Reflector) => {
        return new GuardsCollection([
          new (AuthGuard('jwt'))(),
          new RolesGuard(reflector),
        ]);
      },
      inject: [Reflector],
    },
  ],
})
export class AuthModule {}

另一個可能的選擇是注入一個 ModuleRef,並使用它在當前模組的上下文中解析項目。只要另一個模組被導入到當前模組中,你也應該能夠從另一個模組中獲取項目。

@Injectable()
export class MyGlobalGuard implements CanActivate {
  public constructor(private readonly moduleRef: ModuleRef) {}

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    // ... resolve items.
    return true;
  }
}

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: GlobalGuard,
    },
  ],
})
export class AuthModule {}