typescript - Kan jag direkt komma åt DI-injektorn för att instansiera klasser med NestJS
Jag undrar om det finns ett sätt att få tillgång till DI-injektorn i NestJS och använda den direkt för att instansiera klasser. Detta skulle vara liknande hur Angular eller Inversify exponerar injektorn direkt för användning i speciella fall.
Flera leverantörer skulle lösa mitt användningsfall. Men tills flera leverantörer stöds i NestJS försöker jag istället skapa en kompositvakt som binder flera vakter tillsammans som en, så att jag kan använda flera vakter globalt som behöver tillgång till DI. Jag vill att kompositvakten bara ska ta emot en array av typer och sedan använda injektorn för att fylla i objekt för dessa typer.
Jag försökte specificera Injector som ett fabrikberoende, men Nest kunde inte lösa det. Och om det hade lösts vet jag inte hur jag skulle använda injektorn.
@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);
}
}
Svar #1
Jag gjorde något liknande nyligen, men istället för att skicka typer av vakter till min "multiguard" så instansierade jag vakterna i en fabrik och skickade en array med vaktinstanser istället.
Det är mycket likt sättet vakter körs på av NestJS. Ta en titt på filen 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;
}
}
I fabriken instansierar jag mina vakter. Om mina vakter har några beroenden kan jag lösa dem där, genom att specificera beroendena i Inject-arrayen. Den valfria inject-egenskapen accepterar en array av leverantörer som NestJS kommer att lösa och skicka som argument till fabriksfunktionen under instansieringsprocessen.
@Module({
providers: [
{
provide: APP_GUARD,
useFactory: (reflector: Reflector) => {
return new GuardsCollection([
new (AuthGuard('jwt'))(),
new RolesGuard(reflector),
]);
},
inject: [Reflector],
},
],
})
export class AuthModule {}
Ett annat möjligt alternativ är att injicera en ModuleRef och använda den för att lösa objekt inom kontexten av den aktuella modulen. Du bör också kunna hämta objekt från en annan modul så länge den andra modulen är importerad till den aktuella.
@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 {}
