Dynamic module/service configuration and AOT

2020-02-26 10:14发布

问题:

I need to have some Angular services configured dynamically, depending on a runtime switch. In days before AOT, I got it to work using the following code:

@NgModule({
  imports: [HttpModule],
  providers: []
})
export class MyModule {
  static forRoot(config: MyConfiguration): ModuleWithProviders {
    return {
      ngModule: MyModule,
      providers: [
        SomeService,
        {
          provide: SomeOtherService,
          useFactory: (some: SomeService, http: Http) => {
            switch (config.type) {
              case 'cloud':
                return new SomeOtherService(new SomethingSpecificForCloud());
              case 'server':
                return new SomeOtherService(new SomethingSpecificForServer());
            }
          },
          deps: [SomeService, Http]
        },

      ]
    };
  }
}

Then in my AppModule I would import this as MyModule.forRoot(myConfig).

As I updated CLI and Angular, this no longer compiles, because it cannot be statically analyzed. I understand why, but I am still not sure what the correct way to solve it is.

Have I abused this forRoot() approach in the first place? How do you write modules so that depending on a runtime switch, they produce different services?

回答1:

I found one way to achieve it: Expose the configuration via a provider, then have injected to a "static" factory function. The code above would look like this:

// Necessary if MyConfiguration is an interface
export const MY_CONFIG = new OpaqueToken('my.config');

// Static factory function
export function someOtherServiceFactory(config: MyConfiguration,some: SomeService, http: Http) {
  switch (config.type) {
    case 'cloud':
      return new SomeOtherService(new SomethingSpecificForCloud());
    case 'server':
      return new SomeOtherService(new SomethingSpecificForServer());
  }
}

@NgModule({
  imports: [HttpModule],
  providers: []
})
export class MyModule {
  static forRoot(config: MyConfiguration): ModuleWithProviders {
    return {
      ngModule: MyModule,
      providers: [
        SomeService,
        { provide: MY_CONFIG, useValue: config },
        {
          provide: SomeOtherService,
          useFactory: someOtherServiceFactory,
          deps: [MY_CONFIG, SomeService, Http]
        },

      ]
    };
  }
}

It works and all, but I would still be very interested in knowing whether this is actually a good idea, or if I'm doing something terribly wrong and should be taking a completely different approach to solving this problem.


I found another solution:

  1. Use Angular CLI environments.
  2. Create an abstract classes/interfaces for the services with different implementations or dependencies for different environments.
  3. Export the right type from each enviromnent file (who said it has to only be a plain JS object?).
  4. In the module provider definition, import from the environment.
  5. At compile time, CLI environments will make the right thing get linked.

More information and sample project at my blog.