NestJs: Dynamically Create Instances of Class

Viewed 8700

Instead of Singletons, I want to create dynamically class instances in NestJs.

I found two ways:

1) Directly create the class (ChripSensor is then not @Injectable)

import { ChirpSensor } from './chirp-sensor/chirp-sensor';

@Injectable()
export class SensorsService {
  registeredSensors: any;
  constructor(
    @InjectModel('Sensor') private readonly sensorModel: Model<ISensor>,
    private i2cService: I2cService) {
       const sensors = this.i2cService.getSensors();
       sensors.forEach((sensor) => {this.registeredSensors[sensor._id] = new ChirpSensor({name: sensor.name})});

    }

I'm wondering if that is consistent with the DI way of nest.js

2) The second solution would be via a factory, but here I don't know how to pass the options.

export const chirpFactory = {
  provide: 'CHIRP_SENSOR',
  useFactory: (options) => {
    console.log('USING FACTORY CHIRP, options', options)
    if (process.env.SIMULATION === 'true') {
      return new ChirpSensorMock(options);
    }
    else {
      return new ChirpSensor(options);
    }
  }
};

Not quite sure how to continue here/ inject the factory properly as the examples create the object in the constructor without options?

Question:

What is the NestJs way to create those class instances?

Edit - for B12Toastr

Module - get the Mock or Original on Compile time

providers: [
  {
    provide: 'CHIRP_SENSOR',
    useValue: process.env.SIMULATION === 'true'
      ? ChirpSensorMock
      : ChirpSensor
  },
],

Sensor Service

@Injectable()
export class SensorsService {
  registeredSensors: any;
  constructor(
    @Inject('CHIRP_SENSOR') private ChirpSensorClass: any, // any works but ChirpSensorMock | ChirpSensor not
    private i2cService: I2cService
   ) {
    const sensors = this.i2cService.getSensors();
    sensors.forEach((sensor) => {this.registeredSensors[sensor._id] = new ChirpSensorClass({name: sensor.name})});

  }
1 Answers

You can pass options to your factory via DI via useValue or useClass

providers: [
  {
    provide: MyOptions,
    useValue: options
  },
  {
    provide: 'CHIRP_SENSOR',
    useFactory: (options: MyOptions) => {
      console.log('USING FACTORY CHIRP, options', options);
      if (process.env.SIMULATION === 'true') {
        return new ChirpSensorMock(options);
      } else {
        return new ChirpSensor(options);
      }
    },
  },
],

Alternatively, you could also avoid using a factory altogether and make the decision which class to use at compile time via:

providers: [
  {
    provide: MyOptions,
    useValue: options
  },
  {
    provide: 'CHIRP_SENSOR',
    useValue: process.env.SIMULATION === 'true'
      ? ChirpSensorMock
      : ChirpSensor
  },
],

or simply:

providers: [
  {
    provide: MyOptions,
    useValue: options
  },
  {
    process.env.SIMULATION === 'true' ? ChirpSensorMock : ChirpSensor
  },
],

In case you are not using a factory as described above, you would then inject the options in your ChirpSensor (or the Mocked Sensor)` using typical constructor-based dependency injection:

@Injectable()
export class ChripSensor {
  constructor(@inject(MyOptions) private options: MyOptions) {
  }

  // ...
}

Depending on whether your options are wrapped in a class or a simple object you would either use useValue or useClass. With useClass you have to write less code and do not have to use the @Inject decorator since the class itself is used as DI token. However, it seems if MyOptions is a class, you do not need to use @Inject in any case to inject the dependency because NestJS uses the class as DI token, regardless whether you used useValue or useClass to provide the dependency...

Related