I am trying to find an elegant way to inject an Interface into the tsyringe container based on a given value. (string/enum/Symbol)

This is how I would do it with a switch..case clause:

interface IColor{
  someMethod():void;
  readonly someInfo:any;
}

class Blue implements IColor{
  constructor() {
  }
  someMethod() {
    console.log('I am blue');
  }
}
class Red implements IColor{
  constructor() {
  }
  someMethod() {
    console.log('I am red');
  }
}
@injectable()
class SurfaceService{
  constructor(@inject('IColor')readonly color:IColor) {
  }
}

// main
import {container} from "tsyringe";
const mainFunction = (someData:{colorinfo:string}) =>{

  switch (someData.colorinfo){
    case 'RED':
      container.register("IColor", {useClass: Red});
      break;

    case 'Blue':
      container.register("IColor", {useClass: Blue});
      break;
  }

  const service = container.resolve(SurfaceService); 
}

The problem with this approach is, that it is not very elegant and it requires to import all possible implementation even though, only one is needed at runtime.

Is there a better solution for this?

2

There are 2 best solutions below

1
On BEST ANSWER

Here is a gist which was part of my research of DI solutions.

Apparently one has to use predicateawareclassfactory. But I've found that a simpler version, that worked just fine ¯\_(ツ)_/¯

container.register(Tokens.logger, {
  useFactory: () => {
    return process.env.NODE_ENV === "production"
      ? container.resolve(PinoLogger)
      : container.resolve(ConsoleLogger)
  },
})
3
On

Disclaimer: author here

Here is a solution, but in different DI framework.

I think this is the only DI solution that supports async lazy imports to avoid importing things you don't need at runtime. Any other DI system would have to support async. To my best knowledge, no other does at the time of writing. I've checked around 30.

Interactive working demo

Di code example

  // Business logic
  interface IColor {
    someMethod(): void
    // readonly someInfo:any;
  }

  export class Blue implements IColor {
    constructor() {}
    someMethod() {
      console.log("I am blue")
    }
  }
  export class Red implements IColor {
    constructor() {}
    someMethod() {
      console.log("I am red")
    }
  }
  export class SurfaceService {
    constructor(readonly color: IColor) {}
  }


  // main
  export const container = createContainer()
    .add(() => ({
      blue: async () => {
        const { Blue } = await import("./_0.business-logic")
        return new Blue()
      },
      red: async () => {
        const { Red } = await import("./_0.business-logic")
        return new Red()
      },
    }))
    .add((ctx) => ({
      surfaceService: async () => {
        // @ts-ignore
        if (process?.env?.NODE_ENV === "production") {
          return await ctx.blue
        } else {
          return await ctx.red
        }
      },
      getCustomColor: () => async (someData: { colorInfo: string }) => {
        if (someData.colorInfo === "RED") {
          return await ctx.red
        }
        return await ctx.blue
      },
    }))

  // usage
  const colorByEnv = await container.items.surfaceService // Blue or Red
  const red = await container.items.getCustomColor({ colorInfo: "RED" })