mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): support standalone components in importProvidersFrom (#45687)
This commit refactors `importProvidersFrom` to support pulling providers from the dependencies of a standalone component, in addition to NgModules. Tests will be added in a future commit when standalone components can be created without calling private APIs. PR Close #45687
This commit is contained in:
committed by
Andrew Scott
parent
5a10fc4f82
commit
9f795134fa
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Type} from '../interface/type';
|
||||
import {getComponentDef} from '../render3/definition';
|
||||
import {getFactoryDef} from '../render3/definition_factory';
|
||||
import {throwCyclicDependencyError, throwInvalidProviderError} from '../render3/errors_di';
|
||||
import {deepForEach} from '../util/array_utils';
|
||||
@@ -22,16 +23,15 @@ import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, P
|
||||
import {INJECTOR_DEF_TYPES} from './internal_tokens';
|
||||
|
||||
/**
|
||||
* Collects providers from all NgModules, including transitively imported ones.
|
||||
* Collects providers from all NgModules and standalone components, including transitively imported
|
||||
* ones.
|
||||
*
|
||||
* @returns The list of collected providers from the specified list of NgModules.
|
||||
* @returns The list of collected providers from the specified list of types.
|
||||
* @publicApi
|
||||
*/
|
||||
export function importProvidersFrom(...injectorTypes: Array<Type<unknown>>): Provider[] {
|
||||
export function importProvidersFrom(...types: Array<Type<unknown>>): Provider[] {
|
||||
const providers: SingleProvider[] = [];
|
||||
deepForEach(
|
||||
injectorTypes,
|
||||
injectorDef => walkProviderTree(injectorDef as InjectorType<any>, providers, [], new Set()));
|
||||
deepForEach(types, type => walkProviderTree(type, providers, [], new Set()));
|
||||
return providers;
|
||||
}
|
||||
|
||||
@@ -42,10 +42,8 @@ export type SingleProvider = TypeProvider|ValueProvider|ClassProvider|Constructo
|
||||
ExistingProvider|FactoryProvider|StaticClassProvider;
|
||||
|
||||
/**
|
||||
* The logic visits an `InjectorType` or `InjectorTypeWithProviders` and all of its transitive
|
||||
* providers and invokes specified callbacks when:
|
||||
* - an injector type is visited (typically an NgModule)
|
||||
* - a provider is visited
|
||||
* 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
|
||||
@@ -53,28 +51,38 @@ export type SingleProvider = TypeProvider|ValueProvider|ClassProvider|Constructo
|
||||
* an injector definition are processed. (following View Engine semantics: see FW-1349)
|
||||
*/
|
||||
export function walkProviderTree(
|
||||
container: InjectorType<unknown>|InjectorTypeWithProviders<unknown>,
|
||||
providersOut: SingleProvider[], parents: InjectorType<unknown>[],
|
||||
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;
|
||||
|
||||
// Either the defOrWrappedDef is an InjectorType (with injector def) or an
|
||||
// InjectorDefTypeWithProviders (aka ModuleWithProviders). Detecting either is a megamorphic
|
||||
// read, so care is taken to only do the read once.
|
||||
// The actual type which had the definition. Usually `container`, but may be an unwrapped type
|
||||
// from `InjectorTypeWithProviders`.
|
||||
let defType: Type<unknown>|null = null;
|
||||
|
||||
// First attempt to read the injector def (`ɵinj`).
|
||||
let def = getInjectorDef(container);
|
||||
|
||||
// If that's not present, then attempt to read ngModule from the InjectorDefTypeWithProviders.
|
||||
const ngModule =
|
||||
(def == null) && (container as InjectorTypeWithProviders<any>).ngModule || undefined;
|
||||
|
||||
// Determine the InjectorType. In the case where `defOrWrappedDef` is an `InjectorType`,
|
||||
// then this is easy. In the case of an InjectorDefTypeWithProviders, then the definition type
|
||||
// is the `ngModule`.
|
||||
const defType: InjectorType<any> =
|
||||
(ngModule === undefined) ? (container as InjectorType<any>) : ngModule;
|
||||
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) {
|
||||
@@ -86,92 +94,98 @@ export function walkProviderTree(
|
||||
// Check for multiple imports of the same module
|
||||
const isDuplicate = dedup.has(defType);
|
||||
|
||||
// Finally, if defOrWrappedType was an `InjectorDefTypeWithProviders`, then the actual
|
||||
// `InjectorDef` is on its `ngModule`.
|
||||
if (ngModule !== undefined) {
|
||||
def = getInjectorDef(ngModule);
|
||||
}
|
||||
if (cmpDef) {
|
||||
if (isDuplicate) {
|
||||
// This component definition has already been processed.
|
||||
return false;
|
||||
}
|
||||
dedup.add(defType);
|
||||
|
||||
// If no definition was found, it might be from exports. Remove it.
|
||||
if (def == null) {
|
||||
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)) {
|
||||
if (importTypesWithProviders === undefined) 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) {
|
||||
for (let i = 0; i < importTypesWithProviders.length; i++) {
|
||||
const {ngModule, providers} = importTypesWithProviders[i];
|
||||
deepForEach(providers!, provider => {
|
||||
validateProvider(provider, providers || EMPTY_ARRAY, ngModule);
|
||||
providersOut.push(provider);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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!());
|
||||
|
||||
// Provider to create `defType` using its factory.
|
||||
providersOut.push({
|
||||
provide: defType,
|
||||
useFactory: factory,
|
||||
deps: EMPTY_ARRAY,
|
||||
});
|
||||
|
||||
providersOut.push({
|
||||
provide: INJECTOR_DEF_TYPES,
|
||||
useValue: defType,
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Provider to eagerly instantiate `defType` via `INJECTOR_INITIALIZER`.
|
||||
providersOut.push({
|
||||
provide: INJECTOR_INITIALIZER,
|
||||
useValue: () => inject(defType!),
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Next, include providers listed on the definition itself.
|
||||
const defProviders = injDef.providers;
|
||||
if (defProviders != null && !isDuplicate) {
|
||||
const injectorType = container as InjectorType<any>;
|
||||
deepForEach(defProviders, provider => {
|
||||
// TODO: fix cast
|
||||
validateProvider(provider, defProviders as any[], injectorType);
|
||||
providersOut.push(provider);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Should not happen, but just in case.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add providers in the same way that @NgModule resolution did:
|
||||
|
||||
// First, include providers from any imports.
|
||||
if (def.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(def.imports, imported => {
|
||||
if (walkProviderTree(imported, providersOut, parents, dedup)) {
|
||||
if (importTypesWithProviders === undefined) 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) {
|
||||
for (let i = 0; i < importTypesWithProviders.length; i++) {
|
||||
const {ngModule, providers} = importTypesWithProviders[i];
|
||||
deepForEach(providers!, provider => {
|
||||
validateProvider(provider, providers || EMPTY_ARRAY, ngModule);
|
||||
providersOut.push(provider);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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());
|
||||
|
||||
// Provider to create `defType` using its factory.
|
||||
providersOut.push({
|
||||
provide: defType,
|
||||
useFactory: factory,
|
||||
deps: EMPTY_ARRAY,
|
||||
});
|
||||
|
||||
providersOut.push({
|
||||
provide: INJECTOR_DEF_TYPES,
|
||||
useValue: defType,
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Provider to eagerly instantiate `defType` via `INJECTOR_INITIALIZER`.
|
||||
providersOut.push({
|
||||
provide: INJECTOR_INITIALIZER,
|
||||
useValue: () => inject(defType),
|
||||
multi: true,
|
||||
});
|
||||
|
||||
// Next, include providers listed on the definition itself.
|
||||
const defProviders = def.providers;
|
||||
if (defProviders != null && !isDuplicate) {
|
||||
const injectorType = container as InjectorType<any>;
|
||||
deepForEach(defProviders, provider => {
|
||||
// TODO: fix cast
|
||||
validateProvider(provider, defProviders as any[], injectorType);
|
||||
providersOut.push(provider);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
ngModule !== undefined &&
|
||||
defType !== container &&
|
||||
(container as InjectorTypeWithProviders<any>).providers !== undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../util/ng_dev_mode';
|
||||
import {RuntimeError, RuntimeErrorCode} from '../errors';
|
||||
import {OnDestroy} from '../interface/lifecycle_hooks';
|
||||
import {Type} from '../interface/type';
|
||||
import {getComponentDef} from '../render3/definition';
|
||||
import {FactoryFn, getFactoryDef} from '../render3/definition_factory';
|
||||
import {throwCyclicDependencyError, throwInvalidProviderError, throwMixedMultiProviderError} from '../render3/errors_di';
|
||||
import {flatten, newArray} from '../util/array_utils';
|
||||
|
||||
@@ -310,6 +310,8 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
|
||||
directiveDefs: null!, // assigned in noSideEffects
|
||||
pipeDefs: null!, // assigned in noSideEffects
|
||||
standalone: componentDefinition.standalone === true,
|
||||
dependencies:
|
||||
componentDefinition.standalone === true && componentDefinition.dependencies || null,
|
||||
selectors: componentDefinition.selectors || EMPTY_ARRAY,
|
||||
viewQuery: componentDefinition.viewQuery || null,
|
||||
features: componentDefinition.features as DirectiveDefFeature[] || null,
|
||||
|
||||
@@ -305,6 +305,11 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||
*/
|
||||
pipeDefs: PipeDefListOrFactory|null;
|
||||
|
||||
/**
|
||||
* Unfiltered list of all dependencies of a component, or `null` if none.
|
||||
*/
|
||||
dependencies: TypeOrFactory<DependencyTypeList>|null;
|
||||
|
||||
/**
|
||||
* The set of schemas that declare elements to be allowed in the component's template.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user