Skip to content
This repository has been 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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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