Skip to content

Commit 8be9cba

Browse files
committed
fix(cdk/portal): fix incorrect injector hierarchy with DomPortalOutlet and child environment injectors
This commit fixes a regression that was introduced in #27427, where the injector hierarchy did not respect nested environment injectors. `DomPortalOutlet` was always using the application root as environment injector, yet the element injector may have a custom child environment injector as ancestor. This child environment injector has to be retrieved manually and passed as environment injector of the portal component. Fixes #30609
1 parent 5a98cc6 commit 8be9cba

File tree

2 files changed

+35
-2
lines changed

2 files changed

+35
-2
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ApplicationRef,
1111
ComponentRef,
1212
EmbeddedViewRef,
13+
EnvironmentInjector,
1314
Injector,
1415
NgModuleRef,
1516
createComponent,
@@ -66,9 +67,11 @@ export class DomPortalOutlet extends BasePortalOutlet {
6667
throw Error('Cannot attach component portal to outlet without an ApplicationRef.');
6768
}
6869

70+
const elementInjector = portal.injector || this._defaultInjector || Injector.NULL;
71+
const environmentInjector = elementInjector.get(EnvironmentInjector, this._appRef!.injector);
6972
componentRef = createComponent(portal.component, {
70-
elementInjector: portal.injector || this._defaultInjector || Injector.NULL,
71-
environmentInjector: this._appRef!.injector,
73+
elementInjector,
74+
environmentInjector,
7275
projectableNodes: portal.projectableNodes || undefined,
7376
});
7477

src/cdk/portal/portal.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import {
55
ComponentRef,
66
Directive,
77
ElementRef,
8+
EnvironmentInjector,
89
Injector,
910
QueryList,
1011
TemplateRef,
1112
ViewChild,
1213
ViewChildren,
1314
ViewContainerRef,
15+
createComponent,
16+
createEnvironmentInjector,
1417
inject,
1518
} from '@angular/core';
1619
import {ComponentFixture, TestBed} from '@angular/core/testing';
@@ -614,6 +617,9 @@ describe('Portals', () => {
614617
expect(someDomElement.textContent)
615618
.withContext('Expected the static string "Pizza" in the DomPortalOutlet.')
616619
.toContain('Pizza');
620+
expect(someDomElement.textContent)
621+
.withContext('Did not expect the bound string "Chocolate" in the DomPortalOutlet')
622+
.not.toContain('Chocolate');
617623

618624
componentInstance.snack = new Chocolate();
619625
someFixture.detectChanges();
@@ -628,6 +634,30 @@ describe('Portals', () => {
628634
.toBe('');
629635
});
630636

637+
it('should support a component portal with element injector that has a child environment injector as parent', () => {
638+
// https://github.com/angular/components/issues/30609
639+
const childEnvironment = createEnvironmentInjector(
640+
[Chocolate],
641+
someInjector.get(EnvironmentInjector),
642+
'Child environment',
643+
);
644+
645+
@Component({template: ''})
646+
class ChildComponent {
647+
readonly injector = inject(Injector);
648+
}
649+
650+
const component = createComponent(ChildComponent, {
651+
environmentInjector: childEnvironment,
652+
});
653+
const portal = new ComponentPortal(PizzaMsg, null, component.injector);
654+
655+
const componentInstance = portal.attach(host).instance;
656+
expect(componentInstance.snack)
657+
.withContext('Expected Chocolate to have been injected')
658+
.toBeInstanceOf(Chocolate);
659+
});
660+
631661
it('should call the dispose function even if the host has no attached content', () => {
632662
let spy = jasmine.createSpy('host dispose spy');
633663

0 commit comments

Comments
 (0)