diff --git a/src/cdk/portal/dom-portal-outlet.ts b/src/cdk/portal/dom-portal-outlet.ts index aa9255d469e5..61688ef538d7 100644 --- a/src/cdk/portal/dom-portal-outlet.ts +++ b/src/cdk/portal/dom-portal-outlet.ts @@ -8,10 +8,11 @@ import { ApplicationRef, - ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injector, + NgModuleRef, + createComponent, } from '@angular/core'; import {BasePortalOutlet, ComponentPortal, DomPortal, TemplatePortal} from './portal'; @@ -36,7 +37,11 @@ export class DomPortalOutlet extends BasePortalOutlet { constructor( /** Element into which the content is projected. */ public outletElement: Element, - private _componentFactoryResolver?: ComponentFactoryResolver, + /** + * @deprecated No longer in use. To be removed. + * @breaking-change 18.0.0 + */ + _componentFactoryResolver?: any, private _appRef?: ApplicationRef, private _defaultInjector?: Injector, @@ -51,18 +56,11 @@ export class DomPortalOutlet extends BasePortalOutlet { } /** - * Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver. + * Attach the given ComponentPortal to DOM element. * @param portal Portal to be attached * @returns Reference to the created component. */ attachComponentPortal(portal: ComponentPortal): ComponentRef { - const resolver = (portal.componentFactoryResolver || this._componentFactoryResolver)!; - - if ((typeof ngDevMode === 'undefined' || ngDevMode) && !resolver) { - throw Error('Cannot attach component portal to outlet without a ComponentFactoryResolver.'); - } - - const componentFactory = resolver.resolveComponentFactory(portal.component); let componentRef: ComponentRef; // If the portal specifies a ViewContainerRef, we will use that as the attachment point @@ -70,12 +68,15 @@ export class DomPortalOutlet extends BasePortalOutlet { // When the ViewContainerRef is missing, we use the factory to create the component directly // and then manually attach the view to the application. if (portal.viewContainerRef) { - componentRef = portal.viewContainerRef.createComponent( - componentFactory, - portal.viewContainerRef.length, - portal.injector || portal.viewContainerRef.injector, - portal.projectableNodes || undefined, - ); + const injector = portal.injector || portal.viewContainerRef.injector; + const ngModuleRef = injector.get(NgModuleRef, null, {optional: true}) || undefined; + + componentRef = portal.viewContainerRef.createComponent(portal.component, { + index: portal.viewContainerRef.length, + injector, + ngModuleRef, + projectableNodes: portal.projectableNodes || undefined, + }); this.setDisposeFn(() => componentRef.destroy()); } else { @@ -83,9 +84,12 @@ export class DomPortalOutlet extends BasePortalOutlet { throw Error('Cannot attach component portal to outlet without an ApplicationRef.'); } - componentRef = componentFactory.create( - portal.injector || this._defaultInjector || Injector.NULL, - ); + componentRef = createComponent(portal.component, { + elementInjector: portal.injector || this._defaultInjector || Injector.NULL, + environmentInjector: this._appRef!.injector, + projectableNodes: portal.projectableNodes || undefined, + }); + this._appRef!.attachView(componentRef.hostView); this.setDisposeFn(() => { // Verify that the ApplicationRef has registered views before trying to detach a host view. diff --git a/src/cdk/portal/portal-directives.ts b/src/cdk/portal/portal-directives.ts index bd8f3199542f..67d3c620692e 100644 --- a/src/cdk/portal/portal-directives.ts +++ b/src/cdk/portal/portal-directives.ts @@ -7,7 +7,6 @@ */ import { - ComponentFactoryResolver, ComponentRef, Directive, EmbeddedViewRef, @@ -20,6 +19,7 @@ import { ViewContainerRef, Input, inject, + NgModuleRef, } from '@angular/core'; import {DOCUMENT} from '@angular/common'; import {BasePortalOutlet, ComponentPortal, Portal, TemplatePortal, DomPortal} from './portal'; @@ -79,9 +79,9 @@ export type CdkPortalOutletAttachedRef = ComponentRef | EmbeddedViewRef { describe('CdkPortalOutlet', () => { let fixture: ComponentFixture; - let componentFactoryResolver: ComponentFactoryResolver; beforeEach(() => { fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); - componentFactoryResolver = TestBed.inject(ComponentFactoryResolver); }); it('should load a component into the portal', () => { @@ -451,19 +447,6 @@ describe('Portals', () => { expect(instance.portalOutlet.hasAttached()).toBe(true); }); - it('should use the `ComponentFactoryResolver` from the portal, if available', () => { - const spy = jasmine.createSpy('resolveComponentFactorySpy'); - const portal = new ComponentPortal(PizzaMsg, undefined, undefined, { - resolveComponentFactory: (...args: [Type]) => { - spy(); - return componentFactoryResolver.resolveComponentFactory(...args); - }, - }); - - fixture.componentInstance.portalOutlet.attachComponentPortal(portal); - expect(spy).toHaveBeenCalled(); - }); - it('should render inside outlet when component portal specifies view container ref', () => { const hostContainer = fixture.nativeElement.querySelector('.portal-container'); const portal = new ComponentPortal(PizzaMsg, fixture.componentInstance.alternateContainer); @@ -491,7 +474,6 @@ describe('Portals', () => { }); describe('DomPortalOutlet', () => { - let componentFactoryResolver: ComponentFactoryResolver; let someViewContainerRef: ViewContainerRef; let someInjector: Injector; let someFixture: ComponentFixture; @@ -501,18 +483,10 @@ describe('Portals', () => { let appRef: ApplicationRef; beforeEach(() => { - componentFactoryResolver = TestBed.inject(ComponentFactoryResolver); injector = TestBed.inject(Injector); appRef = TestBed.inject(ApplicationRef); someDomElement = document.createElement('div'); - host = new DomPortalOutlet( - someDomElement, - componentFactoryResolver, - appRef, - injector, - document, - ); - + host = new DomPortalOutlet(someDomElement, null, appRef, injector, document); someFixture = TestBed.createComponent(ArbitraryViewContainerRefComponent); someViewContainerRef = someFixture.componentInstance.viewContainerRef; someInjector = someFixture.componentInstance.injector; @@ -669,19 +643,6 @@ describe('Portals', () => { expect(spy).toHaveBeenCalled(); }); - it('should use the `ComponentFactoryResolver` from the portal, if available', () => { - const spy = jasmine.createSpy('resolveComponentFactorySpy'); - const portal = new ComponentPortal(PizzaMsg, undefined, undefined, { - resolveComponentFactory: (...args: [Type]) => { - spy(); - return componentFactoryResolver.resolveComponentFactory(...args); - }, - }); - - host.attachComponentPortal(portal); - expect(spy).toHaveBeenCalled(); - }); - it('should attach and detach a DOM portal', () => { const fixture = TestBed.createComponent(PortalTestApp); fixture.detectChanges(); diff --git a/src/cdk/portal/portal.ts b/src/cdk/portal/portal.ts index 87d9f836dc85..048196274567 100644 --- a/src/cdk/portal/portal.ts +++ b/src/cdk/portal/portal.ts @@ -13,7 +13,6 @@ import { ComponentRef, EmbeddedViewRef, Injector, - ComponentFactoryResolver, } from '@angular/core'; import { throwNullPortalOutletError, @@ -96,10 +95,10 @@ export class ComponentPortal extends Portal> { injector?: Injector | null; /** - * Alternate `ComponentFactoryResolver` to use when resolving the associated component. - * Defaults to using the resolver from the outlet that the portal is attached to. + * @deprecated No longer in use. To be removed. + * @breaking-change 18.0.0 */ - componentFactoryResolver?: ComponentFactoryResolver | null; + componentFactoryResolver?: any; /** * List of DOM nodes that should be projected through `` of the attached component. @@ -110,14 +109,17 @@ export class ComponentPortal extends Portal> { component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, - componentFactoryResolver?: ComponentFactoryResolver | null, + /** + * @deprecated No longer in use. To be removed. + * @breaking-change 18.0.0 + */ + _componentFactoryResolver?: any, projectableNodes?: Node[][] | null, ) { super(); this.component = component; this.viewContainerRef = viewContainerRef; this.injector = injector; - this.componentFactoryResolver = componentFactoryResolver; this.projectableNodes = projectableNodes; } } diff --git a/src/material/dialog/dialog.spec.ts b/src/material/dialog/dialog.spec.ts index 1e64f0005486..76474d18a9cc 100644 --- a/src/material/dialog/dialog.spec.ts +++ b/src/material/dialog/dialog.spec.ts @@ -16,8 +16,7 @@ import {SpyLocation} from '@angular/common/testing'; import { ChangeDetectionStrategy, Component, - ComponentFactoryResolver, - ComponentRef, + createNgModuleRef, Directive, Inject, Injectable, @@ -27,7 +26,6 @@ import { ViewChild, ViewContainerRef, ViewEncapsulation, - createNgModuleRef, forwardRef, signal, } from '@angular/core'; @@ -119,7 +117,6 @@ describe('MatDialog', () => { expect(overlayContainerElement.textContent).toContain('Pizza'); expect(dialogRef.componentInstance instanceof PizzaMsg).toBe(true); - expect(dialogRef.componentRef instanceof ComponentRef).toBe(true); expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); @@ -746,21 +743,6 @@ describe('MatDialog', () => { expect(scrollStrategy.enable).toHaveBeenCalled(); })); - it('should be able to pass in an alternate ComponentFactoryResolver', inject( - [ComponentFactoryResolver], - (resolver: ComponentFactoryResolver) => { - spyOn(resolver, 'resolveComponentFactory').and.callThrough(); - - dialog.open(PizzaMsg, { - viewContainerRef: testViewContainerRef, - componentFactoryResolver: resolver, - }); - viewContainerFixture.detectChanges(); - - expect(resolver.resolveComponentFactory).toHaveBeenCalled(); - }, - )); - describe('passing in data', () => { it('should be able to pass in data', () => { let config = {data: {stringParam: 'hello', dateParam: new Date()}}; diff --git a/tools/public_api_guard/cdk/portal.md b/tools/public_api_guard/cdk/portal.md index c0bc7ad875a5..6430a782d834 100644 --- a/tools/public_api_guard/cdk/portal.md +++ b/tools/public_api_guard/cdk/portal.md @@ -5,7 +5,6 @@ ```ts import { ApplicationRef } from '@angular/core'; -import { ComponentFactoryResolver } from '@angular/core'; import { ComponentRef } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EmbeddedViewRef } from '@angular/core'; @@ -77,9 +76,11 @@ export type CdkPortalOutletAttachedRef = ComponentRef | EmbeddedViewRef extends Portal> { - constructor(component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, componentFactoryResolver?: ComponentFactoryResolver | null, projectableNodes?: Node[][] | null); + constructor(component: ComponentType, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, + _componentFactoryResolver?: any, projectableNodes?: Node[][] | null); component: ComponentType; - componentFactoryResolver?: ComponentFactoryResolver | null; + // @deprecated (undocumented) + componentFactoryResolver?: any; injector?: Injector | null; projectableNodes?: Node[][] | null; viewContainerRef?: ViewContainerRef | null; @@ -104,7 +105,8 @@ export class DomPortalHost extends DomPortalOutlet { // @public export class DomPortalOutlet extends BasePortalOutlet { constructor( - outletElement: Element, _componentFactoryResolver?: ComponentFactoryResolver | undefined, _appRef?: ApplicationRef | undefined, _defaultInjector?: Injector | undefined, + outletElement: Element, + _componentFactoryResolver?: any, _appRef?: ApplicationRef | undefined, _defaultInjector?: Injector | undefined, _document?: any); attachComponentPortal(portal: ComponentPortal): ComponentRef; // @deprecated