Skip to content

Commit 32bde2d

Browse files
committed
feat($injector): add support for non-string IDs
Previously, only strings could be used as identifiers for Angular services. This commit adds support for using any value as identifier for an Angular service (e.g. used with `.provider()`, `.factory()`, `.service()`, `.value()`, `.constant()`, `.decorator()`). Among other things, non-string identifiers enable: - Private services (without relying on naming conventions). - Collision avoidance (without relying on custom prefixes and namespacing). - Better toolability (e.g. autocomplete support for service identifiers). - Better compression (i.e. strings can't be minified, non-string values could). Identifiers for directives and filters are still restricted to string values, since they need to be referenced in HTML (text). -- For services with string IDs, the corresponding provider ID is constructed by appending `Provider` to the service ID. For services with non-string IDs, the corresponding provider has the exact same ID (it is the context that determines if a service or a provider should be injected). E.g.: ```js var bar = {}; angular. module('myModule', []). provider('foo' /* string ID */, {$get: function () { return 'FOO'; }}). provider( bar /* non-string ID */, {$get: function () { return 'BAR'; }}). config(['fooProvider', function (fooProvider) { // `foo` provider injected (because we are in config block) }]). run(['foo', function (foo) { // `foo` service injected (because we are in run block) }]). config([bar, function (barProvider) { // `bar` provider injected (because we are in config block) // (even though we used the same identifier (`bar`) that we will use in the run block) }]). run([bar, function (bar) { // `bar` service injected (because we are in run block) }]); ``` -- This change is backwards compatible (afaict). Fixes angular#10347
1 parent a9863bf commit 32bde2d

File tree

3 files changed

+254
-72
lines changed

3 files changed

+254
-72
lines changed

Diff for: docs/content/error/$injector/itkn.ngdoc

-26
This file was deleted.

Diff for: src/auto/injector.js

+67-46
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
* Implicit module which gets automatically added to each {@link auto.$injector $injector}.
6464
*/
6565

66+
var PROVIDER_ID_SUFFIX = 'Provider';
6667
var ARROW_ARG = /^([^\(]+?)=>/;
6768
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
6869
var FN_ARG_SPLIT = /,/;
@@ -129,6 +130,14 @@ function annotate(fn, strictDi, name) {
129130
return $inject;
130131
}
131132

133+
function getProviderId(id) {
134+
return !isString(id) ? id : id + PROVIDER_ID_SUFFIX;
135+
}
136+
137+
function stringifyIdForError(id, suffix) {
138+
return isString(id) ? id : id + suffix;
139+
}
140+
132141
///////////////////////////////////////
133142

134143
/**
@@ -647,10 +656,9 @@ function annotate(fn, strictDi, name) {
647656
function createInjector(modulesToLoad, strictDi) {
648657
strictDi = (strictDi === true);
649658
var INSTANTIATING = {},
650-
providerSuffix = 'Provider',
651659
path = [],
652660
loadedModules = new HashMap(null, true),
653-
providerCache = {
661+
providerCache = new HashMap({
654662
$provide: {
655663
provider: supportObject(provider),
656664
factory: supportObject(factory),
@@ -659,24 +667,22 @@ function createInjector(modulesToLoad, strictDi) {
659667
constant: supportObject(constant),
660668
decorator: decorator
661669
}
662-
},
663-
providerInjector = (providerCache.$injector =
664-
createInternalInjector(providerCache, function(serviceName, caller) {
665-
if (angular.isString(caller)) {
666-
path.push(caller);
667-
}
670+
}),
671+
instanceCache = new HashMap(),
672+
providerInjector =
673+
createInternalInjector(providerCache, function(serviceName) {
668674
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
669-
})),
670-
instanceCache = {},
675+
}, ' (provider)'),
671676
protoInstanceInjector =
672-
createInternalInjector(instanceCache, function(serviceName, caller) {
673-
var provider = providerInjector.get(serviceName + providerSuffix, caller);
674-
return instanceInjector.invoke(
675-
provider.$get, provider, undefined, serviceName);
677+
createInternalInjector(instanceCache, function(serviceName) {
678+
var provider = providerInjector.get(getProviderId(serviceName));
679+
return instanceInjector.invoke(provider.$get, provider);
676680
}),
677681
instanceInjector = protoInstanceInjector;
678682

679-
providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };
683+
providerCache.put('$injector', providerInjector);
684+
providerCache.put(getProviderId('$injector'), {$get: valueFn(protoInstanceInjector)});
685+
680686
var runBlocks = loadModules(modulesToLoad);
681687
instanceInjector = protoInstanceInjector.get('$injector');
682688
instanceInjector.strictDi = strictDi;
@@ -690,7 +696,7 @@ function createInjector(modulesToLoad, strictDi) {
690696

691697
function supportObject(delegate) {
692698
return function(key, value) {
693-
if (isObject(key)) {
699+
if ((arguments.length === 1) && isObject(key)) {
694700
forEach(key, reverseParams(delegate));
695701
} else {
696702
return delegate(key, value);
@@ -706,7 +712,10 @@ function createInjector(modulesToLoad, strictDi) {
706712
if (!provider_.$get) {
707713
throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
708714
}
709-
return (providerCache[name + providerSuffix] = provider_);
715+
716+
providerCache.put(getProviderId(name), provider_);
717+
718+
return provider_;
710719
}
711720

712721
function enforceReturnValue(name, factory) {
@@ -737,12 +746,12 @@ function createInjector(modulesToLoad, strictDi) {
737746

738747
function constant(name, value) {
739748
assertNotHasOwnProperty(name, 'constant');
740-
providerCache[name] = value;
741-
instanceCache[name] = value;
749+
providerCache.put(name, value);
750+
instanceCache.put(name, value);
742751
}
743752

744753
function decorator(serviceName, decorFn) {
745-
var origProvider = providerInjector.get(serviceName + providerSuffix),
754+
var origProvider = providerInjector.get(getProviderId(serviceName)),
746755
orig$get = origProvider.$get;
747756

748757
origProvider.$get = function() {
@@ -805,29 +814,45 @@ function createInjector(modulesToLoad, strictDi) {
805814
// internal Injector
806815
////////////////////////////////////
807816

808-
function createInternalInjector(cache, factory) {
817+
function createInternalInjector(cache, factory, displayNameSuffix) {
818+
if (!isString(displayNameSuffix)) {
819+
displayNameSuffix = '';
820+
}
809821

810822
function getService(serviceName, caller) {
811-
if (cache.hasOwnProperty(serviceName)) {
812-
if (cache[serviceName] === INSTANTIATING) {
813-
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
814-
serviceName + ' <- ' + path.join(' <- '));
815-
}
816-
return cache[serviceName];
817-
} else {
818-
try {
819-
path.unshift(serviceName);
820-
cache[serviceName] = INSTANTIATING;
821-
cache[serviceName] = factory(serviceName, caller);
822-
return cache[serviceName];
823-
} catch (err) {
824-
if (cache[serviceName] === INSTANTIATING) {
825-
delete cache[serviceName];
823+
var hasCaller = isDefined(caller);
824+
var hadInstance = cache.has(serviceName);
825+
var instance;
826+
827+
if (hasCaller) {
828+
path.unshift(stringifyIdForError(caller, displayNameSuffix));
829+
}
830+
path.unshift(stringifyIdForError(serviceName, displayNameSuffix));
831+
832+
try {
833+
if (hadInstance) {
834+
instance = cache.get(serviceName);
835+
836+
if (instance === INSTANTIATING) {
837+
throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
826838
}
827-
throw err;
828-
} finally {
829-
path.shift();
839+
840+
return instance;
841+
} else {
842+
cache.put(serviceName, INSTANTIATING);
843+
844+
instance = factory(serviceName);
845+
cache.put(serviceName, instance);
846+
847+
return instance;
848+
}
849+
} finally {
850+
if (!hadInstance && (cache.get(serviceName) === INSTANTIATING)) {
851+
cache.remove(serviceName);
830852
}
853+
854+
path.shift();
855+
if (hasCaller) path.shift();
831856
}
832857
}
833858

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

839864
for (var i = 0, length = $inject.length; i < length; i++) {
840865
var key = $inject[i];
841-
if (typeof key !== 'string') {
842-
throw $injectorMinErr('itkn',
843-
'Incorrect injection token! Expected service name as string, got {0}', key);
844-
}
845-
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
846-
getService(key, serviceName));
866+
var localsHasKey = locals && isString(key) && locals.hasOwnProperty(key);
867+
args.push(localsHasKey ? locals[key] : getService(key, serviceName));
847868
}
848869
return args;
849870
}
@@ -901,7 +922,7 @@ function createInjector(modulesToLoad, strictDi) {
901922
get: getService,
902923
annotate: createInjector.$$annotate,
903924
has: function(name) {
904-
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
925+
return cache.has(name) || providerCache.has(getProviderId(name));
905926
}
906927
};
907928
}

0 commit comments

Comments
 (0)