/
injector_compatibility.ts
244 lines (220 loc) · 8.92 KB
/
injector_compatibility.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../interface/type';
import {getClosureSafeProperty} from '../util/property';
import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token';
import {Injector} from './injector';
import {getInjectableDef, ɵɵInjectableDef} from './interface/defs';
import {InjectFlags} from './interface/injector';
import {ValueProvider} from './interface/provider';
import {Inject, Optional, Self, SkipSelf} from './metadata';
/**
* An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
*
* Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
* project.
*
* @publicApi
*/
export const INJECTOR = new InjectionToken<Injector>(
'INJECTOR',
-1 as any // `-1` is used by Ivy DI system as special value to recognize it as `Injector`.
);
const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
export const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
const NG_TOKEN_PATH = 'ngTokenPath';
const NEW_LINE = /\n/gm;
const NO_NEW_LINE = 'ɵ';
export const SOURCE = '__source';
export const USE_VALUE =
getClosureSafeProperty<ValueProvider>({provide: String, useValue: getClosureSafeProperty});
/**
* Current injector value used by `inject`.
* - `undefined`: it is an error to call `inject`
* - `null`: `inject` can be called but there is no injector (limp-mode).
* - Injector instance: Use the injector for resolution.
*/
let _currentInjector: Injector|undefined|null = undefined;
export function setCurrentInjector(injector: Injector | null | undefined): Injector|undefined|null {
const former = _currentInjector;
_currentInjector = injector;
return former;
}
/**
* Current implementation of inject.
*
* By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
* to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
* way for two reasons:
* 1. `Injector` should not depend on ivy logic.
* 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
*/
let _injectImplementation:
(<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined;
/**
* Sets the current inject implementation.
*/
export function setInjectImplementation(
impl: (<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null) | undefined):
(<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags) => T | null)|undefined {
const previous = _injectImplementation;
_injectImplementation = impl;
return previous;
}
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>): T;
export function injectInjectorOnly<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|
null;
export function injectInjectorOnly<T>(
token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|null {
if (_currentInjector === undefined) {
throw new Error(`inject() must be called from an injection context`);
} else if (_currentInjector === null) {
return injectRootLimpMode(token, undefined, flags);
} else {
return _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
}
}
/**
* Generated instruction: Injects a token from the currently active injector.
*
* Must be used in the context of a factory function such as one defined for an
* `InjectionToken`. Throws an error if not called from such a context.
*
* (Additional documentation moved to `inject`, as it is the public API, and an alias for this instruction)
*
* @see inject
* @codeGenApi
*/
export function ɵɵinject<T>(token: Type<T>| InjectionToken<T>): T;
export function ɵɵinject<T>(token: Type<T>| InjectionToken<T>, flags?: InjectFlags): T|null;
export function ɵɵinject<T>(token: Type<T>| InjectionToken<T>, flags = InjectFlags.Default): T|
null {
return (_injectImplementation || injectInjectorOnly)(token, flags);
}
/**
* Injects a token from the currently active injector.
*
* Must be used in the context of a factory function such as one defined for an
* `InjectionToken`. Throws an error if not called from such a context.
*
* Within such a factory function, using this function to request injection of a dependency
* is faster and more type-safe than providing an additional array of dependencies
* (as has been common with `useFactory` providers).
*
* @param token The injection token for the dependency to be injected.
* @param flags Optional flags that control how injection is executed.
* The flags correspond to injection strategies that can be specified with
* parameter decorators `@Host`, `@Self`, `@SkipSef`, and `@Optional`.
* @returns True if injection is successful, null otherwise.
*
* @usageNotes
*
* ### Example
*
* {@example core/di/ts/injector_spec.ts region='ShakableInjectionToken'}
*
* @publicApi
*/
export const inject = ɵɵinject;
/**
* Injects `root` tokens in limp mode.
*
* If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
* `"root"`. This is known as the limp mode injection. In such case the value is stored in the
* `InjectableDef`.
*/
export function injectRootLimpMode<T>(
token: Type<T>| InjectionToken<T>, notFoundValue: T | undefined, flags: InjectFlags): T|null {
const injectableDef: ɵɵInjectableDef<T>|null = getInjectableDef(token);
if (injectableDef && injectableDef.providedIn == 'root') {
return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
injectableDef.value;
}
if (flags & InjectFlags.Optional) return null;
if (notFoundValue !== undefined) return notFoundValue;
throw new Error(`Injector: NOT_FOUND [${stringify(token)}]`);
}
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
const args: any[] = [];
for (let i = 0; i < types.length; i++) {
const arg = resolveForwardRef(types[i]);
if (Array.isArray(arg)) {
if (arg.length === 0) {
throw new Error('Arguments array must have arguments.');
}
let type: Type<any>|undefined = undefined;
let flags: InjectFlags = InjectFlags.Default;
for (let j = 0; j < arg.length; j++) {
const meta = arg[j];
if (meta instanceof Optional || meta.ngMetadataName === 'Optional' || meta === Optional) {
flags |= InjectFlags.Optional;
} else if (
meta instanceof SkipSelf || meta.ngMetadataName === 'SkipSelf' || meta === SkipSelf) {
flags |= InjectFlags.SkipSelf;
} else if (meta instanceof Self || meta.ngMetadataName === 'Self' || meta === Self) {
flags |= InjectFlags.Self;
} else if (meta instanceof Inject || meta === Inject) {
type = meta.token;
} else {
type = meta;
}
}
args.push(ɵɵinject(type !, flags));
} else {
args.push(ɵɵinject(arg));
}
}
return args;
}
export class NullInjector implements Injector {
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
if (notFoundValue === THROW_IF_NOT_FOUND) {
// Intentionally left behind: With dev tools open the debugger will stop here. There is no
// reason why correctly written application should cause this exception.
// TODO(misko): uncomment the next line once `ngDevMode` works with closure.
// if(ngDevMode) debugger;
const error = new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
error.name = 'NullInjectorError';
throw error;
}
return notFoundValue;
}
}
export function catchInjectorError(
e: any, token: any, injectorErrorName: string, source: string | null): never {
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
if (token[SOURCE]) {
tokenPath.unshift(token[SOURCE]);
}
e.message = formatError('\n' + e.message, tokenPath, injectorErrorName, source);
e[NG_TOKEN_PATH] = tokenPath;
e[NG_TEMP_TOKEN_PATH] = null;
throw e;
}
export function formatError(
text: string, obj: any, injectorErrorName: string, source: string | null = null): string {
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
let context = stringify(obj);
if (obj instanceof Array) {
context = obj.map(stringify).join(' -> ');
} else if (typeof obj === 'object') {
let parts = <string[]>[];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let value = obj[key];
parts.push(
key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
}
}
context = `{${parts.join(', ')}}`;
}
return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
}