Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

WIP - feat($injector): add support for non-string IDs (and other minor stuff)#14998

Open
wants to merge 4 commits into
base:master
Choose a base branch
from
26 changes: 0 additions & 26 deletions docs/content/error/$injector/itkn.ngdoc

This file was deleted.

17 changes: 15 additions & 2 deletions src/apis.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,14 +36,19 @@ function hashKey(obj, nextUidFn) {
/**
* HashMap which can use objects as keys
*/
function HashMap(array, isolatedUid) {
function HashMap(seedData, isolatedUid) {
if (isolatedUid) {
var uid = 0;
this.nextUid = function() {
return ++uid;
};
}
forEach(array, this.put, this);

if (seedData) {
var putFn = isArray(seedData) ?
this.put : reverseParams(this.put.bind(this));
forEach(seedData, putFn, this);
}
}
HashMap.prototype = {
/**
Expand All@@ -63,6 +68,14 @@ HashMap.prototype = {
return this[hashKey(key, this.nextUid)];
},

/**
* @param key
* @returns {boolean} whether a value is stored under the specified key
*/
has: function(key) {
return this.hasOwnProperty(hashKey(key, this.nextUid));
},

/**
* Remove the key/value pair
* @param key
Expand Down
139 changes: 80 additions & 59 deletions src/auto/injector.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,7 @@
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
*/

var PROVIDER_ID_SUFFIX = 'Provider';
var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
Expand All@@ -74,7 +75,7 @@ function stringifyFn(fn) {
// Support: Chrome 50-51 only
// Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51
// (See https://github.com/angular/angular.js/issues/14487.)
// TODO(gkalpak): Remove workaround when Chrome v52 is released
// TODO(gkalpak): Remove workaround when Chrome v52 is released
return Function.prototype.toString.call(fn) + ' ';
}

Expand DownExpand Up@@ -129,6 +130,14 @@ function annotate(fn, strictDi, name) {
return $inject;
}

function getProviderId(id) {
return !isString(id) ? id : id + PROVIDER_ID_SUFFIX;
}

function stringifyIdForError(id, suffix) {
return isString(id) ? id : id + suffix;
}

///////////////////////////////////////

/**
Expand DownExpand Up@@ -647,36 +656,33 @@ function annotate(fn, strictDi, name) {
function createInjector(modulesToLoad, strictDi) {
strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = {
loadedModules = new HashMap(null, true),
providerCache = new HashMap({
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
}),
instanceCache = new HashMap(),
providerInjector =
createInternalInjector(providerCache, function(serviceName) {
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
}, ' (provider)'),
protoInstanceInjector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(
provider.$get, provider, undefined, serviceName);
createInternalInjector(instanceCache, function(serviceName) {
var provider = providerInjector.get(getProviderId(serviceName));
return instanceInjector.invoke(provider.$get, provider);
}),
instanceInjector = protoInstanceInjector;

providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
providerCache.put('$injector', providerInjector);
providerCache.put(getProviderId('$injector'), {$get: valueFn(protoInstanceInjector)});

var runBlocks = loadModules(modulesToLoad);
instanceInjector = protoInstanceInjector.get('$injector');
instanceInjector.strictDi = strictDi;
Expand All@@ -690,7 +696,7 @@ function createInjector(modulesToLoad, strictDi) {

function supportObject(delegate) {
return function(key, value) {
if (isObject(key)) {
if ((arguments.length === 1) && isObject(key)) {
forEach(key, reverseParams(delegate));
} else {
return delegate(key, value);
Expand All@@ -706,7 +712,10 @@ function createInjector(modulesToLoad, strictDi) {
if (!provider_.$get) {
throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
}
return (providerCache[name + providerSuffix] = provider_);

providerCache.put(getProviderId(name), provider_);

return provider_;
}

function enforceReturnValue(name, factory) {
Expand All@@ -731,16 +740,18 @@ function createInjector(modulesToLoad, strictDi) {
}]);
}

function value(name, val) { return factory(name, valueFn(val), false); }
function value(name, val) {
return factory(name, valueFn(val), false);
}

function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
providerCache[name] = value;
instanceCache[name] = value;
providerCache.put(name, value);
instanceCache.put(name, value);
}

function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
var origProvider = providerInjector.get(getProviderId(serviceName)),
orig$get = origProvider.$get;

origProvider.$get = function() {
Expand DownExpand Up@@ -775,9 +786,7 @@ function createInjector(modulesToLoad, strictDi) {
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
runInvokeQueue(moduleFn._invokeQueue);
runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
} else if (isFunction(module) || isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
Expand DownExpand Up@@ -805,29 +814,45 @@ function createInjector(modulesToLoad, strictDi) {
// internal Injector
////////////////////////////////////

function createInternalInjector(cache, factory) {
function createInternalInjector(cache, factory, displayNameSuffix) {
if (!isString(displayNameSuffix)) {
displayNameSuffix = '';
}

function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
cache[serviceName] = factory(serviceName, caller);
return cache[serviceName];
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
var hasCaller = isDefined(caller);
var hadInstance = cache.has(serviceName);
var instance;

if (hasCaller) {
path.unshift(stringifyIdForError(caller, displayNameSuffix));
}
path.unshift(stringifyIdForError(serviceName, displayNameSuffix));

try {
if (hadInstance) {
instance = cache.get(serviceName);

if (instance === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
}
throw err;
} finally {
path.shift();

return instance;
} else {
cache.put(serviceName, INSTANTIATING);

instance = factory(serviceName);
cache.put(serviceName, instance);

return instance;
}
} finally {
if (!hadInstance && (cache.get(serviceName) === INSTANTIATING)) {
cache.remove(serviceName);
}

path.shift();
if (hasCaller) path.shift();
}
}

Expand All@@ -838,12 +863,8 @@ function createInjector(modulesToLoad, strictDi) {

for (var i = 0, length = $inject.length; i < length; i++) {
var key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
getService(key, serviceName));
var localsHasKey = locals && isString(key) && locals.hasOwnProperty(key);
args.push(localsHasKey ? locals[key] : getService(key, serviceName));
}
return args;
}
Expand DownExpand Up@@ -901,7 +922,7 @@ function createInjector(modulesToLoad, strictDi) {
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
return cache.has(name) || providerCache.has(getProviderId(name));
}
};
}
Expand Down
5 changes: 4 additions & 1 deletion src/ng/directive/select.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -563,8 +563,11 @@ var selectDirective = function() {
// Write value now needs to set the selected property of each matching option
selectCtrl.writeValue = function writeMultipleValue(value) {
var items = new HashMap(value);
var selectValueMap = selectCtrl.selectValueMap;

forEach(element.find('option'), function(option) {
option.selected = isDefined(items.get(option.value)) || isDefined(items.get(selectCtrl.selectValueMap[option.value]));
var value = option.value;
option.selected = items.has(value) || items.has(selectValueMap[value]);
});
};

Expand Down
12 changes: 12 additions & 0 deletions test/ApiSpecs.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,11 +8,16 @@ describe('api', function() {
var key = {};
var value1 = {};
var value2 = {};

map.put(key, value1);
map.put(key, value2);

expect(map.has(key)).toBe(true);
expect(map.has({})).toBe(false);
expect(map.get(key)).toBe(value2);
expect(map.get({})).toBeUndefined();
expect(map.remove(key)).toBe(value2);
expect(map.has(key)).toBe(false);
expect(map.get(key)).toBeUndefined();
});

Expand All@@ -23,6 +28,13 @@ describe('api', function() {
expect(map.get('c')).toBeUndefined();
});

it('should init from an object', function() {
var map = new HashMap({a: 'foo', b: 'bar'});
expect(map.get('a')).toBe('foo');
expect(map.get('b')).toBe('bar');
expect(map.get('c')).toBeUndefined();
});

it('should maintain hashKey for object keys', function() {
var map = new HashMap();
var key = {};
Expand Down
Loading
close