diff --git a/README.md b/README.md
index 9398681..b0356bf 100644
--- a/README.md
+++ b/README.md
@@ -381,11 +381,14 @@ Param | Type | Details
**Provider** | *Function* | A constructor function that will be instantiated as a singleton. Should expose a function called `$get` that will be used as a factory to instantiate the service.
#### resetProviders(names)
-Param | Type | Details
-:--------------------------|:-----------|:--------
-**names**
*(optional)*| *Array* | An array of strings which contains names of the providers to be reset.
+Param | Type | Details
+:------------------------------|:-----------|:--------
+**names**
*(optional)* | *Array* | An array of strings which contains names of the providers to be reset.
+**propagate**
*(optional)*| *Boolean* | Propagate the reset to all providers that depend on the previous list.
+
+Used to reset providers for the next reference to re-instantiate the provider.
-Used to reset providers for the next reference to re-instantiate the provider. If `names` param is passed, will reset only the named providers.
+If `names` param is passed, will reset only the named providers. When reseting an specific list of providers, it is possible to also propagate the reset to providers that depend on those.
#### register(Obj)
#### container.$register(Obj)
diff --git a/grunt/config/grunt-contrib-jasmine.json b/grunt/config/grunt-contrib-jasmine.json
index a29fa9d..c0f339f 100644
--- a/grunt/config/grunt-contrib-jasmine.json
+++ b/grunt/config/grunt-contrib-jasmine.json
@@ -1,5 +1,9 @@
{
"jasmine" : {
+ "options": {
+ "version": "3.8.0",
+ "noSandbox": true
+ },
"tests" : {
"src" : "dist/bottle.js",
"options" : {
diff --git a/src/Bottle/middleware.js b/src/Bottle/middleware.js
index 56c75de..8e41312 100644
--- a/src/Bottle/middleware.js
+++ b/src/Bottle/middleware.js
@@ -8,14 +8,24 @@
* @return void
*/
var applyMiddleware = function applyMiddleware(middleware, name, instance, container) {
+ var bottle = this;
var descriptor = {
configurable : true,
- enumerable : true
- };
- if (middleware.length) {
- descriptor.get = function getWithMiddlewear() {
- var index = 0;
- var next = function nextMiddleware(err) {
+ enumerable : true,
+ get : function getWithMiddlewear() {
+ var captureTarget,serviceDependents, index, next;
+ if (bottle.capturingDepsOf.length) {
+ captureTarget = bottle.capturingDepsOf[bottle.capturingDepsOf.length - 1];
+ serviceDependents = bottle.dependents[name] = bottle.dependents[name] || [];
+ if (serviceDependents.indexOf(captureTarget) === -1) {
+ serviceDependents.push(captureTarget);
+ }
+ }
+ if (!middleware.length) {
+ return instance;
+ }
+ index = 0;
+ next = function nextMiddleware(err) {
if (err) {
throw err;
}
@@ -25,11 +35,8 @@ var applyMiddleware = function applyMiddleware(middleware, name, instance, conta
};
next();
return instance;
- };
- } else {
- descriptor.value = instance;
- descriptor.writable = true;
- }
+ },
+ };
Object.defineProperty(container, name, descriptor);
diff --git a/src/Bottle/provider.js b/src/Bottle/provider.js
index 2b11aea..69b1454 100644
--- a/src/Bottle/provider.js
+++ b/src/Bottle/provider.js
@@ -28,9 +28,9 @@ var getWithGlobal = function getWithGlobal(collection, name) {
* @return Bottle
*/
var createProvider = function createProvider(name, Provider) {
- var providerName, properties, container, id, decorators, middlewares;
+ var bottle, providerName, properties, container, decorators, middlewares;
- id = this.id;
+ bottle = this;
container = this.container;
decorators = this.decorators;
middlewares = this.middlewares;
@@ -55,14 +55,18 @@ var createProvider = function createProvider(name, Provider) {
var provider = container[providerName];
var instance;
if (provider) {
+ bottle.capturingDepsOf.push(name);
// filter through decorators
instance = getWithGlobal(decorators, name).reduce(reducer, provider.$get(container));
+ bottle.capturingDepsOf.pop();
delete container[providerName];
delete container[name];
}
- return instance === undefined ? instance : applyMiddleware(getWithGlobal(middlewares, name),
- name, instance, container);
+ if (instance === undefined) {
+ return instance;
+ }
+ return applyMiddleware.call(bottle, getWithGlobal(middlewares, name), name, instance, container);
}
};
diff --git a/src/Bottle/reset-providers.js b/src/Bottle/reset-providers.js
index 9775b1a..634021c 100644
--- a/src/Bottle/reset-providers.js
+++ b/src/Bottle/reset-providers.js
@@ -5,30 +5,58 @@
* @return void
*/
var removeProviderMap = function resetProvider(name) {
+ var parts = splitHead(name);
+ if (parts.length > 1) {
+ removeProviderMap.call(getNestedBottle.call(this, parts[0]), parts[1]);
+ }
delete this.providerMap[name];
delete this.container[name];
delete this.container[name + PROVIDER_SUFFIX];
};
+/**
+ * Clears a reseted service from the dependencies tracker.
+ *
+ * @param String name
+ * @return void
+ */
+var removeFromDeps = function removeFromDeps(name) {
+ var parts = splitHead(name);
+ if (parts.length > 1) {
+ removeFromDeps.call(getNestedBottle.call(this, parts[0]), parts[1]);
+ }
+ Object.keys(this.dependents).forEach(function clearDependents(serviceName) {
+ if (this.dependents[serviceName]) {
+ this.dependents[serviceName] = this.dependents[serviceName]
+ .filter(function (dependent) { return dependent !== name; });
+ }
+ }, this);
+};
+
/**
* Resets providers on a bottle instance. If 'names' array is provided, only the named providers will be reset.
*
* @param Array names
+ * @param Boolean [propagate]
* @return void
*/
-var resetProviders = function resetProviders(names) {
- var tempProviders = this.originalProviders;
+var resetProviders = function resetProviders(names, propagate) {
var shouldFilter = Array.isArray(names);
-
Object.keys(this.originalProviders).forEach(function resetProvider(originalProviderName) {
if (shouldFilter && names.indexOf(originalProviderName) === -1) {
return;
}
- var parts = originalProviderName.split(DELIMITER);
+ var parts = splitHead(originalProviderName);
if (parts.length > 1) {
- parts.forEach(removeProviderMap, getNestedBottle.call(this, parts[0]));
+ resetProviders.call(getNestedBottle.call(this, parts[0]), [parts[1]], propagate);
+ }
+ if (shouldFilter && propagate && this.dependents[originalProviderName]) {
+ this.resetProviders(this.dependents[originalProviderName], propagate);
+ }
+ if (shouldFilter) {
+ removeFromDeps.call(this, originalProviderName);
}
removeProviderMap.call(this, originalProviderName);
- this.provider(originalProviderName, tempProviders[originalProviderName]);
+ this.provider(originalProviderName, this.originalProviders[originalProviderName]);
}, this);
};
diff --git a/src/Bottle/service.js b/src/Bottle/service.js
index a2ffe09..5c8db59 100644
--- a/src/Bottle/service.js
+++ b/src/Bottle/service.js
@@ -8,6 +8,12 @@
var createService = function createService(name, Service, isClass) {
var deps = arguments.length > 3 ? slice.call(arguments, 3) : [];
var bottle = this;
+ deps.forEach(function registerDependents(otherService) {
+ var serviceDependents = bottle.dependents[otherService] = bottle.dependents[otherService] || [];
+ if (serviceDependents.indexOf(name) === -1) {
+ serviceDependents.push(name);
+ }
+ });
return factory.call(this, name, function GenericFactory() {
var serviceFactory = Service; // alias for jshint
var args = deps.map(getNestedService, bottle.container);
diff --git a/src/api.js b/src/api.js
index 9e846f6..4464348 100644
--- a/src/api.js
+++ b/src/api.js
@@ -11,7 +11,9 @@ Bottle = function Bottle(name) {
this.id = id++;
+ this.capturingDepsOf = [];
this.decorators = {};
+ this.dependents = {};
this.middlewares = {};
this.nested = {};
this.providerMap = {};
diff --git a/src/globals.js b/src/globals.js
index 47cb291..027bbe0 100644
--- a/src/globals.js
+++ b/src/globals.js
@@ -69,3 +69,14 @@ var getNestedBottle = function getNestedBottle(name) {
var getNestedService = function getNestedService(fullname) {
return fullname.split(DELIMITER).reduce(getNested, this);
};
+
+/**
+ * Split a dot-notation string on head segment and rest segment.
+ *
+ * @param String fullname
+ * @return Array
+ */
+var splitHead = function splitHead(fullname) {
+ var parts = fullname.split(DELIMITER);
+ return parts.length > 1 ? [parts[0], parts.slice(1).join(DELIMITER)] : [parts[0]];
+};
diff --git a/test/spec/provider.spec.js b/test/spec/provider.spec.js
index 5efdeb6..51e57c0 100644
--- a/test/spec/provider.spec.js
+++ b/test/spec/provider.spec.js
@@ -189,16 +189,88 @@
expect(i).toEqual(2);
expect(j).toEqual(1);
});
- it('allows for sub containers to re-initiate as well', function() {
+ it('allows to propagate the reset to the providers that depend on the selected service names', function() {
+ var i = 0;
+ var j = 0;
+ var k = 0;
+ var l = 0;
+ var b = new Bottle();
+ var FirstService= function() { i = ++i; };
+ var SecondProvider = function() {
+ j = ++j;
+ this.$get = function(container) { return { _first: container.First }; };
+ };
+ var ThirdFactory = function(container) { k = ++k; return { _first: container.First }; };
+ b.factory('Zero', function () { l = ++l; return 0; });
+ b.service('First', FirstService, 'Zero');
+ b.provider('Second', SecondProvider);
+ b.factory('Third', ThirdFactory);
+ expect(b.container.Second._first instanceof FirstService).toBe(true);
+ expect(b.container.Third._first instanceof FirstService).toBe(true);
+ expect(i).toEqual(1);
+ expect(j).toEqual(1);
+ expect(k).toEqual(1);
+ expect(l).toEqual(1);
+ b.resetProviders(['First'], true);
+ expect(b.container.Second._first instanceof FirstService).toBe(true);
+ expect(b.container.Third._first instanceof FirstService).toBe(true);
+ expect(i).toEqual(2);
+ expect(j).toEqual(2);
+ expect(k).toEqual(2);
+ expect(l).toEqual(1);
+ b.resetProviders(['Zero'], true);
+ expect(b.container.Second._first instanceof FirstService).toBe(true);
+ expect(b.container.Third._first instanceof FirstService).toBe(true);
+ expect(i).toEqual(3);
+ expect(j).toEqual(3);
+ expect(k).toEqual(3);
+ expect(l).toEqual(2);
+ });
+ it('will cleanup service dependents if a service is redefined', function() {
+ var i = 0;
+ var j = 0;
+ var k = 0;
+ var b = new Bottle();
+ var FirstService= function() { i = ++i; };
+ var SecondService = function() { j = ++j; };
+ var ThirdService = function() { k = ++k; };
+ b.service('First', FirstService);
+ b.service('Thing.Second', SecondService, 'First');
+ b.service('Third', ThirdService, 'Thing.Second');
+ expect(b.container.First instanceof FirstService).toBe(true);
+ expect(b.container.Thing.Second instanceof SecondService).toBe(true);
+ expect(b.container.Third instanceof ThirdService).toBe(true);
+ expect(i).toEqual(1);
+ expect(j).toEqual(1);
+ expect(k).toEqual(1);
+ b.resetProviders(['Thing.Second'], true);
+ // Redefined to have no dependencies
+ b.service('Thing.Second', SecondService);
+ expect(b.container.First instanceof FirstService).toBe(true);
+ expect(b.container.Thing.Second instanceof SecondService).toBe(true);
+ expect(b.container.Third instanceof ThirdService).toBe(true);
+ expect(i).toEqual(1);
+ expect(j).toEqual(2);
+ expect(k).toEqual(2);
+ // No propagation will happen given that no service depends on First anymore
+ b.resetProviders(['First'], true);
+ expect(b.container.First instanceof FirstService).toBe(true);
+ expect(b.container.Thing.Second instanceof SecondService).toBe(true);
+ expect(b.container.Third instanceof ThirdService).toBe(true);
+ expect(i).toEqual(2);
+ expect(j).toEqual(2);
+ expect(k).toEqual(2);
+ });
+ it('allows for deep sub containers to re-initiate as well', function() {
var i = 0;
var b = new Bottle();
var ThingProvider = function() { i = ++i; this.$get = function() { return this; }; };
- b.provider('Thing.Something', ThingProvider);
- expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
+ b.provider('Thing.In.Something', ThingProvider);
+ expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
// Intentionally calling twice to prove the construction is cached until reset
- expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
+ expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
b.resetProviders();
- expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
+ expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
expect(i).toEqual(2);
});
it('will not break if a nested container has multiple children', function() {