Skip to content

Commit db53a21

Browse files
fix(cdk/portal): add directives support to ComponentPortal
Added a `directives` property to `ComponentPortal` that gets passed through to `createComponent` in both `DomPortalOutlet` and `CdkPortalOutlet`. This allows applying directives to dynamically created components via portals. Closes #33141
1 parent a2392bf commit db53a21

8 files changed

Lines changed: 58 additions & 1 deletion

File tree

goldens/cdk/dialog/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as _angular_cdk_portal from '@angular/cdk/portal';
1010
import { Binding } from '@angular/core';
1111
import { ChangeDetectorRef } from '@angular/core';
1212
import { ComponentRef } from '@angular/core';
13+
import { DirectiveWithBindings } from '@angular/core';
1314
import { DoCheck } from '@angular/core';
1415
import { ElementRef } from '@angular/core';
1516
import { EmbeddedViewRef } from '@angular/core';

goldens/cdk/menu/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { AfterContentInit } from '@angular/core';
88
import { Binding } from '@angular/core';
99
import { ComponentRef } from '@angular/core';
10+
import { DirectiveWithBindings } from '@angular/core';
1011
import { DoCheck } from '@angular/core';
1112
import { ElementRef } from '@angular/core';
1213
import { EmbeddedViewRef } from '@angular/core';

goldens/cdk/overlay/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { AfterContentInit } from '@angular/core';
88
import { Binding } from '@angular/core';
99
import { ComponentRef } from '@angular/core';
10+
import { DirectiveWithBindings } from '@angular/core';
1011
import { DoCheck } from '@angular/core';
1112
import { ElementRef } from '@angular/core';
1213
import { EmbeddedViewRef } from '@angular/core';

goldens/cdk/portal/index.api.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { ApplicationRef } from '@angular/core';
88
import { Binding } from '@angular/core';
99
import { ComponentRef } from '@angular/core';
10+
import { DirectiveWithBindings } from '@angular/core';
1011
import { ElementRef } from '@angular/core';
1112
import { EmbeddedViewRef } from '@angular/core';
1213
import { EventEmitter } from '@angular/core';
@@ -15,6 +16,7 @@ import { Injector } from '@angular/core';
1516
import { OnDestroy } from '@angular/core';
1617
import { OnInit } from '@angular/core';
1718
import { TemplateRef } from '@angular/core';
19+
import { Type } from '@angular/core';
1820
import { ViewContainerRef } from '@angular/core';
1921

2022
// @public
@@ -72,9 +74,10 @@ export type CdkPortalOutletAttachedRef = ComponentRef<any> | EmbeddedViewRef<any
7274

7375
// @public
7476
export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
75-
constructor(component: ComponentType<T>, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, projectableNodes?: Node[][] | null, bindings?: Binding[]);
77+
constructor(component: ComponentType<T>, viewContainerRef?: ViewContainerRef | null, injector?: Injector | null, projectableNodes?: Node[][] | null, bindings?: Binding[], directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[]);
7678
readonly bindings: Binding[] | null;
7779
component: ComponentType<T>;
80+
readonly directives: (Type<unknown> | DirectiveWithBindings<unknown>)[] | null;
7881
injector?: Injector | null;
7982
projectableNodes?: Node[][] | null;
8083
viewContainerRef?: ViewContainerRef | null;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export class DomPortalOutlet extends BasePortalOutlet {
6060
ngModuleRef,
6161
projectableNodes: portal.projectableNodes || undefined,
6262
bindings: portal.bindings || undefined,
63+
directives: portal.directives || undefined,
6364
});
6465

6566
this.setDisposeFn(() => componentRef.destroy());
@@ -76,6 +77,7 @@ export class DomPortalOutlet extends BasePortalOutlet {
7677
environmentInjector,
7778
projectableNodes: portal.projectableNodes || undefined,
7879
bindings: portal.bindings || undefined,
80+
directives: portal.directives || undefined,
7981
});
8082

