Skip to content

Commit c9c311b

Browse files
authored
Merge pull request #196 from esroyo/feat/reset-providers-propagation
feat: allows to propagete providers named reset to its dependents
2 parents 9db7e22 + f38b91c commit c9c311b

File tree

9 files changed

+167
-30
lines changed

9 files changed

+167
-30
lines changed

README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,14 @@ Param | Type | Details
381381
**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.
382382

383383
#### resetProviders(names)
384-
Param | Type | Details
385-
:--------------------------|:-----------|:--------
386-
**names**<br />*(optional)*| *Array* | An array of strings which contains names of the providers to be reset.
384+
Param | Type | Details
385+
:------------------------------|:-----------|:--------
386+
**names**<br />*(optional)* | *Array* | An array of strings which contains names of the providers to be reset.
387+
**propagate**<br />*(optional)*| *Boolean* | Propagate the reset to all providers that depend on the previous list.
388+
389+
Used to reset providers for the next reference to re-instantiate the provider.
387390

388-
Used to reset providers for the next reference to re-instantiate the provider. If `names` param is passed, will reset only the named providers.
391+
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.
389392

390393
#### register(Obj)
391394
#### container.$register(Obj)

grunt/config/grunt-contrib-jasmine.json

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"jasmine" : {
3+
"options": {
4+
"version": "3.8.0",
5+
"noSandbox": true
6+
},
37
"tests" : {
48
"src" : "dist/bottle.js",
59
"options" : {

src/Bottle/middleware.js

+18-11
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88
* @return void
99
*/
1010
var applyMiddleware = function applyMiddleware(middleware, name, instance, container) {
11+
var bottle = this;
1112
var descriptor = {
1213
configurable : true,
13-
enumerable : true
14-
};
15-
if (middleware.length) {
16-
descriptor.get = function getWithMiddlewear() {
17-
var index = 0;
18-
var next = function nextMiddleware(err) {
14+
enumerable : true,
15+
get : function getWithMiddlewear() {
16+
var captureTarget,serviceDependents, index, next;
17+
if (bottle.capturingDepsOf.length) {
18+
captureTarget = bottle.capturingDepsOf[bottle.capturingDepsOf.length - 1];
19+
serviceDependents = bottle.dependents[name] = bottle.dependents[name] || [];
20+
if (serviceDependents.indexOf(captureTarget) === -1) {
21+
serviceDependents.push(captureTarget);
22+
}
23+
}
24+
if (!middleware.length) {
25+
return instance;
26+
}
27+
index = 0;
28+
next = function nextMiddleware(err) {
1929
if (err) {
2030
throw err;
2131
}
@@ -25,11 +35,8 @@ var applyMiddleware = function applyMiddleware(middleware, name, instance, conta
2535
};
2636
next();
2737
return instance;
28-
};
29-
} else {
30-
descriptor.value = instance;
31-
descriptor.writable = true;
32-
}
38+
},
39+
};
3340

3441
Object.defineProperty(container, name, descriptor);
3542

src/Bottle/provider.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ var getWithGlobal = function getWithGlobal(collection, name) {
2828
* @return Bottle
2929
*/
3030
var createProvider = function createProvider(name, Provider) {
31-
var providerName, properties, container, id, decorators, middlewares;
31+
var bottle, providerName, properties, container, decorators, middlewares;
3232

33-
id = this.id;
33+
bottle = this;
3434
container = this.container;
3535
decorators = this.decorators;
3636
middlewares = this.middlewares;
@@ -55,14 +55,18 @@ var createProvider = function createProvider(name, Provider) {
5555
var provider = container[providerName];
5656
var instance;
5757
if (provider) {
58+
bottle.capturingDepsOf.push(name);
5859
// filter through decorators
5960
instance = getWithGlobal(decorators, name).reduce(reducer, provider.$get(container));
61+
bottle.capturingDepsOf.pop();
6062

6163
delete container[providerName];
6264
delete container[name];
6365
}
64-
return instance === undefined ? instance : applyMiddleware(getWithGlobal(middlewares, name),
65-
name, instance, container);
66+
if (instance === undefined) {
67+
return instance;
68+
}
69+
return applyMiddleware.call(bottle, getWithGlobal(middlewares, name), name, instance, container);
6670
}
6771
};
6872

src/Bottle/reset-providers.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,58 @@
55
* @return void
66
*/
77
var removeProviderMap = function resetProvider(name) {
8+
var parts = splitHead(name);
9+
if (parts.length > 1) {
10+
removeProviderMap.call(getNestedBottle.call(this, parts[0]), parts[1]);
11+
}
812
delete this.providerMap[name];
913
delete this.container[name];
1014
delete this.container[name + PROVIDER_SUFFIX];
1115
};
1216

17+
/**
18+
* Clears a reseted service from the dependencies tracker.
19+
*
20+
* @param String name
21+
* @return void
22+
*/
23+
var removeFromDeps = function removeFromDeps(name) {
24+
var parts = splitHead(name);
25+
if (parts.length > 1) {
26+
removeFromDeps.call(getNestedBottle.call(this, parts[0]), parts[1]);
27+
}
28+
Object.keys(this.dependents).forEach(function clearDependents(serviceName) {
29+
if (this.dependents[serviceName]) {
30+
this.dependents[serviceName] = this.dependents[serviceName]
31+
.filter(function (dependent) { return dependent !== name; });
32+
}
33+
}, this);
34+
};
35+
1336
/**
1437
* Resets providers on a bottle instance. If 'names' array is provided, only the named providers will be reset.
1538
*
1639
* @param Array names
40+
* @param Boolean [propagate]
1741
* @return void
1842
*/
19-
var resetProviders = function resetProviders(names) {
20-
var tempProviders = this.originalProviders;
43+
var resetProviders = function resetProviders(names, propagate) {
2144
var shouldFilter = Array.isArray(names);
22-
2345
Object.keys(this.originalProviders).forEach(function resetProvider(originalProviderName) {
2446
if (shouldFilter && names.indexOf(originalProviderName) === -1) {
2547
return;
2648
}
27-
var parts = originalProviderName.split(DELIMITER);
49+
var parts = splitHead(originalProviderName);
2850
if (parts.length > 1) {
29-
parts.forEach(removeProviderMap, getNestedBottle.call(this, parts[0]));
51+
resetProviders.call(getNestedBottle.call(this, parts[0]), [parts[1]], propagate);
52+
}
53+
if (shouldFilter && propagate && this.dependents[originalProviderName]) {
54+
this.resetProviders(this.dependents[originalProviderName], propagate);
55+
}
56+
if (shouldFilter) {
57+
removeFromDeps.call(this, originalProviderName);
3058
}
3159
removeProviderMap.call(this, originalProviderName);
32-
this.provider(originalProviderName, tempProviders[originalProviderName]);
60+
this.provider(originalProviderName, this.originalProviders[originalProviderName]);
3361
}, this);
3462
};

src/Bottle/service.js

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
var createService = function createService(name, Service, isClass) {
99
var deps = arguments.length > 3 ? slice.call(arguments, 3) : [];
1010
var bottle = this;
11+
deps.forEach(function registerDependents(otherService) {
12+
var serviceDependents = bottle.dependents[otherService] = bottle.dependents[otherService] || [];
13+
if (serviceDependents.indexOf(name) === -1) {
14+
serviceDependents.push(name);
15+
}
16+
});
1117
return factory.call(this, name, function GenericFactory() {
1218
var serviceFactory = Service; // alias for jshint
1319
var args = deps.map(getNestedService, bottle.container);

src/api.js

+2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ Bottle = function Bottle(name) {
1111

1212
this.id = id++;
1313

14+
this.capturingDepsOf = [];
1415
this.decorators = {};
16+
this.dependents = {};
1517
this.middlewares = {};
1618
this.nested = {};
1719
this.providerMap = {};

src/globals.js

+11
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,14 @@ var getNestedBottle = function getNestedBottle(name) {
6969
var getNestedService = function getNestedService(fullname) {
7070
return fullname.split(DELIMITER).reduce(getNested, this);
7171
};
72+
73+
/**
74+
* Split a dot-notation string on head segment and rest segment.
75+
*
76+
* @param String fullname
77+
* @return Array
78+
*/
79+
var splitHead = function splitHead(fullname) {
80+
var parts = fullname.split(DELIMITER);
81+
return parts.length > 1 ? [parts[0], parts.slice(1).join(DELIMITER)] : [parts[0]];
82+
};

test/spec/provider.spec.js

+77-5
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,88 @@
189189
expect(i).toEqual(2);
190190
expect(j).toEqual(1);
191191
});
192-
it('allows for sub containers to re-initiate as well', function() {
192+
it('allows to propagate the reset to the providers that depend on the selected service names', function() {
193+
var i = 0;
194+
var j = 0;
195+
var k = 0;
196+
var l = 0;
197+
var b = new Bottle();
198+
var FirstService= function() { i = ++i; };
199+
var SecondProvider = function() {
200+
j = ++j;
201+
this.$get = function(container) { return { _first: container.First }; };
202+
};
203+
var ThirdFactory = function(container) { k = ++k; return { _first: container.First }; };
204+
b.factory('Zero', function () { l = ++l; return 0; });
205+
b.service('First', FirstService, 'Zero');
206+
b.provider('Second', SecondProvider);
207+
b.factory('Third', ThirdFactory);
208+
expect(b.container.Second._first instanceof FirstService).toBe(true);
209+
expect(b.container.Third._first instanceof FirstService).toBe(true);
210+
expect(i).toEqual(1);
211+
expect(j).toEqual(1);
212+
expect(k).toEqual(1);
213+
expect(l).toEqual(1);
214+
b.resetProviders(['First'], true);
215+
expect(b.container.Second._first instanceof FirstService).toBe(true);
216+
expect(b.container.Third._first instanceof FirstService).toBe(true);
217+
expect(i).toEqual(2);
218+
expect(j).toEqual(2);
219+
expect(k).toEqual(2);
220+
expect(l).toEqual(1);
221+
b.resetProviders(['Zero'], true);
222+
expect(b.container.Second._first instanceof FirstService).toBe(true);
223+
expect(b.container.Third._first instanceof FirstService).toBe(true);
224+
expect(i).toEqual(3);
225+
expect(j).toEqual(3);
226+
expect(k).toEqual(3);
227+
expect(l).toEqual(2);
228+
});
229+
it('will cleanup service dependents if a service is redefined', function() {
230+
var i = 0;
231+
var j = 0;
232+
var k = 0;
233+
var b = new Bottle();
234+
var FirstService= function() { i = ++i; };
235+
var SecondService = function() { j = ++j; };
236+
var ThirdService = function() { k = ++k; };
237+
b.service('First', FirstService);
238+
b.service('Thing.Second', SecondService, 'First');
239+
b.service('Third', ThirdService, 'Thing.Second');
240+
expect(b.container.First instanceof FirstService).toBe(true);
241+
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
242+
expect(b.container.Third instanceof ThirdService).toBe(true);
243+
expect(i).toEqual(1);
244+
expect(j).toEqual(1);
245+
expect(k).toEqual(1);
246+
b.resetProviders(['Thing.Second'], true);
247+
// Redefined to have no dependencies
248+
b.service('Thing.Second', SecondService);
249+
expect(b.container.First instanceof FirstService).toBe(true);
250+
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
251+
expect(b.container.Third instanceof ThirdService).toBe(true);
252+
expect(i).toEqual(1);
253+
expect(j).toEqual(2);
254+
expect(k).toEqual(2);
255+
// No propagation will happen given that no service depends on First anymore
256+
b.resetProviders(['First'], true);
257+
expect(b.container.First instanceof FirstService).toBe(true);
258+
expect(b.container.Thing.Second instanceof SecondService).toBe(true);
259+
expect(b.container.Third instanceof ThirdService).toBe(true);
260+
expect(i).toEqual(2);
261+
expect(j).toEqual(2);
262+
expect(k).toEqual(2);
263+
});
264+
it('allows for deep sub containers to re-initiate as well', function() {
193265
var i = 0;
194266
var b = new Bottle();
195267
var ThingProvider = function() { i = ++i; this.$get = function() { return this; }; };
196-
b.provider('Thing.Something', ThingProvider);
197-
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
268+
b.provider('Thing.In.Something', ThingProvider);
269+
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
198270
// Intentionally calling twice to prove the construction is cached until reset
199-
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
271+
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
200272
b.resetProviders();
201-
expect(b.container.Thing.Something instanceof ThingProvider).toBe(true);
273+
expect(b.container.Thing.In.Something instanceof ThingProvider).toBe(true);
202274
expect(i).toEqual(2);
203275
});
204276
it('will not break if a nested container has multiple children', function() {

0 commit comments

Comments
 (0)