Search code examples
typescriptdependency-injectiondependenciestokennestjs

Is there any way to avoid the predeclaration of injection token that would be declared in other modules?


I am creating an EventPublisher that must take 3 dependencies in the constructor

Publisher requiring the dependencies


@Injectable()
export class EventStorePublisher implements IEventPublisher {
    constructor(private readonly eventStore: IEventStore,
                @Inject('STREAM_NAME_PUBLISHER') private readonly category: string,
                @Inject('ID_ATTRIBUTE_NAME_PUBLISHER') private readonly idName: string) {
    }
}

This way I want to customize the publishing.

It is a part of a util module that does not know at the moment which string inject inside:

Util module

@Global()
@Module({
    providers: [
        {
            provide: IEventStore,
            useClass: EventStore,
        },
//...
    ],
    exports: [
        {
            provide: IEventStore,
            useClass: EventStore,
        },
//...
    ],
})
export class EventStoreModule {
//...

    static forFeature(): DynamicModule {
        return {
            module: EventStoreModule,
            providers: [
                EventStorePublisher,
                {
                    provide: "STREAM_NAME_PUBLISHER",
                    useValue: null
                },
                {
                    provide: "ID_ATTRIBUTE_NAME_PUBLISHER",
                    useValue: null
                },
            ],
            exports: [
                EventStorePublisher,
                {
                    provide: "STREAM_NAME_PUBLISHER",
                    useValue: null
                },
                {
                    provide: "ID_ATTRIBUTE_NAME_PUBLISHER",
                    useValue: null
                },

            ],
        };
    }
}

I didn't find any other way to prevent NestJS to throw error about missing definitions for the tokens STREAM_NAME_PUBLISHER or ID_ATTRIBUTE_NAME_PUBLISHER. I am forced to provide them and to export them, otherwise the module does not work.

When it comes to a Module that needs to customize the category and the idName :

Final module that will customize my tokens

@Module({
    imports: [
        CqrsModule,
        EventStoreModule.forFeature(),
        EventStorePublisher,
    ],
    providers: [
        //...
        {
            provide: "STREAM_NAME_PUBLISHER",
            useValue: "person"
        },
        {
            provide: "ID_ATTRIBUTE_NAME_PUBLISHER",
            useValue: "personId"
        },
        {
            provide: EventStorePublisher,
            useFactory: (eventStore:IEventStore,category: string, id: string) => new EventStorePublisher(eventStore, category,id),
            inject: [IEventStore, "STREAM_NAME_PUBLISHER", "ID_ATTRIBUTE_NAME_PUBLISHER"]
        },

    ],
    controllers: [
        //...
    ],
})
export class PersonModule implements OnModuleInit {
//...
}

Is there any way to avoid the predeclaration of with null in the EventStoreModule ? I want these tokens to be always overriden for each module using this EventStoreModule and the idea I come up with seems very very very ugly.


Solution

  • I've implemented something kind of similar to this, but overall, it's definitely pretty messy to do. For my code, I ended up implementing a NoopService kind of approach, where the code is as minimal as possible and does nothing but let things pass through. All of my providers that were to be overwritten were initialized with

    {
      provide: ProviderName,
      useClass: NoopService,
    }
    

    So that I could just let the providers properly initalise without worrying about any code running through them. This also lead to needing a lot of checks around the code as I wouldn't want these NoopServices actually doing anything. If you want to check it out the codebase is here, specifically in the ogma-core.module.ts, ogma.providers.ts, and interceptor/ogma.intereptor.ts files (along with those you can follow from there).