There are several recipes, which can be used separately or together.
Configuration service
Having a service to provide necessary configuration in key/value form is usually desirable.
There may be more than one configuration service to configure one application entity, e.g. someConfig
for generic user-defined configuration, and someDefaultConfig
for all default values that are supposed to be possibly changed. For example, someConfig
may contain auth credentials that are always user-defined, and someDefaultConfig
may contain default hook callbacks, deep settings for auth providers, etc. The most simple way to implement this is to merge configuration objects with Object.assign
.
Mandatory configuration service
A recipe that requires the user to define configuration service explicitly, it basically uses DI to designate that some module won't work without proper configuration.
AngularJS
// third-party module
// will fail if someConfig wasn't defined by the user
angular.module('some', []).factory('someService', (someConfig) => { ... })
// user-defined module
angular.module('app', ['some']).constant('someConfig', { foo: 'foo' });
Angular
// third-party module
export const SOME_CONFIG = new InjectionToken('someConfig');
@Injectable
class SomeService {
constructor(@Inject(SOME_CONFIG) someConfig) { ... }
}
@NgModule({ providers: [SomeService] })
export class SomeModule {}
// user-defined module
@NgModule({
imports: [SomeModule],
providers: [{ provide: SOME_CONFIG, useValue: { foo: 'foo' } }]
)
export class AppModule {}
Optional configuration service with overridable empty value
This is a slight variation of the previous recipe, the only difference is that there is empty default value that won't make the application to fail if configuration service wasn't defined by a user:
AngularJS
// third-party module
angular.module('some', [])
.constant('someConfig', {})
...
Angular
// third-party module
@NgModule({ providers: [..., { provide: SOME_CONFIG, useValue: {} }] })
export class SomeModule {}
...
Optional configuration service
Alternatively, configuration service can be made totally optional for injection.
AngularJS
// third-party module
angular.module('some', []).factory('someService', ($injector) => {
const someConfig = $injector.has('someConfig') ? $injector.get('someConfig') : {};
...
})
...
Angular
// third-party module
export const SOME_CONFIG = new InjectionToken('someConfig');
@Injectable
class SomeService {
constructor(@Inject(SOME_CONFIG) @Optional() someConfig) {
this.someConfig = someConfig !== null ? someConfig : {};
...
}
}
@NgModule({ providers: [SomeService] })
export class SomeModule {}
...
forRoot method
forRoot
static module method is a convention that is followed by Angular router module and numerous third-party modules. As explained in the guide, the method returns an object that implements ModuleWithProviders
.
Basically it gives an opportunity to define module providers dynamically, based on forRoot(...)
arguments. This can be considered an alternative to AngularJS config
and provider
units that don't exist in Angular.
AngularJS
// third-party module
angular.module('some', [])
.constant('someDefaultConfig', { bar: 'bar' })
.provider('someService', function (someDefaultConfig) {
let someMergedConfig;
this.configure = (config) => {
someMergedConfig = Object.assign({}, someDefaultConfig, config);
};
this.$get = ...
});
// user-defined module
angular.module('app', ['some']).config((someServiceProvider) => {
someServiceProvider.configure({ foo: 'foo' });
});
Angular
// third-party module
export const SOME_CONFIG = new InjectionToken('someConfig');
export const SOME_DEFAULT_CONFIG = new InjectionToken('someDefaultConfig');
@Injectable
class SomeService {
constructor(
@Inject(SOME_CONFIG) someConfig,
@Inject(SOME_DEFAULT_CONFIG) someDefaultConfig
) {
this.someMergedConfig = Object.assign({}, someDefaultConfig, someConfig);
...
}
}
@NgModule({ providers: [
SomeService,
{ provide: SOME_DEFAULT_CONFIG, useValue { bar: 'bar' } }
] })
export class SomeModule {
static forRoot(config): ModuleWithProviders {
return {
ngModule: SomeModule,
providers: [{ provide: SOME_CONFIG, useValue: config }]
};
}
}
// user-defined module
@NgModule({ imports: [SomeModule.forRoot({ foo: 'foo' })] })
export class AppModule {}
APP_INITIALIZER multi-provider
Angular APP_INITIALIZER
multi-provider allows to provide asynchronous initialization routines for the application.
APP_INITIALIZER
shares some similarities with AngularJS config phase. APP_INITIALIZER
routines are susceptible to race conditions, similarly to config
and run
blocks in AngularJS. For instance, Router
is available for injection in root component but not in APP_INITIALIZER
, due to circular dependency on another APP_INITIALIZER
.
Synchronous initialization routine
AngularJS
...
// user-defined module
angular.module('app', ['some']).config((someServiceProvider) => {
someServiceProvider.configure({ foo: 'foo' });
});
Angular
...
// user-defined module
export function someAppInitializer(someService: SomeService) {
return () => {
someService.configure({ foo: 'foo' });
};
}
@NgModule({
imports: [SomeModule],
providers: [{
provide: APP_INITIALIZER,
multi: true,
useFactory: someAppInitializer,
deps: [SomeService]
}]
})
export class AppModule {}
Asynchronous initialization routine
Initialization may involve fetching configuration from remote source to configure services; something that is not possible with single AngularJS application. This requires to have another application that initializes and bootstraps main module. This scenario is naturally handled by APP_INITIALIZER
.
AngularJS
...
// user-defined module
angular.module('app', ['some']);
angular.module('appInitializer', [])
.factory('initializer', ($document, $http) => {
return $http.get('data.json')
.then((result) => result.data)
.then((data) => {
$document.ready(() => {
angular.bootstrap($document.find('body'), ['app', (someServiceProvider) => {
someServiceProvider.configure(data);
}]);
});
});
});
angular.injector(['ng', 'appInitializer'])
.get('initializer')
.catch((err) => console.error(err));
Angular
...
// user-defined module
export function someAppInitializer(http: HttpClient, someService: SomeService) {
return () => {
return http.get('data.json').toPromise()
.then(data => {
someService.configure(data);
});
};
}
@NgModule({
imports: [SomeModule],
providers: [{
provide: APP_INITIALIZER,
multi: true,
useFactory: someAppInitializer,
deps: [HttpClient, SomeService]
}]
})
export class AppModule {}