Search code examples
angulartransloco

Combine common Transloco config with project-specific variable


I'm trying to implement Transloco in my Angular application that consists of a library and multiple SPA projects. I would like to have one global configuration class in the library project common and extend that with a production mode variable that the library does not have access to.

Currently, I have the following working configuration architecture:

transloco-common-config.module.ts (in common library):

@NgModule()
export class TranslocoCommonConfig implements TranslocoConfig {
    public defaultLang = 'en';
    public availableLangs: string[] = [];
    public reRenderOnLangChange = true;

    constructor(private localeService: LocaleService) {
        this.availableLangs = this.localeService.getAvailableLanguages();
    }
}

Which I can successfully use in an SPA project-specific module like this:

@NgModule({
    exports: [TranslocoModule],
    providers: [
        { provide: TRANSLOCO_CONFIG, useClass: TranslocoCommonConfig },
        { provide: TRANSLOCO_LOADER, useClass: TranslocoHttpLoader },
    ],
})
export class TranslocoRootModule {
}

The available languages are correctly loaded through LocaleService and Transloco works in the SPA project as expected.

To extend this configuration in the SPA project, my intuition is to use the forRoot() pattern. I modified the common configuration module into this:

class TranslocoCommonConfigOptions {
    productionMode = true;
}

@NgModule()
export class TranslocoCommonConfig implements TranslocoConfig {
    public defaultLang = 'en';
    public availableLangs: string[] = [];
    public reRenderOnLangChange = true;

    constructor(private localeService: LocaleService) {
        this.availableLangs = this.localeService.getAvailableLanguages();
    }

    static forRoot(options: TranslocoCommonConfigOptions): ModuleWithProviders<TranslocoCommonConfig> {
        return {
            ngModule: TranslocoCommonConfig,
            providers: [
                {
                    provide: TranslocoCommonConfigOptions,
                    useValue: options,
                },
            ],
        };
    }
}

To my understanding, this should be correct. However, I'm struggling to use this correctly in the SPA project. If I modify the TranslocoRootModule class to provide TRANSLOCO_CONFIG like this:

{ provide: TRANSLOCO_CONFIG, useClass: TranslocoCommonConfig.forRoot({ productionMode: true }) }

I get the following error:

TS2322: Type '{ provide: InjectionToken<TranslocoConfig>; useClass: ModuleWithProviders<TranslocoCommonConfig>; }' is not assignable to type 'Provider'.   Types of property 'useClass' are incompatible.     Type 'ModuleWithProviders<TranslocoCommonConfig>' is missing the following properties from type 'Type<any>': apply, call, bind, prototype, and 5 more.

How should I implement this with preferably minimal boilerplate code (I have five SPA projects and would like to have as little duplicate code as possible)?


Solution

  • I was able to solve this by creating two library modules for Transloco configuration.

    TranslocoCommonConfigModule is responsible for the actual configuration:

    export class TranslocoCommonConfigOptions {
        productionMode: boolean;
        loader: Type<TranslocoLoader>;
    }
    
    /**
     * Global configuration for the Transloco library. Accepts a `TranslocoCommonConfigOptions`
     * configuration object to dynamically set `TranslocoConfig.prodMode`.
     */
    @NgModule()
    export class TranslocoCommonConfigModule implements TranslocoConfig {
        // Static and global configuration:
        defaultLang = 'en';
        reRenderOnLangChange = true;
    
        // Dynamic configuration:
        availableLangs: string[] = [];
        prodMode = true;
    
        constructor(private localeService: LocaleService, config: TranslocoCommonConfigOptions) {
            this.availableLangs = this.localeService.getAvailableLanguages();
            this.prodMode = config.productionMode;
        }
    }
    

    While TranslocoCommonModule is resposible for injecting the required service and configuration object via the forRoot method:

    /**
     * Common module for importing Transloco configuration in frontends with minimal boilerplate.
     *
     * Uses the `forRoot()` paradigm to inject `environment.production` dynamically from a frontend along with
     * a frontend-specific translation loader. The rest of `TranslocoConfig` options are global and provided by
     * `TranslocoCommonConfigModule`.
     */
    @NgModule()
    export class TranslocoCommonModule {
        static forRoot(options: TranslocoCommonConfigOptions): ModuleWithProviders<TranslocoCommonModule> {
            return {
                ngModule: TranslocoCommonModule,
                providers: [
                    {
                        provide: TRANSLOCO_CONFIG,
                        useFactory: (localeService: LocaleService) =>
                            new TranslocoCommonConfigModule(localeService, options),
                        deps: [LocaleService],
                    },
                    {
                        provide: TRANSLOCO_LOADER,
                        useClass: options.loader,
                    },
                ],
            };
        }
    }
    

    After implementing a frontend-specific translation file loader, the configuration can be imported in a module like so:

    @NgModule({
        imports: [
            TranslocoCommonModule.forRoot({
                productionMode: environment.production,
                loader: TranslocoHttpLoader,
            }),
        ],
    })