8183
appRef.attachView(componentRef.hostView);

src/cdk/portal/portal-directives.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export class CdkPortalOutlet extends BasePortalOutlet implements OnInit, OnDestr
133133
projectableNodes: portal.projectableNodes || undefined,
134134
ngModuleRef: this._moduleRef || undefined,
135135
bindings: portal.bindings || undefined,
136+
directives: portal.directives || undefined,
136137
});
137138

138139
// If we're using a view container that's different from the injected one (e.g. when the portal

src/cdk/portal/portal.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,20 @@ describe('Portals', () => {
481481
fixture.detectChanges();
482482
expect(ref.instance.flavor).toBe('cheese');
483483
});
484+
485+
it('should be able to pass directives to the component via a CdkPortalOutlet', () => {
486+
const componentPortal = new ComponentPortal(PizzaMsg, null, null, null, undefined, [
487+
HighlightDirective,
488+
]);
489+
490+
fixture.componentInstance.selectedPortal = componentPortal;
491+
fixture.changeDetectorRef.markForCheck();
492+
fixture.detectChanges();
493+
494+
const ref = fixture.componentInstance.portalOutlet.attachedRef as ComponentRef<PizzaMsg>;
495+
const hostElement = ref.location.nativeElement as HTMLElement;
496+
expect(hostElement.style.background).toBe('yellow');
497+
});
484498
});
485499

486500
describe('DomPortalOutlet', () => {
@@ -750,6 +764,19 @@ describe('Portals', () => {
750764
someFixture.detectChanges();
751765
expect(componentInstance.flavor).toBe('pepperoni');
752766
});
767+
768+
it('should be able to pass directives to the component via a DomPortalOutlet', () => {
769+
const portal = new ComponentPortal(PizzaMsg, null, null, null, undefined, [
770+
HighlightDirective,
771+
]);
772+
773+
const ref = portal.attach(host);
774+
someFixture.changeDetectorRef.markForCheck();
775+
someFixture.detectChanges();
776+
777+
const hostElement = ref.location.nativeElement as HTMLElement;
778+
expect(hostElement.style.background).toBe('yellow');
779+
});
753780
});
754781
});
755782

@@ -769,6 +796,18 @@ class ChocolateInjector {
769796
}
770797
}
771798

799+
/** Simple directive for testing directives support in ComponentPortal. */
800+
@Directive({
801+
selector: 'pizza-msg',
802+
})
803+
class HighlightDirective {
804+
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
805+
806+
constructor() {
807+
this._elementRef.nativeElement.style.background = 'yellow';
808+
}
809+
}
810+
772811
/** Simple component for testing ComponentPortal. */
773812
@Component({
774813
selector: 'pizza-msg',

src/cdk/portal/portal.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
EmbeddedViewRef,
1515
Injector,
1616
Binding,
17+
Type,
18+
DirectiveWithBindings,
1719
} from '@angular/core';
1820
import {
1921
throwNullPortalOutletError,
@@ -105,19 +107,26 @@ export class ComponentPortal<T> extends Portal<ComponentRef<T>> {
105107
*/
106108
readonly bindings: Binding[] | null;
107109

110+
/**
111+
* Directives to apply to the created component.
112+
*/
113+
readonly directives: (Type<unknown> | DirectiveWithBindings<unknown>)[] | null;
114+
108115
constructor(
109116
component: ComponentType<T>,
110117
viewContainerRef?: ViewContainerRef | null,
111118
injector?: Injector | null,
112119
projectableNodes?: Node[][] | null,
113120
bindings?: Binding[],
121+
directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[],
114122
) {
115123
super();
116124
this.component = component;
117125
this.viewContainerRef = viewContainerRef;
118126
this.injector = injector;
119127
this.projectableNodes = projectableNodes;
120128
this.bindings = bindings || null;
129+
this.directives = directives || null;
121130
}
122131
}
123132

0 commit comments

Comments
 (0)