/
provider_collection.ts
336 lines (302 loc) · 12.9 KB
/
provider_collection.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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/**
* @license
* Copyright Google LLC 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 {RuntimeError, RuntimeErrorCode} from '../errors';
import {Type} from '../interface/type';
import {getComponentDef} from '../render3/definition';
import {getFactoryDef} from '../render3/definition_factory';
import {throwCyclicDependencyError, throwInvalidProviderError} from '../render3/errors_di';
import {stringifyForError} from '../render3/util/stringify_utils';
import {deepForEach} from '../util/array_utils';
import {getClosureSafeProperty} from '../util/property';
import {stringify} from '../util/stringify';
import {EMPTY_ARRAY} from '../view';
import {resolveForwardRef} from './forward_ref';
import {ENVIRONMENT_INITIALIZER} from './initializer_token';
import {ɵɵinject as inject} from './injector_compatibility';
import {getInjectorDef, InjectorType, InjectorTypeWithProviders} from './interface/defs';
import {ClassProvider, ConstructorProvider, EnvironmentProviders, ExistingProvider, FactoryProvider, ImportedNgModuleProviders, InternalEnvironmentProviders, isEnvironmentProviders, ModuleWithProviders, Provider, StaticClassProvider, TypeProvider, ValueProvider} from './interface/provider';
import {INJECTOR_DEF_TYPES} from './internal_tokens';
/**
* Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
* referenced in `@Component in a component injector.
*/
export function makeEnvironmentProviders(providers: Provider[]): EnvironmentProviders {
return {
ɵproviders: providers,
} as unknown as EnvironmentProviders;
}
/**
* A source of providers for the `importProvidersFrom` function.
*
* @developerPreview
* @publicApi
*/
export type ImportProvidersSource =
Type<unknown>|ModuleWithProviders<unknown>|Array<ImportProvidersSource>;
/**
* Collects providers from all NgModules and standalone components, including transitively imported
* ones.
*
* Providers extracted via `importProvidersFrom` are only usable in an application injector or
* another environment injector (such as a route injector). They should not be used in component
* providers.
*
* More information about standalone components can be found in [this
* guide](guide/standalone-components).
*
* @usageNotes
* The results of the `importProvidersFrom` call can be used in the `bootstrapApplication` call:
*
* ```typescript
* await bootstrapApplication(RootComponent, {
* providers: [
* importProvidersFrom(NgModuleOne, NgModuleTwo)
* ]
* });
* ```
*
* You can also use the `importProvidersFrom` results in the `providers` field of a route, when a
* standalone component is used:
*
* ```typescript
* export const ROUTES: Route[] = [
* {
* path: 'foo',
* providers: [
* importProvidersFrom(NgModuleOne, NgModuleTwo)
* ],
* component: YourStandaloneComponent
* }
* ];
* ```
*
* @returns Collected providers from the specified list of types.
* @publicApi
* @developerPreview
*/
export function importProvidersFrom(...sources: ImportProvidersSource[]): EnvironmentProviders {
return {
ɵproviders: internalImportProvidersFrom(true, sources),
ɵfromNgModule: true,
} as InternalEnvironmentProviders;
}
export function internalImportProvidersFrom(
checkForStandaloneCmp: boolean, ...sources: ImportProvidersSource[]): Provider[] {
const providersOut: SingleProvider[] = [];
const dedup = new Set<Type<unknown>>(); // already seen types
let injectorTypesWithProviders: InjectorTypeWithProviders<unknown>[]|undefined;
deepForEach(sources, source => {
if ((typeof ngDevMode === 'undefined' || ngDevMode) && checkForStandaloneCmp) {
const cmpDef = getComponentDef(source);
if (cmpDef?.standalone) {
throw new RuntimeError(
RuntimeErrorCode.IMPORT_PROVIDERS_FROM_STANDALONE,
`Importing providers supports NgModule or ModuleWithProviders but got a standalone component "${
stringifyForError(source)}"`);
}
}
// Narrow `source` to access the internal type analogue for `ModuleWithProviders`.
const internalSource = source as Type<unknown>| InjectorTypeWithProviders<unknown>;
if (walkProviderTree(internalSource, providersOut, [], dedup)) {
injectorTypesWithProviders ||= [];
injectorTypesWithProviders.push(internalSource);
}
});
// Collect all providers from `ModuleWithProviders` types.
if (injectorTypesWithProviders !== undefined) {
processInjectorTypesWithProviders(injectorTypesWithProviders, providersOut);
}
return providersOut;
}
/**
* Collects all providers from the list of `ModuleWithProviders` and appends them to the provided
* array.
*/
function processInjectorTypesWithProviders(
typesWithProviders: InjectorTypeWithProviders<unknown>[], providersOut: Provider[]): void {
for (let i = 0; i < typesWithProviders.length; i++) {
const {ngModule, providers} = typesWithProviders[i];
deepForEachProvider(providers! as Array<Provider|InternalEnvironmentProviders>, provider => {
ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule);
providersOut.push(provider);
});
}
}
/**
* Internal type for a single provider in a deep provider array.
*/
export type SingleProvider = TypeProvider|ValueProvider|ClassProvider|ConstructorProvider|
ExistingProvider|FactoryProvider|StaticClassProvider;
/**
* The logic visits an `InjectorType`, an `InjectorTypeWithProviders`, or a standalone
* `ComponentType`, and all of its transitive providers and collects providers.
*
* If an `InjectorTypeWithProviders` that declares providers besides the type is specified,
* the function will return "true" to indicate that the providers of the type definition need
* to be processed. This allows us to process providers of injector types after all imports of
* an injector definition are processed. (following View Engine semantics: see FW-1349)
*/
export function walkProviderTree(
container: Type<unknown>|InjectorTypeWithProviders<unknown>, providersOut: SingleProvider[],
parents: Type<unknown>[],
dedup: Set<Type<unknown>>): container is InjectorTypeWithProviders<unknown> {
container = resolveForwardRef(container);
if (!container) return false;
// The actual type which had the definition. Usually `container`, but may be an unwrapped type
// from `InjectorTypeWithProviders`.
let defType: Type<unknown>|null = null;
let injDef = getInjectorDef(container);
const cmpDef = !injDef && getComponentDef(container);
if (!injDef && !cmpDef) {
// `container` is not an injector type or a component type. It might be:
// * An `InjectorTypeWithProviders` that wraps an injector type.
// * A standalone directive or pipe that got pulled in from a standalone component's
// dependencies.
// Try to unwrap it as an `InjectorTypeWithProviders` first.
const ngModule: Type<unknown>|undefined =
(container as InjectorTypeWithProviders<any>).ngModule as Type<unknown>| undefined;
injDef = getInjectorDef(ngModule);
if (injDef) {
defType = ngModule!;
} else {
// Not a component or injector type, so ignore it.
return false;
}
} else if (cmpDef && !cmpDef.standalone) {
return false;
} else {
defType = container as Type<unknown>;
}
// Check for circular dependencies.
if (ngDevMode && parents.indexOf(defType) !== -1) {
const defName = stringify(defType);
const path = parents.map(stringify);
throwCyclicDependencyError(defName, path);
}
// Check for multiple imports of the same module
const isDuplicate = dedup.has(defType);
if (cmpDef) {
if (isDuplicate) {
// This component definition has already been processed.
return false;
}
dedup.add(defType);
if (cmpDef.dependencies) {
const deps =
typeof cmpDef.dependencies === 'function' ? cmpDef.dependencies() : cmpDef.dependencies;
for (const dep of deps) {
walkProviderTree(dep, providersOut, parents, dedup);
}
}
} else if (injDef) {
// First, include providers from any imports.
if (injDef.imports != null && !isDuplicate) {
// Before processing defType's imports, add it to the set of parents. This way, if it ends
// up deeply importing itself, this can be detected.
ngDevMode && parents.push(defType);
// Add it to the set of dedups. This way we can detect multiple imports of the same module
dedup.add(defType);
let importTypesWithProviders: (InjectorTypeWithProviders<any>[])|undefined;
try {
deepForEach(injDef.imports, imported => {
if (walkProviderTree(imported, providersOut, parents, dedup)) {
importTypesWithProviders ||= [];
// If the processed import is an injector type with providers, we store it in the
// list of import types with providers, so that we can process those afterwards.
importTypesWithProviders.push(imported);
}
});
} finally {
// Remove it from the parents set when finished.
ngDevMode && parents.pop();
}
// Imports which are declared with providers (TypeWithProviders) need to be processed
// after all imported modules are processed. This is similar to how View Engine
// processes/merges module imports in the metadata resolver. See: FW-1349.
if (importTypesWithProviders !== undefined) {
processInjectorTypesWithProviders(importTypesWithProviders, providersOut);
}
}
if (!isDuplicate) {
// Track the InjectorType and add a provider for it.
// It's important that this is done after the def's imports.
const factory = getFactoryDef(defType) || (() => new defType!());
// Append extra providers to make more info available for consumers (to retrieve an injector
// type), as well as internally (to calculate an injection scope correctly and eagerly
// instantiate a `defType` when an injector is created).
providersOut.push(
// Provider to create `defType` using its factory.
{provide: defType, useFactory: factory, deps: EMPTY_ARRAY},
// Make this `defType` available to an internal logic that calculates injector scope.
{provide: INJECTOR_DEF_TYPES, useValue: defType, multi: true},
// Provider to eagerly instantiate `defType` via `ENVIRONMENT_INITIALIZER`.
{provide: ENVIRONMENT_INITIALIZER, useValue: () => inject(defType!), multi: true} //
);
}
// Next, include providers listed on the definition itself.
const defProviders = injDef.providers as Array<SingleProvider|InternalEnvironmentProviders>;
if (defProviders != null && !isDuplicate) {
const injectorType = container as InjectorType<any>;
deepForEachProvider(defProviders, provider => {
ngDevMode && validateProvider(provider as SingleProvider, defProviders, injectorType);
providersOut.push(provider as SingleProvider);
});
}
} else {
// Should not happen, but just in case.
return false;
}
return (
defType !== container &&
(container as InjectorTypeWithProviders<any>).providers !== undefined);
}
function validateProvider(
provider: SingleProvider, providers: Array<SingleProvider|InternalEnvironmentProviders>,
containerType: Type<unknown>): void {
if (isTypeProvider(provider) || isValueProvider(provider) || isFactoryProvider(provider) ||
isExistingProvider(provider)) {
return;
}
// Here we expect the provider to be a `useClass` provider (by elimination).
const classRef = resolveForwardRef(
provider && ((provider as StaticClassProvider | ClassProvider).useClass || provider.provide));
if (!classRef) {
throwInvalidProviderError(containerType, providers, provider);
}
}
function deepForEachProvider(
providers: Array<Provider|InternalEnvironmentProviders>,
fn: (provider: SingleProvider) => void): void {
for (let provider of providers) {
if (isEnvironmentProviders(provider)) {
provider = provider.ɵproviders;
}
if (Array.isArray(provider)) {
deepForEachProvider(provider, fn);
} else {
fn(provider);
}
}
}
export const USE_VALUE =
getClosureSafeProperty<ValueProvider>({provide: String, useValue: getClosureSafeProperty});
export function isValueProvider(value: SingleProvider): value is ValueProvider {
return value !== null && typeof value == 'object' && USE_VALUE in value;
}
export function isExistingProvider(value: SingleProvider): value is ExistingProvider {
return !!(value && (value as ExistingProvider).useExisting);
}
export function isFactoryProvider(value: SingleProvider): value is FactoryProvider {
return !!(value && (value as FactoryProvider).useFactory);
}
export function isTypeProvider(value: SingleProvider): value is TypeProvider {
return typeof value === 'function';
}
export function isClassProvider(value: SingleProvider): value is ClassProvider {
return !!(value as StaticClassProvider | ClassProvider).useClass;
}