diff --git a/README.md b/README.md index 0a80507..4447ece 100644 --- a/README.md +++ b/README.md @@ -415,6 +415,37 @@ Param | Type | Details **Constructor** | *Function* | A constructor function that will be instantiated as a singleton. **dependency**
*(optional)* | *String* | An optional name for a dependency to be passed to the constructor. A dependency will be passed to the constructor for each name passed to `Bottle#service` in the order they are listed. +#### serviceFactory(name, factoryService [, dependency [, ...]]) + +Used to register a service factory function. Works exactly like `factory` except the factory arguments will be injected instead of receiving the `container`. This is useful when implementing the Module Pattern or adding dependencies to your Higher Order Functions. + +```js +function createApiActions(axios) { + return { + createUser: function(user) { + return axios.post('/users', user); + } + }; +} + +var bottle = new Bottle(); + +bottle.serviceFactory('api', createApiActions, 'axios'); +bottle.factory('axios', function() { + return axios.create({baseURL: 'http://api.mydomain'}); +}); + +bottle.container.api.createUser({name: "BottleJS"}); +``` + +If `Bottle.config.strict` is set to `true`, this method will throw an error if an injected dependency is `undefined`. + +Param | Type | Details +:--------------------------------|:-----------|:-------- +**name** | *String* | The name of the service. Must be unique to each Bottle instance. +**serviceFactory** | *Function* | A function that will be invoked to create the service object/value. +**dependency**
*(optional)* | *String* | An optional name for a dependency to be passed to the service function. A dependency will be passed to the service function for each name passed to `Bottle#serviceFn` in the order they are listed. + #### value(name, val) Used to add an arbitrary value to the container. diff --git a/dist/bottle.d.ts b/dist/bottle.d.ts index 36905c1..29f8b40 100644 --- a/dist/bottle.d.ts +++ b/dist/bottle.d.ts @@ -80,6 +80,12 @@ declare class Bottle { service(name: string, Constructor: ((...any: any[]) => any), ...dependency: string[]): this; service(name: string, Constructor: new (...any: any[]) => T, ...dependency: string[]): this; + /** + * Register a service function. If Bottle.config.strict is set to true, this method will throw an error if an injected dependency is undefined. + */ + serviceFactory(name: string, factoryService: ((...any: any[]) => any), ...dependency: string[]): this; + serviceFactory(name: string, factoryService: ((...any: any[]) => T), ...dependency: string[]): this; + /** * Add an arbitrary value to the container. */ diff --git a/src/Bottle/service-factory.js b/src/Bottle/service-factory.js new file mode 100644 index 0000000..6a9e410 --- /dev/null +++ b/src/Bottle/service-factory.js @@ -0,0 +1,6 @@ +/** + * Register a function service + */ +var serviceFactory = function serviceFactory(name, factoryService) { + return createService.apply(this, [name, factoryService, false].concat(slice.call(arguments, 2))); +}; diff --git a/src/Bottle/service.js b/src/Bottle/service.js index c7cd56b..2df7513 100644 --- a/src/Bottle/service.js +++ b/src/Bottle/service.js @@ -1,20 +1,31 @@ /** - * Register a service inside a generic factory. + * Register a class service * * @param String name * @param Function Service * @return Bottle */ var service = function service(name, Service) { - var deps = arguments.length > 2 ? slice.call(arguments, 2) : null; + return createService.apply(this, [name, Service, true].concat(slice.call(arguments, 2))); +}; + +/** + * Private helper for creating service and service factories. + * + * @param String name + * @param Function Service + * @return Bottle + */ +var createService = function createService(name, Service, isClass) { + var deps = arguments.length > 3 ? slice.call(arguments, 3) : []; var bottle = this; return factory.call(this, name, function GenericFactory() { - var ServiceCopy = Service; - if (deps) { - var args = deps.map(getNestedService, bottle.container); - args.unshift(Service); - ServiceCopy = Service.bind.apply(Service, args); + var serviceFactory = Service; // alias for jshint + var args = deps.map(getNestedService, bottle.container); + + if (!isClass) { + return serviceFactory.apply(null, args); } - return new ServiceCopy(); + return new (Service.bind.apply(Service, [null].concat(args)))(); }); }; diff --git a/src/api.js b/src/api.js index 0cd2923..dd47b10 100644 --- a/src/api.js +++ b/src/api.js @@ -41,6 +41,7 @@ Bottle.prototype = { register : register, resolve : resolve, service : service, + serviceFactory : serviceFactory, value : value }; diff --git a/test/spec/service-factory.spec.js b/test/spec/service-factory.spec.js new file mode 100644 index 0000000..941fd80 --- /dev/null +++ b/test/spec/service-factory.spec.js @@ -0,0 +1,24 @@ +/* globals Bottle */ +;(function() { + 'use strict'; + + /** + * Bottle Factory test suite + */ + describe('Bottle#serviceFactory', function() { + it('injects dependencies to a service factory', function() { + var b = new Bottle(); + var createThing = function(foo, bar) { + return {foo: foo, bar: bar}; + }; + b.serviceFactory('Thing', createThing, 'foo', 'bar'); + b.service('foo', function() { this.name = 'foo'; }); + b.value('bar', 'bippity'); + + expect(b.container.Thing).toBeDefined(); + expect(b.container.Thing.foo).toBeDefined(); + expect(b.container.Thing.foo.name).toBe('foo'); + expect(b.container.Thing.bar).toBe('bippity'); + }); + }); +}());