Skip to content

Commit d9d00bd

Browse files
mheveryvicb
authored andcommitted
feat(core): Create StaticInjector which does not depend on Reflect polyfill.
1 parent f69561b commit d9d00bd

File tree

12 files changed

+1010
-49
lines changed

12 files changed

+1010
-49
lines changed

packages/core/src/di.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
1818

1919
export {Injector} from './di/injector';
2020
export {ReflectiveInjector} from './di/reflective_injector';
21-
export {Provider, TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider} from './di/provider';
21+
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
2222
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
2323
export {ReflectiveKey} from './di/reflective_key';
2424
export {InjectionToken, OpaqueToken} from './di/injection_token';

packages/core/src/di/injector.ts

Lines changed: 307 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
import {Type} from '../type';
1010
import {stringify} from '../util';
1111

12+
import {resolveForwardRef} from './forward_ref';
1213
import {InjectionToken} from './injection_token';
14+
import {Inject, Optional, Self, SkipSelf} from './metadata';
15+
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider';
1316

1417
const _THROW_IF_NOT_FOUND = new Object();
1518
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
1619

1720
class _NullInjector implements Injector {
1821
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
1922
if (notFoundValue === _THROW_IF_NOT_FOUND) {
20-
throw new Error(`No provider for ${stringify(token)}!`);
23+
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
2124
}
2225
return notFoundValue;
2326
}
@@ -60,4 +63,307 @@ export abstract class Injector {
6063
* @suppress {duplicate}
6164
*/
6265
abstract get(token: any, notFoundValue?: any): any;
66+
67+
/**
68+
* Create a new Injector which is configure using `StaticProvider`s.
69+
*
70+
* ### Example
71+
*
72+
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
73+
*/
74+
static create(providers: StaticProvider[], parent?: Injector): Injector {
75+
return new StaticInjector(providers, parent);
76+
}
77+
}
78+
79+
80+
81+
const IDENT = function<T>(value: T): T {
82+
return value;
83+
};
84+
const EMPTY = <any[]>[];
85+
const CIRCULAR = IDENT;
86+
const MULTI_PROVIDER_FN = function(): any[] {
87+
return Array.prototype.slice.call(arguments);
88+
};
89+
const GET_PROPERTY_NAME = {} as any;
90+
const USE_VALUE =
91+
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
92+
const NG_TOKEN_PATH = 'ngTokenPath';
93+
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
94+
const enum OptionFlags {
95+
Optional = 1 << 0,
96+
CheckSelf = 1 << 1,
97+
CheckParent = 1 << 2,
98+
Default = CheckSelf | CheckParent
99+
}
100+
const NULL_INJECTOR = Injector.NULL;
101+
const NEW_LINE = /\n/gm;
102+
const NO_NEW_LINE = 'ɵ';
103+
104+
export class StaticInjector implements Injector {
105+
readonly parent: Injector;
106+
107+
private _records: Map<any, Record>;
108+
109+
constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) {
110+
this.parent = parent;
111+
const records = this._records = new Map<any, Record>();
112+
records.set(
113+
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
114+
recursivelyProcessProviders(records, providers);
115+
}
116+
117+
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
118+
get(token: any, notFoundValue?: any): any;
119+
get(token: any, notFoundValue?: any): any {
120+
const record = this._records.get(token);
121+
try {
122+
return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
123+
} catch (e) {
124+
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
125+
e.message = formatError('\n' + e.message, tokenPath);
126+
e[NG_TOKEN_PATH] = tokenPath;
127+
e[NG_TEMP_TOKEN_PATH] = null;
128+
throw e;
129+
}
130+
}
131+
132+
toString() {
133+
const tokens = <string[]>[], records = this._records;
134+
records.forEach((v, token) => tokens.push(stringify(token)));
135+
return `StaticInjector[${tokens.join(', ')}]`;
136+
}
137+
}
138+
139+
type SupportedProvider =
140+
ValueProvider | ExistingProvider | StaticClassProvider | ConstructorProvider | FactoryProvider;
141+
142+
interface Record {
143+
fn: Function;
144+
useNew: boolean;
145+
deps: DependencyRecord[];
146+
value: any;
147+
}
148+
149+
interface DependencyRecord {
150+
token: any;
151+
options: number;
152+
}
153+
154+
type TokenPath = Array<any>;
155+
156+
function resolveProvider(provider: SupportedProvider): Record {
157+
const deps = computeDeps(provider);
158+
let fn: Function = IDENT;
159+
let value: any = EMPTY;
160+
let useNew: boolean = false;
161+
let provide = resolveForwardRef(provider.provide);
162+
if (USE_VALUE in provider) {
163+
// We need to use USE_VALUE in provider since provider.useValue could be defined as undefined.
164+
value = (provider as ValueProvider).useValue;
165+
} else if ((provider as FactoryProvider).useFactory) {
166+
fn = (provider as FactoryProvider).useFactory;
167+
} else if ((provider as ExistingProvider).useExisting) {
168+
// Just use IDENT
169+
} else if ((provider as StaticClassProvider).useClass) {
170+
useNew = true;
171+
fn = resolveForwardRef((provider as StaticClassProvider).useClass);
172+
} else if (typeof provide == 'function') {
173+
useNew = true;
174+
fn = provide;
175+
} else {
176+
throw staticError(
177+
'StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable',
178+
provider);
179+
}
180+
return {deps, fn, useNew, value};
181+
}
182+
183+
function multiProviderMixError(token: any) {
184+
return staticError('Cannot mix multi providers and regular providers', token);
185+
}
186+
187+
function recursivelyProcessProviders(records: Map<any, Record>, provider: StaticProvider) {
188+
if (provider) {
189+
provider = resolveForwardRef(provider);
190+
if (provider instanceof Array) {
191+
// if we have an array recurse into the array
192+
for (let i = 0; i < provider.length; i++) {
193+
recursivelyProcessProviders(records, provider[i]);
194+
}
195+
} else if (typeof provider === 'function') {
196+
// Functions were supported in ReflectiveInjector, but are not here. For safety give useful
197+
// error messages
198+
throw staticError('Function/Class not supported', provider);
199+
} else if (provider && typeof provider === 'object' && provider.provide) {
200+
// At this point we have what looks like a provider: {provide: ?, ....}
201+
let token = resolveForwardRef(provider.provide);
202+
const resolvedProvider = resolveProvider(provider);
203+
if (provider.multi === true) {
204+
// This is a multi provider.
205+
let multiProvider: Record|undefined = records.get(token);
206+
if (multiProvider) {
207+
if (multiProvider.fn !== MULTI_PROVIDER_FN) {
208+
throw multiProviderMixError(token);
209+
}
210+
} else {
211+
// Create a placeholder factory which will look up the constituents of the multi provider.
212+
records.set(token, multiProvider = <Record>{
213+
token: provider.provide,
214+
deps: [],
215+
useNew: false,
216+
fn: MULTI_PROVIDER_FN,
217+
value: EMPTY
218+
});
219+
}
220+
// Treat the provider as the token.
221+
token = provider;
222+
multiProvider.deps.push({token, options: OptionFlags.Default});
223+
}
224+
const record = records.get(token);
225+
if (record && record.fn == MULTI_PROVIDER_FN) {
226+
throw multiProviderMixError(token);
227+
}
228+
records.set(token, resolvedProvider);
229+
} else {
230+
throw staticError('Unexpected provider', provider);
231+
}
232+
}
233+
}
234+
235+
function tryResolveToken(
236+
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
237+
notFoundValue: any): any {
238+
try {
239+
return resolveToken(token, record, records, parent, notFoundValue);
240+
} catch (e) {
241+
// ensure that 'e' is of type Error.
242+
if (!(e instanceof Error)) {
243+
e = new Error(e);
244+
}
245+
const path: any[] = e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || [];
246+
path.unshift(token);
247+
if (record && record.value == CIRCULAR) {
248+
// Reset the Circular flag.
249+
record.value = EMPTY;
250+
}
251+
throw e;
252+
}
253+
}
254+
255+
function resolveToken(
256+
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
257+
notFoundValue: any): any {
258+
let value;
259+
if (record) {
260+
// If we don't have a record, this implies that we don't own the provider hence don't know how
261+
// to resolve it.
262+
value = record.value;
263+
if (value == CIRCULAR) {
264+
throw Error(NO_NEW_LINE + 'Circular dependency');
265+
} else if (value === EMPTY) {
266+
record.value = CIRCULAR;
267+
let obj = undefined;
268+
let useNew = record.useNew;
269+
let fn = record.fn;
270+
let depRecords = record.deps;
271+
let deps = EMPTY;
272+
if (depRecords.length) {
273+
deps = [];
274+
for (let i = 0; i < depRecords.length; i++) {
275+
const depRecord: DependencyRecord = depRecords[i];
276+
const options = depRecord.options;
277+
const childRecord =
278+
options & OptionFlags.CheckSelf ? records.get(depRecord.token) : undefined;
279+
deps.push(tryResolveToken(
280+
// Current Token to resolve
281+
depRecord.token,
282+
// A record which describes how to resolve the token.
283+
// If undefined, this means we don't have such a record
284+
childRecord,
285+
// Other records we know about.
286+
records,
287+
// If we don't know how to resolve dependency and we should not check parent for it,
288+
// than pass in Null injector.
289+
!childRecord && !(options & OptionFlags.CheckParent) ? NULL_INJECTOR : parent,
290+
options & OptionFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND));
291+
}
292+
}
293+
record.value = value = useNew ? new (fn as any)(...deps) : fn.apply(obj, deps);
294+
}
295+
} else {
296+
value = parent.get(token, notFoundValue);
297+
}
298+
return value;
299+
}
300+
301+
302+
function computeDeps(provider: StaticProvider): DependencyRecord[] {
303+
let deps: DependencyRecord[] = EMPTY;
304+
const providerDeps: any[] =
305+
(provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;
306+
if (providerDeps && providerDeps.length) {
307+
deps = [];
308+
for (let i = 0; i < providerDeps.length; i++) {
309+
let options = OptionFlags.Default;
310+
let token = resolveForwardRef(providerDeps[i]);
311+
if (token instanceof Array) {
312+
for (let j = 0, annotations = token; j < annotations.length; j++) {
313+
const annotation = annotations[j];
314+
if (annotation instanceof Optional || annotation == Optional) {
315+
options = options | OptionFlags.Optional;
316+
} else if (annotation instanceof SkipSelf || annotation == SkipSelf) {
317+
options = options & ~OptionFlags.CheckSelf;
318+
} else if (annotation instanceof Self || annotation == Self) {
319+
options = options & ~OptionFlags.CheckParent;
320+
} else if (annotation instanceof Inject) {
321+
token = (annotation as Inject).token;
322+
} else {
323+
token = resolveForwardRef(annotation);
324+
}
325+
}
326+
}
327+
deps.push({token, options});
328+
}
329+
} else if ((provider as ExistingProvider).useExisting) {
330+
const token = resolveForwardRef((provider as ExistingProvider).useExisting);
331+
deps = [{token, options: OptionFlags.Default}];
332+
} else if (!providerDeps && !(USE_VALUE in provider)) {
333+
// useValue & useExisting are the only ones which are exempt from deps all others need it.
334+
throw staticError('\'deps\' required', provider);
335+
}
336+
return deps;
337+
}
338+
339+
function formatError(text: string, obj: any): string {
340+
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
341+
let context = stringify(obj);
342+
if (obj instanceof Array) {
343+
context = obj.map(stringify).join(' -> ');
344+
} else if (typeof obj === 'object') {
345+
let parts = <string[]>[];
346+
for (let key in obj) {
347+
if (obj.hasOwnProperty(key)) {
348+
let value = obj[key];
349+
parts.push(
350+
key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
351+
}
352+
}
353+
context = `{${parts.join(', ')}}`;
354+
}
355+
return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
356+
}
357+
358+
function staticError(text: string, obj: any): Error {
359+
return new Error(formatError(text, obj));
360+
}
361+
362+
function getClosureSafeProperty<T>(objWithPropertyToExtract: T): string {
363+
for (let key in objWithPropertyToExtract) {
364+
if (objWithPropertyToExtract[key] === GET_PROPERTY_NAME) {
365+
return key;
366+
}
367+
}
368+
throw Error('!prop');
63369
}

0 commit comments

Comments
 (0)