Skip to content

Commit f7d0b80

Browse files
committed
fix(cdk/portal): remove ComponentFactoryResolver usages
We now have all the necessary APIs to allow to remove our usages of the deprecated `ComponentFactoryResolver`. Fixes #24334.
1 parent 560878a commit f7d0b80

File tree

6 files changed

+49
-100
lines changed

6 files changed

+49
-100
lines changed

src/cdk/portal/dom-portal-outlet.ts

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88

99
import {
1010
ApplicationRef,
11-
ComponentFactoryResolver,
1211
ComponentRef,
1312
EmbeddedViewRef,
1413
Injector,
14+
NgModuleRef,
15+
createComponent,
1516
} from '@angular/core';
1617
import {BasePortalOutlet, ComponentPortal, DomPortal, TemplatePortal} from './portal';
1718

@@ -36,7 +37,11 @@ export class DomPortalOutlet extends BasePortalOutlet {
3637
constructor(
3738
/** Element into which the content is projected. */
3839
public outletElement: Element,
39-
private _componentFactoryResolver?: ComponentFactoryResolver,
40+
/**
41+
* @deprecated No longer in use. To be removed.
42+
* @breaking-change 18.0.0
43+
*/
44+
_componentFactoryResolver?: any,
4045
private _appRef?: ApplicationRef,
4146
private _defaultInjector?: Injector,
4247

@@ -51,41 +56,40 @@ export class DomPortalOutlet extends BasePortalOutlet {
5156
}
5257

5358
/**
54-
* Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver.
59+
* Attach the given ComponentPortal to DOM element.
5560
* @param portal Portal to be attached
5661
* @returns Reference to the created component.
5762
*/
5863
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {
59-
const resolver = (portal.componentFactoryResolver || this._componentFactoryResolver)!;
60-
61-
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !resolver) {
62-
throw Error('Cannot attach component portal to outlet without a ComponentFactoryResolver.');
63-
}
64-
65-
const componentFactory = resolver.resolveComponentFactory(portal.component);
6664
let componentRef: ComponentRef<T>;
6765

6866
// If the portal specifies a ViewContainerRef, we will use that as the attachment point
6967
// for the component (in terms of Angular's component tree, not rendering).
7068
// When the ViewContainerRef is missing, we use the factory to create the component directly
7169
// and then manually attach the view to the application.
7270
if (portal.viewContainerRef) {
73-
componentRef = portal.viewContainerRef.createComponent(
74-
componentFactory,
75-
portal.viewContainerRef.length,
76-
portal.injector || portal.viewContainerRef.injector,
77-
portal.projectableNodes || undefined,
78-
);
71+
const injector = portal.injector || portal.viewContainerRef.injector;
72+
const ngModuleRef = injector.get(NgModuleRef, null, {optional: true}) || undefined;
73+
74+
componentRef = portal.viewContainerRef.createComponent(portal.component, {
75+
index: portal.viewContainerRef.length,
76+
injector,
77+
ngModuleRef,
78+
projectableNodes: portal.projectableNodes || undefined,
79+
});
7980

8081
this.setDisposeFn(() => componentRef.destroy());
8182
} else {
8283
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !this._appRef) {
8384
throw Error('Cannot attach component portal to outlet without an ApplicationRef.');
8485
}
8586

86-
componentRef = componentFactory.create(
87-
portal.injector || this._defaultInjector || Injector.NULL,
88-
);
87+
componentRef = createComponent(portal.component, {
88+
elementInjector: portal.injector || this._defaultInjector || Injector.NULL,
89+
environmentInjector: this._appRef!.injector,
90+
projectableNodes: portal.projectableNodes || undefined,
91+
});
92+
8993
this._appRef!.attachView(componentRef.hostView);
9094
this.setDisposeFn(() => {
9195
// Verify that the ApplicationRef has registered views before trying to detach a host view.

src/cdk/portal/portal-directives.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import {
10-
ComponentFactoryResolver,
1110
ComponentRef,
1211
Directive,
1312
EmbeddedViewRef,
@@ -20,6 +19,7 @@ import {
2019
ViewContainerRef,
2120
Input,
2221
inject,
22+
NgModuleRef,
2323
} from '@angular/core';
2424
import {DOCUMENT} from '@angular/common';
2525
import {BasePortalOutlet, ComponentPortal, Portal, TemplatePortal, DomPortal} from './portal';
@@ -79,9 +79,9 @@ export type CdkPortalOutletAttachedRef = ComponentRef<any> | EmbeddedViewRef<any
7979
standalone: true,
8080
})
8181
export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestroy {
82-
private _componentFactoryResolver = inject(ComponentFactoryResolver);
83-
private _viewContainerRef = inject(ViewContainerRef);
82+
private _moduleRef = inject(NgModuleRef, {optional: true});
8483
private _document = inject(DOCUMENT);
84+
private _viewContainerRef = inject(ViewContainerRef);
8585

8686
/** Whether the portal component is initialized. */
8787
private _isInitialized = false;
@@ -140,7 +140,7 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
140140
}
141141

142142
/**
143-
* Attach the given ComponentPortal to this PortalOutlet using the ComponentFactoryResolver.
143+
* Attach the given ComponentPortal to this PortalOutlet.
144144
*
145145
* @param portal Portal to be attached to the portal outlet.
146146
* @returns Reference to the created component.
@@ -153,14 +153,12 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
153153
const viewContainerRef =
154154
portal.viewContainerRef != null ? portal.viewContainerRef : this._viewContainerRef;
155155

156-
const resolver = portal.componentFactoryResolver || this._componentFactoryResolver;
157-
const componentFactory = resolver.resolveComponentFactory(portal.component);
158-
const ref = viewContainerRef.createComponent(
159-
componentFactory,
160-
viewContainerRef.length,
161-
portal.injector || viewContainerRef.injector,
162-
portal.projectableNodes || undefined,
163-
);
156+
const ref = viewContainerRef.createComponent(portal.component, {
157+
index: viewContainerRef.length,
158+
injector: portal.injector || viewContainerRef.injector,
159+
projectableNodes: portal.projectableNodes || undefined,
160+
ngModuleRef: this._moduleRef || undefined,
161+
});
164162

165163
// If we're using a view container that's different from the injected one (e.g. when the portal
166164
// specifies its own) we need to move the component into the outlet, otherwise it'll be rendered

src/cdk/portal/portal.spec.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ import {
33
AfterViewInit,
44
ApplicationRef,
55
Component,
6-
ComponentFactoryResolver,
76
ComponentRef,
87
Directive,
98
ElementRef,
109
Injector,
1110
QueryList,
1211
TemplateRef,
13-
Type,
1412
ViewChild,
1513
ViewChildren,
1614
ViewContainerRef,
@@ -38,12 +36,10 @@ describe('Portals', () => {
3836

3937
describe('CdkPortalOutlet', () => {
4038
let fixture: ComponentFixture<PortalTestApp>;
41-
let componentFactoryResolver: ComponentFactoryResolver;
4239

4340
beforeEach(() => {
4441
fixture = TestBed.createComponent(PortalTestApp);
4542
fixture.detectChanges();
46-
componentFactoryResolver = TestBed.inject(ComponentFactoryResolver);
4743
});
4844

4945
it('should load a component into the portal', () => {
@@ -451,19 +447,6 @@ describe('Portals', () => {
451447
expect(instance.portalOutlet.hasAttached()).toBe(true);
452448
});
453449

454-
it('should use the `ComponentFactoryResolver` from the portal, if available', () => {
455-
const spy = jasmine.createSpy('resolveComponentFactorySpy');
456-
const portal = new ComponentPortal(PizzaMsg, undefined, undefined, {
457-
resolveComponentFactory: <T>(...args: [Type<T>]) => {
458-
spy();
459-
return componentFactoryResolver.resolveComponentFactory(...args);
460-
},
461-
});
462-
463-
fixture.componentInstance.portalOutlet.attachComponentPortal(portal);
464-
expect(spy).toHaveBeenCalled();
465-
});
466-
467450
it('should render inside outlet when component portal specifies view container ref', () => {
468451
const hostContainer = fixture.nativeElement.querySelector('.portal-container');
469452
const portal = new ComponentPortal(PizzaMsg, fixture.componentInstance.alternateContainer);
@@ -491,7 +474,6 @@ describe('Portals', () => {
491474
});
492475

493476
describe('DomPortalOutlet', () => {
494-
let componentFactoryResolver: ComponentFactoryResolver;
495477
let someViewContainerRef: ViewContainerRef;
496478
let someInjector: Injector;
497479
let someFixture: ComponentFixture<ArbitraryViewContainerRefComponent>;
@@ -501,18 +483,10 @@ describe('Portals', () => {
501483
let appRef: ApplicationRef;
502484

503485
beforeEach(() => {
504-
componentFactoryResolver = TestBed.inject(ComponentFactoryResolver);
505486
injector = TestBed.inject(Injector);
506487
appRef = TestBed.inject(ApplicationRef);
507488
someDomElement = document.createElement('div');
508-
host = new DomPortalOutlet(
509-
someDomElement,
510-
componentFactoryResolver,
511-
appRef,
512-
injector,
513-
document,
514-
);
515-
489+
host = new DomPortalOutlet(someDomElement, null, appRef, injector, document);
516490
someFixture = TestBed.createComponent(ArbitraryViewContainerRefComponent);
517491
someViewContainerRef = someFixture.componentInstance.viewContainerRef;
518492
someInjector = someFixture.componentInstance.injector;
@@ -669,19 +643,6 @@ describe('Portals', () => {
669643
expect(spy).toHaveBeenCalled();
670644
});
671645

672-
it('should use the `ComponentFactoryResolver` from the portal, if available', () => {
673-
const spy = jasmine.createSpy('resolveComponentFactorySpy');
674-
const portal = new ComponentPortal(PizzaMsg, undefined, undefined, {
675-
resolveComponentFactory: <T>(...args: [Type<T>]) => {
676-
spy();
677-
return componentFactoryResolver.resolveComponentFactory(...args);
678-
},
679-
});
680-
681-
host.attachComponentPortal(portal);
682-
expect(spy).toHaveBeenCalled();
683-
});
684-
685646
it('should attach and detach a DOM portal', () => {
686647
const fixture = TestBed.createComponent(PortalTestApp);
687648
fixture.detectChanges();

src/cdk/portal/portal.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
ComponentRef,
1414
EmbeddedViewRef,
1515
Injector,
16-
ComponentFactoryResolver,
1716
} from '@angular/core';
1817
import {
1918
throwNullPortalOutletError,
@@ -96,10 +95,10 @@ export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
9695
injector?: Injector | null;
9796

9897
/**
99-
* Alternate `ComponentFactoryResolver` to use when resolving the associated component.
100-
* Defaults to using the resolver from the outlet that the portal is attached to.
98+
* @deprecated No longer in use. To be removed.
99+
* @breaking-change 18.0.0
101100
*/
102-
componentFactoryResolver?: ComponentFactoryResolver | null;
101+
componentFactoryResolver?: any;
103102

104103
/**
105104
* List of DOM nodes that should be projected through `<ng-content>` of the attached component.
@@ -110,14 +109,17 @@ export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
110109
component: ComponentType<T>,
111110
viewContainerRef?: ViewContainerRef | null,
112111
injector?: Injector | null,
113-
componentFactoryResolver?: ComponentFactoryResolver | null,
112+
/**
113+
* @deprecated No longer in use. To be removed.
114+
* @breaking-change 18.0.0
115+
*/
116+
_componentFactoryResolver?: any,
114117
projectableNodes?: Node[][] | null,
115118
) {
116119
super();
117120
this.component = component;
118121
this.viewContainerRef = viewContainerRef;
119122
this.injector = injector;
120-
this.componentFactoryResolver = componentFactoryResolver;
121123
this.projectableNodes = projectableNodes;
122124
}
123125
}

src/material/dialog/dialog.spec.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ import {SpyLocation} from '@angular/common/testing';
1616
import {
1717
ChangeDetectionStrategy,
1818
Component,
19-
ComponentFactoryResolver,
20-
ComponentRef,
19+
createNgModuleRef,
2120
Directive,
2221
Inject,
2322
Injectable,
@@ -27,7 +26,6 @@ import {
2726
ViewChild,
2827
ViewContainerRef,
2928
ViewEncapsulation,
30-
createNgModuleRef,
3129
forwardRef,
3230
signal,
3331
} from '@angular/core';
@@ -119,7 +117,6 @@ describe('MatDialog', () => {
119117

120118
expect(overlayContainerElement.textContent).toContain('Pizza');
121119
expect(dialogRef.componentInstance instanceof PizzaMsg).toBe(true);
122-
expect(dialogRef.componentRef instanceof ComponentRef).toBe(true);
123120
expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef);
124121

125122
viewContainerFixture.detectChanges();
@@ -746,21 +743,6 @@ describe('MatDialog', () => {
746743
expect(scrollStrategy.enable).toHaveBeenCalled();
747744
}));
748745

749-
it('should be able to pass in an alternate ComponentFactoryResolver', inject(
750-
[ComponentFactoryResolver],
751-
(resolver: ComponentFactoryResolver) => {
752-
spyOn(resolver, 'resolveComponentFactory').and.callThrough();
753-
754-
dialog.open(PizzaMsg, {
755-
viewContainerRef: testViewContainerRef,
756-
componentFactoryResolver: resolver,
757-
});
758-
viewContainerFixture.detectChanges();
759-
760-
expect(resolver.resolveComponentFactory).toHaveBeenCalled();
761-
},
762-
));
763-
764746
describe('passing in data', () => {
765747
it('should be able to pass in data', () => {
766748
let config = {data: {stringParam: 'hello', dateParam: new Date()}};

tools/public_api_guard/cdk/portal.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
```ts
66

77
import { ApplicationRef } from '@angular/core';
8-
import { ComponentFactoryResolver } from '@angular/core';
98
import { ComponentRef } from '@angular/core';
109
import { ElementRef } from '@angular/core';
1110
import { EmbeddedViewRef } from '@angular/core';
@@ -77,9 +76,11 @@ export type CdkPortalOutletAttachedRef = ComponentRef<any> | EmbeddedViewRef<any
7776

7877
// @public
7978
export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
80-
constructor(component: ComponentType<T>, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, componentFactoryResolver?: ComponentFactoryResolver | null, projectableNodes?: Node[][] | null);
79+
constructor(component: ComponentType<T>, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null,
80+
_componentFactoryResolver?: any, projectableNodes?: Node[][] | null);
8181
component: ComponentType<T>;
82-
componentFactoryResolver?: ComponentFactoryResolver | null;
82+
// @deprecated (undocumented)
83+
componentFactoryResolver?: any;
8384
injector?: Injector | null;
8485
projectableNodes?: Node[][] | null;
8586
viewContainerRef?: ViewContainerRef | null;
@@ -104,7 +105,8 @@ export class DomPortalHost extends DomPortalOutlet {
104105
// @public
105106
export class DomPortalOutlet extends BasePortalOutlet {
106107
constructor(
107-
outletElement: Element, _componentFactoryResolver?: ComponentFactoryResolver | undefined, _appRef?: ApplicationRef | undefined, _defaultInjector?: Injector | undefined,
108+
outletElement: Element,
109+
_componentFactoryResolver?: any, _appRef?: ApplicationRef | undefined, _defaultInjector?: Injector | undefined,
108110
_document?: any);
109111
attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T>;
110112
// @deprecated

0 commit comments

Comments
 (0)