From ecae525970a2eca1d758c538c4cb0927e983dc8f Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 6 Apr 2026 09:04:05 -0700 Subject: [PATCH] Revert "refactor(core): remove ComponentFactoryResolver & ComponentFactory from the api surface" This reverts commit 9d76ac82290e047f1481fb38bd95233e951a77de. g3 cleanup not complete --- goldens/public-api/core/index.api.md | 29 +++ .../core/src/application/application_ref.ts | 78 ++++++-- packages/core/src/linker.ts | 4 +- packages/core/src/linker/component_factory.ts | 5 + .../src/linker/component_factory_resolver.ts | 5 + .../core/src/linker/view_container_ref.ts | 169 ++++++++++++------ .../acceptance/view_container_ref_spec.ts | 39 +++- packages/core/test/application_ref_spec.ts | 58 ++++++ .../bundle.golden_symbols.json | 1 + .../forms_reactive/bundle.golden_symbols.json | 1 + .../bundle.golden_symbols.json | 1 + .../router/bundle.golden_symbols.json | 1 + packages/core/test/linker/integration_spec.ts | 10 +- .../core/test/render3/reactive_safety_spec.ts | 11 +- .../src/component-factory-strategy.ts | 14 +- packages/elements/src/utils.ts | 2 +- packages/elements/test/slots_spec.ts | 2 +- .../test/testing_public_spec.ts | 11 ++ .../src/common/src/downgrade_component.ts | 8 +- .../common/src/downgrade_component_adapter.ts | 8 +- .../test/downgrade_component_adapter_spec.ts | 2 +- 21 files changed, 364 insertions(+), 95 deletions(-) diff --git a/goldens/public-api/core/index.api.md b/goldens/public-api/core/index.api.md index 10285c88f03..bcea6c7a0dd 100644 --- a/goldens/public-api/core/index.api.md +++ b/goldens/public-api/core/index.api.md @@ -134,6 +134,8 @@ export class ApplicationRef { constructor(); attachView(viewRef: ViewRef): void; bootstrap(component: Type, rootSelectorOrNode?: string | any): ComponentRef; + // @deprecated + bootstrap(componentFactory: ComponentFactory, rootSelectorOrNode?: string | any): ComponentRef; readonly components: ComponentRef[]; readonly componentTypes: Type[]; destroy(): void; @@ -291,6 +293,31 @@ export interface ComponentDecorator { new (obj: Component): Component; } +// @public @deprecated +export abstract class ComponentFactory { + abstract get componentType(): Type; + abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, environmentInjector?: EnvironmentInjector | NgModuleRef, directives?: (Type | DirectiveWithBindings)[], bindings?: Binding[]): ComponentRef; + abstract get inputs(): { + propName: string; + templateName: string; + transform?: (value: any) => any; + isSignal: boolean; + }[]; + abstract get ngContentSelectors(): string[]; + abstract get outputs(): { + propName: string; + templateName: string; + }[]; + abstract get selector(): string; +} + +// @public @deprecated +export abstract class ComponentFactoryResolver { + // (undocumented) + static NULL: ComponentFactoryResolver; + abstract resolveComponentFactory(component: Type): ComponentFactory; +} + // @public export interface ComponentMirror { get inputs(): ReadonlyArray<{ @@ -2045,6 +2072,8 @@ export abstract class ViewContainerRef { directives?: (Type | DirectiveWithBindings)[]; bindings?: Binding[]; }): ComponentRef; + // @deprecated + abstract createComponent(componentFactory: ComponentFactory, index?: number, injector?: Injector, projectableNodes?: any[][], environmentInjector?: EnvironmentInjector | NgModuleRef, directives?: (Type | DirectiveWithBindings)[], bindings?: Binding[]): ComponentRef; abstract createEmbeddedView(templateRef: TemplateRef, context?: C, options?: { index?: number; injector?: Injector; diff --git a/packages/core/src/application/application_ref.ts b/packages/core/src/application/application_ref.ts index 9ecb9f7535d..b52c6d7b33c 100644 --- a/packages/core/src/application/application_ref.ts +++ b/packages/core/src/application/application_ref.ts @@ -10,13 +10,13 @@ import '../util/ng_hmr_mode'; import '../util/ng_jit_mode'; import '../util/ng_server_mode'; -import {type Observable, Subject, type Subscription} from 'rxjs'; -import {map} from 'rxjs/operators'; import { - getActiveConsumer, setActiveConsumer, + getActiveConsumer, setThrowInvalidWriteToSignalError, } from '../../primitives/signals'; +import {type Observable, Subject, type Subscription} from 'rxjs'; +import {map} from 'rxjs/operators'; import {ZONELESS_ENABLED} from '../change_detection/scheduling/zoneless_scheduling'; import {Console} from '../console'; @@ -25,8 +25,8 @@ import {Injectable} from '../di/injectable'; import {InjectionToken} from '../di/injection_token'; import {Injector} from '../di/injector'; import {EnvironmentInjector, type R3Injector} from '../di/r3_injector'; -import {INTERNAL_APPLICATION_ERROR_HANDLER} from '../error_handler'; import {formatRuntimeError, RuntimeError, RuntimeErrorCode} from '../errors'; +import {INTERNAL_APPLICATION_ERROR_HANDLER} from '../error_handler'; import {Type} from '../interface/type'; import {ComponentFactory, ComponentRef} from '../linker/component_factory'; import {ComponentFactoryResolver} from '../linker/component_factory_resolver'; @@ -44,10 +44,10 @@ import {ViewRef as InternalViewRef} from '../render3/view_ref'; import {TESTABILITY} from '../testability/testability'; import {NgZone} from '../zone/ng_zone'; -import {ProfilerEvent} from '../../primitives/devtools'; import {profiler} from '../render3/profiler'; -import {isReactiveLViewConsumer} from '../render3/reactive_lview_consumer'; +import {ProfilerEvent} from '../../primitives/devtools'; import {EffectScheduler} from '../render3/reactivity/root_effect_scheduler'; +import {isReactiveLViewConsumer} from '../render3/reactive_lview_consumer'; import {ApplicationInitStatus} from './application_init'; import {TracingAction, TracingService, TracingSnapshot} from './tracing'; @@ -441,13 +441,61 @@ export class ApplicationRef { * While in this example, we are providing reference to a DOM node. * * {@example core/ts/platform/platform.ts region='domNode'} + * + * @deprecated Passing Component factories as the `Application.bootstrap` function argument is + * deprecated. Pass Component Types instead. */ - bootstrap(component: Type, rootSelectorOrNode?: string | any): ComponentRef { - return this.bootstrapImpl(component, rootSelectorOrNode); + bootstrap( + componentFactory: ComponentFactory, + rootSelectorOrNode?: string | any, + ): ComponentRef; + + /** + * Bootstrap a component onto the element identified by its selector or, optionally, to a + * specified element. + * + * @usageNotes + * ### Bootstrap process + * + * When bootstrapping a component, Angular mounts it onto a target DOM element + * and kicks off automatic change detection. The target DOM element can be + * provided using the `rootSelectorOrNode` argument. + * + * If the target DOM element is not provided, Angular tries to find one on a page + * using the `selector` of the component that is being bootstrapped + * (first matched element is used). + * + * ### Example + * + * Generally, we define the component to bootstrap in the `bootstrap` array of `NgModule`, + * but it requires us to know the component while writing the application code. + * + * Imagine a situation where we have to wait for an API call to decide about the component to + * bootstrap. We can use the `ngDoBootstrap` hook of the `NgModule` and call this method to + * dynamically bootstrap a component. + * + * {@example core/ts/platform/platform.ts region='componentSelector'} + * + * Optionally, a component can be mounted onto a DOM element that does not match the + * selector of the bootstrapped component. + * + * In the following example, we are providing a CSS selector to match the target element. + * + * {@example core/ts/platform/platform.ts region='cssSelector'} + * + * While in this example, we are providing reference to a DOM node. + * + * {@example core/ts/platform/platform.ts region='domNode'} + */ + bootstrap( + componentOrFactory: ComponentFactory | Type, + rootSelectorOrNode?: string | any, + ): ComponentRef { + return this.bootstrapImpl(componentOrFactory, rootSelectorOrNode); } private bootstrapImpl( - component: Type, + componentOrFactory: ComponentFactory | Type, rootSelectorOrNode?: string | any, injector: Injector = Injector.NULL, ): ComponentRef { @@ -456,12 +504,13 @@ export class ApplicationRef { profiler(ProfilerEvent.BootstrapComponentStart); (typeof ngDevMode === 'undefined' || ngDevMode) && warnIfDestroyed(this._destroyed); + const isComponentFactory = componentOrFactory instanceof ComponentFactory; const initStatus = this._injector.get(ApplicationInitStatus); if (!initStatus.done) { let errorMessage = ''; if (typeof ngDevMode === 'undefined' || ngDevMode) { - const standalone = isStandalone(component); + const standalone = !isComponentFactory && isStandalone(componentOrFactory); errorMessage = 'Cannot bootstrap as there are still asynchronous initializers running.' + (standalone @@ -471,8 +520,13 @@ export class ApplicationRef { throw new RuntimeError(RuntimeErrorCode.ASYNC_INITIALIZERS_STILL_RUNNING, errorMessage); } - const resolver = this._injector.get(ComponentFactoryResolver); - const componentFactory = resolver.resolveComponentFactory(component)!; + let componentFactory: ComponentFactory; + if (isComponentFactory) { + componentFactory = componentOrFactory; + } else { + const resolver = this._injector.get(ComponentFactoryResolver); + componentFactory = resolver.resolveComponentFactory(componentOrFactory)!; + } this.componentTypes.push(componentFactory.componentType); // Create a factory associated with the current module if it's not bound to some other diff --git a/packages/core/src/linker.ts b/packages/core/src/linker.ts index 1222b804960..ba6dcb1800f 100644 --- a/packages/core/src/linker.ts +++ b/packages/core/src/linker.ts @@ -14,8 +14,8 @@ export { CompilerOptions, ModuleWithComponentFactories, } from './linker/compiler'; -export {ComponentRef, ComponentFactory as ɵComponentFactory} from './linker/component_factory'; -export {ComponentFactoryResolver as ɵComponentFactoryResolver} from './linker/component_factory_resolver'; +export {ComponentFactory, ComponentRef} from './linker/component_factory'; +export {ComponentFactoryResolver} from './linker/component_factory_resolver'; export {DestroyRef} from './linker/destroy_ref'; export {ElementRef} from './linker/element_ref'; export {NgModuleFactory, NgModuleRef} from './linker/ng_module_factory'; diff --git a/packages/core/src/linker/component_factory.ts b/packages/core/src/linker/component_factory.ts index 0b8f7bc8cec..51c29b1d7d5 100644 --- a/packages/core/src/linker/component_factory.ts +++ b/packages/core/src/linker/component_factory.ts @@ -85,6 +85,11 @@ export abstract class ComponentRef { * Base class for a factory that can create a component dynamically. * Instantiate a factory for a given type of component with `resolveComponentFactory()`. * Use the resulting `ComponentFactory.create()` method to create a component of that type. + * + * @publicApi + * + * @deprecated Angular no longer requires Component factories. Please use other APIs where + * Component class can be used directly. */ export abstract class ComponentFactory { /** diff --git a/packages/core/src/linker/component_factory_resolver.ts b/packages/core/src/linker/component_factory_resolver.ts index 6f5b544c28a..be12c38c8d0 100644 --- a/packages/core/src/linker/component_factory_resolver.ts +++ b/packages/core/src/linker/component_factory_resolver.ts @@ -32,6 +32,11 @@ class _NullComponentFactoryResolver implements ComponentFactoryResolver { * Note: since v13, dynamic component creation via * [`ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent) * does **not** require resolving component factory: component class can be used directly. + * + * @publicApi + * + * @deprecated Angular no longer requires Component factories. Please use other APIs where + * Component class can be used directly. */ export abstract class ComponentFactoryResolver { static NULL: ComponentFactoryResolver = /* @__PURE__ */ new _NullComponentFactoryResolver(); diff --git a/packages/core/src/linker/view_container_ref.ts b/packages/core/src/linker/view_container_ref.ts index 2e5c5b8f216..fd9f0280d0a 100644 --- a/packages/core/src/linker/view_container_ref.ts +++ b/packages/core/src/linker/view_container_ref.ts @@ -17,7 +17,7 @@ import { markRNodeAsClaimedByHydration, } from '../hydration/utils'; import {findMatchingDehydratedView, locateDehydratedViewsInContainer} from '../hydration/views'; -import {Type} from '../interface/type'; +import {isType, Type} from '../interface/type'; import {assertNodeInjector} from '../render3/assert'; import {ComponentFactory as R3ComponentFactory} from '../render3/component_ref'; import {getComponentDef} from '../render3/def_getters'; @@ -74,7 +74,7 @@ import {RuntimeError, RuntimeErrorCode} from '../errors'; import {Binding, DirectiveWithBindings} from '../render3/dynamic_bindings'; import {addToEndOfViewTree} from '../render3/view/construction'; import {addLViewToLContainer, createLContainer, detachView} from '../render3/view/container'; -import {ComponentRef} from './component_factory'; +import {ComponentFactory, ComponentRef} from './component_factory'; import {createElementRef, ElementRef} from './element_ref'; import {NgModuleRef} from './ng_module_factory'; import {TemplateRef} from './template_ref'; @@ -244,6 +244,36 @@ export abstract class ViewContainerRef { }, ): ComponentRef; + /** + * Instantiates a single component and inserts its host view into this container. + * + * @param componentFactory Component factory to use. + * @param index The index at which to insert the new component's host view into this container. + * If not specified, appends the new view as the last entry. + * @param injector The injector to use as the parent for the new component. + * @param projectableNodes List of DOM nodes that should be projected through + * [``](api/core/ng-content) of the new component instance. + * @param ngModuleRef An instance of the NgModuleRef that represent an NgModule. + * This information is used to retrieve corresponding NgModule injector. + * @param directives Directives that should be applied to the component. + * @param bindings Bindings that should be applied to the component. + * + * @returns The new `ComponentRef` which contains the component instance and the host view. + * + * @deprecated Angular no longer requires component factories to dynamically create components. + * Use different signature of the `createComponent` method, which allows passing + * Component class directly. + */ + abstract createComponent( + componentFactory: ComponentFactory, + index?: number, + injector?: Injector, + projectableNodes?: any[][], + environmentInjector?: EnvironmentInjector | NgModuleRef, + directives?: (Type | DirectiveWithBindings)[], + bindings?: Binding[], + ): ComponentRef; + /** * Inserts a view into this container. * @param viewRef The view to insert. @@ -405,63 +435,102 @@ class R3ViewContainerRef extends ViewContainerRef { bindings?: Binding[]; }, ): ComponentRef; + /** + * @deprecated Angular no longer requires component factories to dynamically create components. + * Use different signature of the `createComponent` method, which allows passing + * Component class directly. + */ override createComponent( - componentType: Type, - opts?: { - index?: number; - injector?: Injector; - ngModuleRef?: NgModuleRef; - environmentInjector?: EnvironmentInjector | NgModuleRef; - projectableNodes?: Node[][]; - directives?: (Type | DirectiveWithBindings)[]; - bindings?: Binding[]; - }, + componentFactory: ComponentFactory, + index?: number | undefined, + injector?: Injector | undefined, + projectableNodes?: any[][] | undefined, + environmentInjector?: EnvironmentInjector | NgModuleRef | undefined, + directives?: (Type | DirectiveWithBindings)[], + bindings?: Binding[], + ): ComponentRef; + override createComponent( + componentFactoryOrType: ComponentFactory | Type, + indexOrOptions?: + | number + | undefined + | { + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + environmentInjector?: EnvironmentInjector | NgModuleRef; + projectableNodes?: Node[][]; + directives?: (Type | DirectiveWithBindings)[]; + bindings?: Binding[]; + }, injector?: Injector | undefined, projectableNodes?: any[][] | undefined, environmentInjector?: EnvironmentInjector | NgModuleRef | undefined, directives?: (Type | DirectiveWithBindings)[], bindings?: Binding[], ): ComponentRef { + const isComponentFactory = componentFactoryOrType && !isType(componentFactoryOrType); let index: number | undefined; - if (ngDevMode) { - assertDefined( - getComponentDef(componentType), - `Provided Component class doesn't contain Component definition. ` + - `Please check whether provided class has @Component decorator.`, - ); - assertEqual( - typeof opts !== 'number', - true, - 'It looks like Component type was provided as the first argument ' + - "and a number (representing an index at which to insert the new component's " + - 'host view into this container as the second argument. This combination of arguments ' + - 'is incompatible. Please use an object as the second argument instead.', - ); + // This function supports 2 signatures and we need to handle options correctly for both: + // 1. When first argument is a Component type. This signature also requires extra + // options to be provided as object (more ergonomic option). + // 2. First argument is a Component factory. In this case extra options are represented as + // positional arguments. This signature is less ergonomic and will be deprecated. + if (isComponentFactory) { + if (ngDevMode) { + assertEqual( + typeof indexOrOptions !== 'object', + true, + 'It looks like Component factory was provided as the first argument ' + + 'and an options object as the second argument. This combination of arguments ' + + 'is incompatible. You can either change the first argument to provide Component ' + + 'type or change the second argument to be a number (representing an index at ' + + "which to insert the new component's host view into this container)", + ); + } + index = indexOrOptions as number | undefined; + } else { + if (ngDevMode) { + assertDefined( + getComponentDef(componentFactoryOrType), + `Provided Component class doesn't contain Component definition. ` + + `Please check whether provided class has @Component decorator.`, + ); + assertEqual( + typeof indexOrOptions !== 'number', + true, + 'It looks like Component type was provided as the first argument ' + + "and a number (representing an index at which to insert the new component's " + + 'host view into this container as the second argument. This combination of arguments ' + + 'is incompatible. Please use an object as the second argument instead.', + ); + } + const options = (indexOrOptions || {}) as { + index?: number; + injector?: Injector; + ngModuleRef?: NgModuleRef; + environmentInjector?: EnvironmentInjector | NgModuleRef; + projectableNodes?: Node[][]; + directives?: (Type | DirectiveWithBindings)[]; + bindings?: Binding[]; + }; + if (ngDevMode && options.environmentInjector && options.ngModuleRef) { + throwError( + `Cannot pass both environmentInjector and ngModuleRef options to createComponent().`, + ); + } + index = options.index; + injector = options.injector; + projectableNodes = options.projectableNodes; + environmentInjector = options.environmentInjector || options.ngModuleRef; + directives = options.directives; + bindings = options.bindings; } - const options = (opts || {}) as { - index?: number; - injector?: Injector; - ngModuleRef?: NgModuleRef; - environmentInjector?: EnvironmentInjector | NgModuleRef; - projectableNodes?: Node[][]; - directives?: (Type | DirectiveWithBindings)[]; - bindings?: Binding[]; - }; - if (ngDevMode && options.environmentInjector && options.ngModuleRef) { - throwError( - `Cannot pass both environmentInjector and ngModuleRef options to createComponent().`, - ); - } - index = options.index; - injector = options.injector; - projectableNodes = options.projectableNodes; - environmentInjector = options.environmentInjector || options.ngModuleRef; - directives = options.directives; - bindings = options.bindings; - - const componentFactory = new R3ComponentFactory(getComponentDef(componentType)!); + const componentFactory: ComponentFactory = isComponentFactory + ? (componentFactoryOrType as ComponentFactory) + : new R3ComponentFactory(getComponentDef(componentFactoryOrType)!); const contextInjector = injector || this.parentInjector; // If an `NgModuleRef` is not provided explicitly, try retrieving it from the DI tree. @@ -482,7 +551,7 @@ class R3ViewContainerRef extends ViewContainerRef { // NgModule outside of a module tree). Instead, we always use `ViewContainerRef`'s parent // injector, which is normally connected to the DI tree, which includes module injector // subtree. - const _injector = this.parentInjector; + const _injector = isComponentFactory ? contextInjector : this.parentInjector; // DO NOT REFACTOR. The code here used to have a `injector.get(NgModuleRef, null) || // undefined` expression which seems to cause internal google apps to fail. This is documented @@ -509,7 +578,7 @@ class R3ViewContainerRef extends ViewContainerRef { index, shouldAddViewToDom(this._hostTNode, dehydratedView), ); - return componentRef as ComponentRef; + return componentRef; } override insert(viewRef: ViewRef, index?: number): ViewRef { diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index f349555a398..c24b4383196 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -13,7 +13,6 @@ import {By, DomSanitizer} from '@angular/platform-browser'; import {expect} from '@angular/private/testing/matchers'; import {ANIMATION_QUEUE} from '../../src/animation/queue'; import { - ChangeDetectionStrategy, ChangeDetectorRef, Compiler, Component, @@ -48,6 +47,7 @@ import { ViewChildren, ViewContainerRef, ɵsetDocument, + ChangeDetectionStrategy, } from '../../src/core'; import {ComponentFixture, TestBed, TestComponentRenderer} from '../../testing'; @@ -1671,6 +1671,43 @@ describe('ViewContainerRef', () => { componentRef.destroy(); }); + it('should be compatible with componentRef generated via TestBed.createComponent in component factory', () => { + @Component({ + selector: 'child', + template: `Child Component`, + standalone: false, + + changeDetection: ChangeDetectionStrategy.Eager, + }) + class Child {} + + @Component({ + selector: 'comp', + template: '', + standalone: false, + + changeDetection: ChangeDetectionStrategy.Eager, + }) + class Comp { + @ViewChild('ref', {read: ViewContainerRef, static: true}) + viewContainerRef!: ViewContainerRef; + + ngOnInit() { + const makeComponentFactory = (componentType: any) => ({ + create: () => TestBed.createComponent(componentType).componentRef, + }); + this.viewContainerRef.createComponent(makeComponentFactory(Child) as any); + } + } + + TestBed.configureTestingModule({declarations: [Comp, Child]}); + + const fixture = TestBed.createComponent(Comp); + fixture.detectChanges(); + + expect(fixture.debugElement.nativeElement.innerHTML).toContain('Child Component'); + }); + it('should return ComponentRef with ChangeDetectorRef attached to root view', () => { @Component({ selector: 'dynamic-cmp', diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index 0d6112d0080..deba0adf410 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -19,9 +19,11 @@ import { APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ChangeDetectionStrategy, + Compiler, Component, DestroyRef, EnvironmentInjector, + InjectionToken, Injector, LOCALE_ID, NgModule, @@ -114,6 +116,62 @@ describe('bootstrap', () => { return MyModule; } + it('should bootstrap a component from a child module', waitForAsync( + inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + standalone: false, + }) + class SomeComponent {} + + const helloToken = new InjectionToken('hello'); + + @NgModule({ + providers: [{provide: helloToken, useValue: 'component'}], + declarations: [SomeComponent], + }) + class SomeModule {} + + createRootEl(); + const modFactory = compiler.compileModuleSync(SomeModule); + const module = modFactory.create(TestBed.inject(Injector)); + const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent); + const component = app.bootstrap(cmpFactory); + + // The component should see the child module providers + expect(component.injector.get(helloToken)).toEqual('component'); + }), + )); + + it('should bootstrap a component with a custom selector', waitForAsync( + inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { + @Component({ + selector: 'bootstrap-app', + template: '', + standalone: false, + }) + class SomeComponent {} + + const helloToken = new InjectionToken('hello'); + + @NgModule({ + providers: [{provide: helloToken, useValue: 'component'}], + declarations: [SomeComponent], + }) + class SomeModule {} + + createRootEl('custom-selector'); + const modFactory = compiler.compileModuleSync(SomeModule); + const module = modFactory.create(TestBed.inject(Injector)); + const cmpFactory = module.componentFactoryResolver.resolveComponentFactory(SomeComponent); + const component = app.bootstrap(cmpFactory, 'custom-selector'); + + // The component should see the child module providers + expect(component.injector.get(helloToken)).toEqual('component'); + }), + )); + describe('ApplicationRef', () => { beforeEach(async () => { TestBed.configureTestingModule({ diff --git a/packages/core/test/bundling/create_component/bundle.golden_symbols.json b/packages/core/test/bundling/create_component/bundle.golden_symbols.json index 9614a27e99d..c8d0d191498 100644 --- a/packages/core/test/bundling/create_component/bundle.golden_symbols.json +++ b/packages/core/test/bundling/create_component/bundle.golden_symbols.json @@ -543,6 +543,7 @@ "isSubscriber", "isSubscription", "isTemplateNode", + "isType", "isTypeProvider", "isValidLink", "isValueProvider", diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 6cbc835a67b..c4c6b9c925b 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -783,6 +783,7 @@ "isSubscriber", "isSubscription", "isTemplateNode", + "isType", "isTypeProvider", "isValidLink", "isValidatorFn", diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index de67197ee8d..7cc6fa65bc7 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -777,6 +777,7 @@ "isSubscriber", "isSubscription", "isTemplateNode", + "isType", "isTypeProvider", "isValidLink", "isValidatorFn", diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 30c31b1ee9b..effaa2cef02 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -864,6 +864,7 @@ "isSubscriber", "isSubscription", "isTemplateNode", + "isType", "isTypeProvider", "isUrlTree", "isValidLink", diff --git a/packages/core/test/linker/integration_spec.ts b/packages/core/test/linker/integration_spec.ts index fbb0544c571..a403ed5e92f 100644 --- a/packages/core/test/linker/integration_spec.ts +++ b/packages/core/test/linker/integration_spec.ts @@ -18,6 +18,7 @@ import { Attribute, Compiler, Component, + ComponentFactory, ComponentRef, ContentChildren, createComponent, @@ -1126,13 +1127,14 @@ describe('integration tests', function () { imports: [RootModule], }).createComponent(RootComp); const compiler = TestBed.inject(Compiler); - const myCompFactory = - compiler.compileModuleAndAllComponentsSync(MyModule).componentFactories[0]; + const myCompFactory = >( + compiler.compileModuleAndAllComponentsSync(MyModule).componentFactories[0] + ); // Note: the ComponentFactory was created directly via the compiler, i.e. it // does not have an association to an NgModuleRef. // -> expect the providers of the module that the view container belongs to. - const compRef = myCompFactory.create(compFixture.componentInstance.vc.injector); + const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory); expect(compRef.instance.someToken).toBe('someRootValue'); }); @@ -1220,7 +1222,7 @@ describe('integration tests', function () { // Note: MyComp was declared as entryComponent in MyModule, // and we don't pass an explicit ModuleRef to the createComponent call. // -> expect the providers of MyModule! - const compRef = myCompFactory.create(compFixture.componentInstance.vc.injector); + const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory); expect(compRef.instance.someToken).toBe('someValue'); }); }); diff --git a/packages/core/test/render3/reactive_safety_spec.ts b/packages/core/test/render3/reactive_safety_spec.ts index fe74900b179..54b389104d8 100644 --- a/packages/core/test/render3/reactive_safety_spec.ts +++ b/packages/core/test/render3/reactive_safety_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -import {getActiveConsumer} from '../../primitives/signals'; import { Component, + ComponentFactoryResolver, createComponent, createEnvironmentInjector, effect, @@ -24,6 +24,7 @@ import { ViewChild, ViewContainerRef, } from '../../src/core'; +import {getActiveConsumer} from '../../primitives/signals'; import {createInjector} from '../../src/di/create_injector'; import {TestBed} from '../../testing'; @@ -87,10 +88,10 @@ describe('reactive safety', () => { } } - const environmentInjector = TestBed.inject(EnvironmentInjector); - expectNotToThrowInReactiveContext(() => { - createComponent(TestCmp, {environmentInjector}); - }); + const injector = TestBed.inject(EnvironmentInjector); + const resolver = TestBed.inject(ComponentFactoryResolver); + const factory = resolver.resolveComponentFactory(TestCmp); + expectNotToThrowInReactiveContext(() => factory.create(injector)); }); it('should be safe to flip @if to true', () => { diff --git a/packages/elements/src/component-factory-strategy.ts b/packages/elements/src/component-factory-strategy.ts index a462d99c08e..ac34c7e1767 100644 --- a/packages/elements/src/component-factory-strategy.ts +++ b/packages/elements/src/component-factory-strategy.ts @@ -11,19 +11,19 @@ import type {} from 'zone.js'; import { ApplicationRef, - ɵChangeDetectionScheduler as ChangeDetectionScheduler, - ɵComponentFactory as ComponentFactory, - ɵComponentFactoryResolver as ComponentFactoryResolver, + ComponentFactory, + ComponentFactoryResolver, ComponentRef, EventEmitter, Injector, + NgZone, + Type, + ɵChangeDetectionScheduler as ChangeDetectionScheduler, + ɵNotificationSource as NotificationSource, + ɵViewRef as ViewRef, ɵisViewDirty as isViewDirty, ɵmarkForRefresh as markForRefresh, - NgZone, - ɵNotificationSource as NotificationSource, OutputRef, - Type, - ɵViewRef as ViewRef, } from '@angular/core'; import {merge, Observable, ReplaySubject} from 'rxjs'; import {switchMap} from 'rxjs/operators'; diff --git a/packages/elements/src/utils.ts b/packages/elements/src/utils.ts index 1af409ff5a3..32dd04af619 100644 --- a/packages/elements/src/utils.ts +++ b/packages/elements/src/utils.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {ɵComponentFactoryResolver as ComponentFactoryResolver, Injector, Type} from '@angular/core'; +import {ComponentFactoryResolver, Injector, Type} from '@angular/core'; /** * Provide methods for scheduling the execution of a callback. diff --git a/packages/elements/test/slots_spec.ts b/packages/elements/test/slots_spec.ts index 1036d8b8c8c..6eabbdc3a99 100644 --- a/packages/elements/test/slots_spec.ts +++ b/packages/elements/test/slots_spec.ts @@ -8,7 +8,7 @@ import { Component, - ɵComponentFactoryResolver as ComponentFactoryResolver, + ComponentFactoryResolver, destroyPlatform, EventEmitter, Input, diff --git a/packages/platform-browser/test/testing_public_spec.ts b/packages/platform-browser/test/testing_public_spec.ts index f5c3e43cbab..095e6ef6f99 100644 --- a/packages/platform-browser/test/testing_public_spec.ts +++ b/packages/platform-browser/test/testing_public_spec.ts @@ -11,6 +11,7 @@ import { ChangeDetectionStrategy, Compiler, Component, + ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, Inject, @@ -523,6 +524,16 @@ describe('public testing API', () => { }); describe('overriding providers', () => { + describe('in core', () => { + it('ComponentFactoryResolver', () => { + const componentFactoryMock = jasmine.createSpyObj('componentFactory', [ + 'resolveComponentFactory', + ]); + TestBed.overrideProvider(ComponentFactoryResolver, {useValue: componentFactoryMock}); + expect(TestBed.inject(ComponentFactoryResolver)).toEqual(componentFactoryMock); + }); + }); + describe('in NgModules', () => { it('should support useValue', () => { TestBed.configureTestingModule({ diff --git a/packages/upgrade/src/common/src/downgrade_component.ts b/packages/upgrade/src/common/src/downgrade_component.ts index d422e0d57f1..5cb557da72e 100644 --- a/packages/upgrade/src/common/src/downgrade_component.ts +++ b/packages/upgrade/src/common/src/downgrade_component.ts @@ -6,13 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ɵComponentFactory as ComponentFactory, - ɵComponentFactoryResolver as ComponentFactoryResolver, - Injector, - NgZone, - Type, -} from '@angular/core'; +import {ComponentFactory, ComponentFactoryResolver, Injector, NgZone, Type} from '@angular/core'; import { IAnnotatedFunction, diff --git a/packages/upgrade/src/common/src/downgrade_component_adapter.ts b/packages/upgrade/src/common/src/downgrade_component_adapter.ts index 1e858febc67..e4ccf4c7b4d 100644 --- a/packages/upgrade/src/common/src/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/src/downgrade_component_adapter.ts @@ -9,19 +9,19 @@ import { ApplicationRef, ChangeDetectorRef, - ɵComponentFactory as ComponentFactory, + ComponentFactory, ComponentRef, type EventEmitter, Injector, - type ɵInputSignalNode as InputSignalNode, OnChanges, - type OutputEmitterRef, - ɵSIGNAL as SIGNAL, SimpleChange, SimpleChanges, StaticProvider, Testability, TestabilityRegistry, + type OutputEmitterRef, + type ɵInputSignalNode as InputSignalNode, + ɵSIGNAL as SIGNAL, } from '@angular/core'; import { diff --git a/packages/upgrade/src/common/test/downgrade_component_adapter_spec.ts b/packages/upgrade/src/common/test/downgrade_component_adapter_spec.ts index 906b07eef3c..2fc84ac37eb 100644 --- a/packages/upgrade/src/common/test/downgrade_component_adapter_spec.ts +++ b/packages/upgrade/src/common/test/downgrade_component_adapter_spec.ts @@ -8,7 +8,7 @@ import { Compiler, Component, - ɵComponentFactory as ComponentFactory, + ComponentFactory, Injector, NgModule, TestabilityRegistry,