diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 903dc3160eb..5c730fa11a8 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -51,6 +51,15 @@ "packages/core/src/di/injector_compatibility.ts", "packages/core/src/di/injector.ts" ], + [ + "packages/core/src/di/create_injector.ts", + "packages/core/src/di/provider_collection.ts", + "packages/core/src/render3/definition.ts", + "packages/core/src/render3/interfaces/definition.ts", + "packages/core/src/render3/interfaces/node.ts", + "packages/core/src/render3/interfaces/view.ts", + "packages/core/src/di/injector.ts" + ], [ "packages/core/src/di/create_injector.ts", "packages/core/src/di/r3_injector.ts", diff --git a/goldens/public-api/core/index.md b/goldens/public-api/core/index.md index 1dec55ef901..18beec2191f 100644 --- a/goldens/public-api/core/index.md +++ b/goldens/public-api/core/index.md @@ -578,7 +578,7 @@ export interface HostListenerDecorator { } // @public -export function importProvidersFrom(...injectorTypes: Array>): Provider[]; +export function importProvidersFrom(...types: Array>): Provider[]; // @public export interface Inject { diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index 196030d9f44..ce15a08f6e0 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -33,7 +33,7 @@ "cli-hello-world-lazy": { "uncompressed": { "runtime": 2835, - "main": 235837, + "main": 236444, "polyfills": 33842, "src_app_lazy_lazy_module_ts": 795 } diff --git a/packages/core/src/di/provider_collection.ts b/packages/core/src/di/provider_collection.ts index 71abd5d34e7..22594c1f847 100644 --- a/packages/core/src/di/provider_collection.ts +++ b/packages/core/src/di/provider_collection.ts @@ -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>): Provider[] { +export function importProvidersFrom(...types: Array>): Provider[] { const providers: SingleProvider[] = []; - deepForEach( - injectorTypes, - injectorDef => walkProviderTree(injectorDef as InjectorType, 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|InjectorTypeWithProviders, - providersOut: SingleProvider[], parents: InjectorType[], + container: Type|InjectorTypeWithProviders, providersOut: SingleProvider[], + parents: Type[], dedup: Set>): container is InjectorTypeWithProviders { 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|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).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 = - (ngModule === undefined) ? (container as InjectorType) : 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|undefined = + (container as InjectorTypeWithProviders).ngModule as Type| 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; + } // 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[])|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; + 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[])|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; - deepForEach(defProviders, provider => { - // TODO: fix cast - validateProvider(provider, defProviders as any[], injectorType); - providersOut.push(provider); - }); - } - return ( - ngModule !== undefined && + defType !== container && (container as InjectorTypeWithProviders).providers !== undefined); } diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 7869323cc93..4a3194bb652 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -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'; diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 051af362d89..f59173bacbf 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -310,6 +310,8 @@ export function ɵɵdefineComponent(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, diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 877e0883d94..57d7f3cd0d7 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -305,6 +305,11 @@ export interface ComponentDef extends DirectiveDef { */ pipeDefs: PipeDefListOrFactory|null; + /** + * Unfiltered list of all dependencies of a component, or `null` if none. + */ + dependencies: TypeOrFactory|null; + /** * The set of schemas that declare elements to be allowed in the component's template. */ diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 276007b2e45..c61903f5971 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -29,6 +29,9 @@ { "name": "NEW_LINE" }, + { + "name": "NG_COMP_DEF" + }, { "name": "NG_FACTORY_DEF" },