Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I'm wondering what would be the best way to set up configurable modules in angular2. In angular1 this was done usually via providers. With them being changed quite a bit, how would you go about passing config parameters to reusable ng2 modules/directives/components?

An ng1 example:

// configuring a (third party) module    
.config(function (angularPromiseButtonsProvider) {
  angularPromiseButtonsProvider.extendConfig({
    spinnerTpl: '<div class="other-class"></span>',
    disableBtn: false
  });
});

 // setting up the provider
.provider('angularPromiseButtons', function angularPromiseButtonsProvider() {
    var config = {
        spinnerTpl: '<span class="btn-spinner"></span>',
        priority: 0,
        disableBtn: true,
    };

    return {
        extendConfig: function(newConfig) {
            config = angular.extend(config, newConfig);
        },

        $get: function() {
            return {
                config: config
            };
        }
    };
})

// using the result in the directive, etc.
.directive('promiseBtn', function(angularPromiseButtons){
    var config = angularPromiseButtons.config;
})

This is basically the same question as this one but directed at angular2.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
129 views
Welcome To Ask or Share your Answers For Others

1 Answer

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 {}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...