Robert Koritnik
Robert Koritnik

Reputation: 105029

Prevent direct module import in Angular

I have a feature module that declares my components and also has a static function that configures feature routing.

@NgModule({
  declarations: FEATURE_COMPONENTS,
  entryComponents: FEATURE_COMPONENTS,
  imports: [
    UIRouterModule,
    ...
  ]
})
export class FeatureModule {
  public static setParentState(stateName: string): ModuleWithProviders {
    ...
    return {
      ngModule: FeatureModule,
      providers: [
        ...UIRouterModule.forChild({ states: ... }).providers
        // my providers
        {
          provide: FEATURE_PROVIDER_TOKEN,
          useValue: ...
          multi: false
        }
      ]
    };
  }
}

My module on its own isn't enough to actually use the features, it also needs routing configuration which is done correctly through upper static function.

I would like to prevent this scenario:

@NgModule({
  ...
  imports: [
    // invalid import - I want to prevent this one
    FeatureModule
    // valid import
    FeatureModule.setParentState(...)
  ]
})
export class SomeAppModule {}

How can I throw some error when user tries to import my feature module directly as per above code? Correct import would be through calling the static function and not directly...

One of the possible things I might be able to use is the extra provider I'm adding my static function. But how? Via module constructor and its injections? Maybe via some custom decorator?

I was thinking of doing it this way somehow, but would also like to provide some custom error message with information how to correctly import the module:

@NgModule({ ... })
export class FeatureModule {
  public static setParentState(stateName: string): ModuleWithProviders {
    // FEATURE_PROVIDER_TOKEN defined here (see above)
    ...
  }

  constructor(
    @Import(FEATURE_PROVIDER_TOKEN) tokenTest: any
  } {
    // this will throw an error when provider isn't defined, but won't allow me to set specific error message
  }
}

Any ideas?

Upvotes: 1

Views: 1240

Answers (1)

Robert Koritnik
Robert Koritnik

Reputation: 105029

One possible and working solution

This is what I've implemented and it seems to satisfy the requirements:

  1. It doesn't allow importing a module directly
  2. It throws and error which describes correct usage

This is how it's implemented:

const PREVENT_DIRECT_IMPORT: InjectionToken<boolean> = new InjectionToken('FeatureModule:PREVENT_DIRECT_IMPORT');

@NgModule({
  declarations: FEATURE_COMPONENTS,
  entryComponents: FEATURE_COMPONENTS,
  imports: [ ... ],
  exports: [ ... ]
  providers: [{
    // erroneous provider when module is directly imported
    provide: PREVENT_DIRECT_IMPORT,
    useFactory: () => { throw new Error('Correct usage info'); },
    multi: false
  }]
})
export class FeatureModule {
  public static setParentState(stateName: string): ModuleWithProviders {
    ...
    return {
      ngModule: FeatureModule,
      providers: [
        ...UIRouterModule.forChild({ states: ... }).providers
        // my providers
        {
          provide: FEATURE_PROVIDER_TOKEN,
          useValue: ...
          multi: false
        }, {
          // this is the same provider when module is correctly imported
          provide: PREVENT_DIRECT_IMPORT,
          useValue: true,
          multi: false
        }
      ]
    };
  }

  constructor(
    @Import(PREVENT_DIRECT_IMPORT) prevent: boolean
  ) {
    // direct import will throw error when DI tries to get the injected provider
  }
}

Upvotes: 2

Related Questions