Skip to content

Commit a367908

Browse files
committed
fix(overlay): Render the templates before placing them in the overlay.
This fixes positioning issues when rendering templates which contain embedded templates. The templates need to be rendered in order to properly determine width which can then determine placement.
1 parent 038a337 commit a367908

File tree

5 files changed

+60
-3
lines changed

5 files changed

+60
-3
lines changed

src/demo-app/overlay/overlay-demo.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
Pasta 3
1111
</button>
1212

13+
<button cdk-overlay-origin #tortelliniOrigin="cdkOverlayOrigin" (click)="openTortelliniPanel()">
14+
Pasta 4
15+
</button>
16+
1317

1418
<button cdk-overlay-origin #trigger="cdkOverlayOrigin" (click)="isMenuOpen = !isMenuOpen">
1519
Open menu
@@ -27,4 +31,8 @@
2731
<p class="demo-fusilli"> Fusilli </p>
2832
</ng-template>
2933

34+
<ng-template cdk-portal #tortelliniTemplate="cdkPortal">
35+
<ul class="demo-tortellini"><li *ngFor="let filling of tortelliniFillings">{{filling}}</li></ul>
36+
</ng-template>
37+
3038
<button (click)="openPanelWithBackdrop()">Backdrop panel</button>

src/demo-app/overlay/overlay-demo.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@
1818
background-color: rebeccapurple;
1919
opacity: 0.5;
2020
}
21+
22+
.demo-tortellini {
23+
margin: 0;
24+
padding: 10px;
25+
border: 1px solid black;
26+
color: white;
27+
background-color: orangered;
28+
opacity: 0.5;
29+
}

src/demo-app/overlay/overlay-demo.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ import {
2626
export class OverlayDemo {
2727
nextPosition: number = 0;
2828
isMenuOpen: boolean = false;
29+
tortelliniFillings = ['cheese and spinach', 'mushroom and broccoli'];
2930

3031
@ViewChildren(TemplatePortalDirective) templatePortals: QueryList<Portal<any>>;
3132
@ViewChild(OverlayOrigin) _overlayOrigin: OverlayOrigin;
33+
@ViewChild('tortelliniOrigin') tortelliniOrigin: OverlayOrigin;
34+
@ViewChild('tortelliniTemplate') tortelliniTemplate: TemplatePortalDirective;
3235

3336
constructor(public overlay: Overlay, public viewContainerRef: ViewContainerRef) { }
3437

@@ -75,6 +78,21 @@ export class OverlayDemo {
7578
overlayRef.attach(new ComponentPortal(SpagettiPanel, this.viewContainerRef));
7679
}
7780

81+
openTortelliniPanel() {
82+
let strategy = this.overlay.position()
83+
.connectedTo(
84+
this.tortelliniOrigin.elementRef,
85+
{originX: 'start', originY: 'bottom'},
86+
{overlayX: 'end', overlayY: 'top'} );
87+
88+
let config = new OverlayState();
89+
config.positionStrategy = strategy;
90+
91+
let overlayRef = this.overlay.create(config);
92+
93+
overlayRef.attach(this.tortelliniTemplate);
94+
}
95+
7896
openPanelWithBackdrop() {
7997
let config = new OverlayState();
8098

src/lib/core/portal/dom-portal-host.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export class DomPortalHost extends BasePortalHost {
6464
attachTemplatePortal(portal: TemplatePortal): Map<string, any> {
6565
let viewContainer = portal.viewContainerRef;
6666
let viewRef = viewContainer.createEmbeddedView(portal.templateRef);
67+
viewRef.detectChanges();
6768

6869
// The method `createEmbeddedView` will add the view as a child of the viewContainer.
6970
// But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay Container)

src/lib/core/portal/portal.spec.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Injector,
1212
ApplicationRef,
1313
} from '@angular/core';
14+
import {CommonModule} from '@angular/common';
1415
import {TemplatePortalDirective, PortalHostDirective, PortalModule} from './portal-directives';
1516
import {Portal, ComponentPortal} from './portal';
1617
import {DomPortalHost} from './dom-portal-host';
@@ -258,6 +259,15 @@ describe('Portals', () => {
258259
expect(someDomElement.textContent).toContain('Cake');
259260
});
260261

262+
it('should render a template portal with an inner template', () => {
263+
let fixture = TestBed.createComponent(PortalTestApp);
264+
fixture.detectChanges();
265+
266+
fixture.componentInstance.portalWithTemplate.attach(host);
267+
268+
expect(someDomElement.textContent).toContain('Durian');
269+
});
270+
261271
it('should attach and detach a template portal with a binding', () => {
262272
let fixture = TestBed.createComponent(PortalTestApp);
263273

@@ -384,14 +394,21 @@ class ArbitraryViewContainerRefComponent {
384394
<ng-template cdk-portal>Cake</ng-template>
385395
386396
<div *cdk-portal>Pie</div>
387-
388-
<ng-template cdk-portal> {{fruit}} </ng-template>`,
397+
<ng-template cdk-portal> {{fruit}} </ng-template>
398+
399+
<ng-template cdk-portal>
400+
<ul>
401+
<li *ngFor="let fruitName of fruits"> {{fruitName}} </li>
402+
</ul>
403+
</ng-template>
404+
`,
389405
})
390406
class PortalTestApp {
391407
@ViewChildren(TemplatePortalDirective) portals: QueryList<TemplatePortalDirective>;
392408
@ViewChild(PortalHostDirective) portalHost: PortalHostDirective;
393409
selectedPortal: Portal<any>;
394410
fruit: string = 'Banana';
411+
fruits = ['Apple', 'Pineapple', 'Durian'];
395412

396413
constructor(public injector: Injector) { }
397414

@@ -406,13 +423,17 @@ class PortalTestApp {
406423
get portalWithBinding() {
407424
return this.portals.toArray()[2];
408425
}
426+
427+
get portalWithTemplate() {
428+
return this.portals.toArray()[3];
429+
}
409430
}
410431

411432
// Create a real (non-test) NgModule as a workaround for
412433
// https://github.com/angular/angular/issues/10760
413434
const TEST_COMPONENTS = [PortalTestApp, ArbitraryViewContainerRefComponent, PizzaMsg];
414435
@NgModule({
415-
imports: [PortalModule],
436+
imports: [CommonModule, PortalModule],
416437
exports: TEST_COMPONENTS,
417438
declarations: TEST_COMPONENTS,
418439
entryComponents: TEST_COMPONENTS,

0 commit comments

Comments
 (0)