最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How should I make configurable modules in Angular2 - Stack Overflow

programmeradmin0浏览0评论

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.

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.

Share Improve this question edited Feb 14, 2018 at 5:27 Estus Flask 222k78 gold badges469 silver badges605 bronze badges asked May 6, 2017 at 23:28 hugo der hungrigehugo der hungrige 12.9k9 gold badges59 silver badges86 bronze badges 5
  • 1 Can you expand your question? You shouldn't have to read another question just to figure out what you're asking here. You can continue to refer to the other question for additional context, but this question should be able to stand on its own – snorkpete Commented May 6, 2017 at 23:46
  • 1 I really beginn to hate this community here that it is so hostile towards new questions. It's really annoying. @snorkpete Thanks for pointing that out. It's a valid point. – hugo der hungrige Commented May 6, 2017 at 23:51
  • 2 it's the reason why i commented rather than voted - to allow you to update your question instead of jumping down your throat. I would say that the community does provide great content overall, even if the means used to get there isn't always the most friendly. Such is life with a big enough community – snorkpete Commented May 7, 2017 at 0:05
  • 2 Providing some simple code of what exactly is your case would help. If you're not sure what you're trying to do in A2, it also makes sense provide A1 code as an example. It's a good strategy to keep the question constructive and not get 'too broad' close votes or downvotes. – Estus Flask Commented May 7, 2017 at 0:08
  • 2 And dude, you have a high enough reputation to know all that already. The standard of questions is pretty dire right now. Lead by example. – Mike Chamberlain Commented May 7, 2017 at 5:57
Add a comment  | 

1 Answer 1

Reset to default 23

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 {}
发布评论

评论列表(0)

  1. 暂无评论