Skip to content

Commit b3221a8

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.
1 parent a2392bf commit b3221a8

4 files changed

Lines changed: 51 additions & 0 deletions

File tree

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)