最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Inject correct service based on parameter - Stack Overflow

programmeradmin19浏览0评论

Let's assume I have a two modules which are exporting BService and CService where both of those services extends AService

So code looks like this:

abstract class AService {
    public run() {}
}

@Injectable()
export class BService extends AService {}

@Injectable()
export class CService extends AService {}

@Module({
    providers: [BService],
    exports: [BService],
})
export class BModule {}


@Module({
    providers: [CService],
    exports: [CService],
})
export class CModule {}

@Injectable()
class AppService {
    constructor(protected readonly service: AService) {}

    public run(context: string) { // let's assume context may be B or C
        this.service.run();
    }
}


@Module({
    imports: [CModule, BModule],
    providers: [{
        provide: AppService,
        useFactory: () => {
            return new AppService(); // how to use BService/CService depending on the context?
        }
    }]
})
export class AppModule {}

But the key is, I cannot use REQUEST (to inject it directly in useFactory) from @nestjs/core as I'm using this service in cron jobs and with the API call

I also don't think Factory pattern is useful there, I mean it would work but I want to do it correctly

I was thinking about property based injection.

But I'm not sure how to use it in my case

Let's assume I have a two modules which are exporting BService and CService where both of those services extends AService

So code looks like this:

abstract class AService {
    public run() {}
}

@Injectable()
export class BService extends AService {}

@Injectable()
export class CService extends AService {}

@Module({
    providers: [BService],
    exports: [BService],
})
export class BModule {}


@Module({
    providers: [CService],
    exports: [CService],
})
export class CModule {}

@Injectable()
class AppService {
    constructor(protected readonly service: AService) {}

    public run(context: string) { // let's assume context may be B or C
        this.service.run();
    }
}


@Module({
    imports: [CModule, BModule],
    providers: [{
        provide: AppService,
        useFactory: () => {
            return new AppService(); // how to use BService/CService depending on the context?
        }
    }]
})
export class AppModule {}

But the key is, I cannot use REQUEST (to inject it directly in useFactory) from @nestjs/core as I'm using this service in cron jobs and with the API call

I also don't think Factory pattern is useful there, I mean it would work but I want to do it correctly

I was thinking about property based injection.

But I'm not sure how to use it in my case

Share Improve this question edited Dec 5, 2019 at 13:19 Kim Kern 60.4k20 gold badges216 silver badges213 bronze badges asked Dec 5, 2019 at 12:02 hejkerooohejkerooo 7873 gold badges8 silver badges22 bronze badges 1
  • Check my answer below to see if it fits your needs. This sounds like a good place to use the strategy pattern. stackoverflow.com/a/69502460/8148483 – Francisco Garcia Commented Oct 10, 2021 at 0:10
Add a comment  | 

2 Answers 2

Reset to default 7

In my opinion, the factory approach is exactly what you need. You described that you need a different service based on the context which is a great for for the factory approach. Let's try this:

Create an injectable factory:

import { Injectable } from '@nestjs/common';
import { AService } from './AService';
import { BService } from './BService';
import { CService } from './CService';

@Injectable()
export class ServiceFactory {

    public getService(context: string) : AService {

        switch(context) {
            case 'a': return new BService();
            case 'b': return new CService();
            default: throw new Error(`No service defined for the context: "${context}"`);
        }
    }
}

Now import that factory into your app module:

import { ServiceFactory } from './ServiceFactory';
import { AService } from './AService';

@Module({
    providers: [AppService, ServiceFactory]
})
export class AppModule {}

Now your app service will get the factory as a dependency which will create the appropriate service based on the context:

import { ServiceFactory } from './ServiceFactory';
import { AService } from './AService';

@Injectable()
class AppService {

    constructor(readonly serviceFactory: ServiceFactory) { }

    public run(context: string) {
        const service: AService = this.serviceFactory.getService(context);
        service.run();
    }
}

If the property is static (e.g. environment variable), you can use a custom provider to choose the proper instance. However, if the property is in someway dynamic, you cannot soley rely on nest's dependency injection as it instantiates the provider on startup (with the exception of REQUEST scope, which isn't an option for you).

Static Property

Create a custom provider that instantiates the needed implementation based on a static property (e.g. environment variable).

{
  provide: AService,
  useClass: process.ENV.useBService ? BService : CService,
}

Dynamic Property with Request-Scope

Let's assume we have two different implementations of a service:

@Injectable()
export class BService {
  public count = 0;
  run() {
    this.count++;
    return 'B';
  }
}

@Injectable()
export class CService {
  public count = 0;
  run() {
    this.count++;
    return 'C';
  }
}

When the sum of the count variables of both is even, the BService should be used; CService when it's odd. For this, we create a custom provider with request scope.

{
  provide: 'MyService',
  scope: Scope.REQUEST,
  useFactory: (bService: BService, cService: CService) => {
    if ((bService.count + cService.count) % 2 === 0) {
      return bService;
    } else {
      return cService;
    }
  },
  inject: [BService, CService],
},

If our controller now injects the MyService token (@Inject('MyService')) and exposes its run method via an endpoint it will return B C B ...

Dynamic Property with Default-Scope

As we want to use the default scope (Singleton!), the static instantiation of nest's dependency injection cannot be used. Instead you can use the delegate pattern to select the wanted instance in the root class (AService in your example).

Provide all services as they are:

providers: [AService, BService, CService]

Decide dynamically in your AService which implementation to use:

@Injectable()
export class AService {
  constructor(private bService: BService, private cService: CService) {}

  run(dynamicProperty) {
    if (dynamicProperty === 'BService') {
      return this.bService.run();
    } else {
      return this.cService.run();
    }
  }
}
发布评论

评论列表(0)

  1. 暂无评论