diff --git a/src/demo-app/overlay/overlay-demo.html b/src/demo-app/overlay/overlay-demo.html index c431cffc15fc..df048577ba7d 100644 --- a/src/demo-app/overlay/overlay-demo.html +++ b/src/demo-app/overlay/overlay-demo.html @@ -10,6 +10,10 @@ Pasta 3 + + diff --git a/src/demo-app/overlay/overlay-demo.scss b/src/demo-app/overlay/overlay-demo.scss index 8e5d1cbd4743..275b5161e5f5 100644 --- a/src/demo-app/overlay/overlay-demo.scss +++ b/src/demo-app/overlay/overlay-demo.scss @@ -18,3 +18,12 @@ background-color: rebeccapurple; opacity: 0.5; } + +.demo-tortellini { + margin: 0; + padding: 10px; + border: 1px solid black; + color: white; + background-color: orangered; + opacity: 0.5; +} diff --git a/src/demo-app/overlay/overlay-demo.ts b/src/demo-app/overlay/overlay-demo.ts index c8f3708658a2..c4e61eba6d21 100644 --- a/src/demo-app/overlay/overlay-demo.ts +++ b/src/demo-app/overlay/overlay-demo.ts @@ -26,9 +26,12 @@ import { export class OverlayDemo { nextPosition: number = 0; isMenuOpen: boolean = false; + tortelliniFillings = ['cheese and spinach', 'mushroom and broccoli']; @ViewChildren(TemplatePortalDirective) templatePortals: QueryList>; @ViewChild(OverlayOrigin) _overlayOrigin: OverlayOrigin; + @ViewChild('tortelliniOrigin') tortelliniOrigin: OverlayOrigin; + @ViewChild('tortelliniTemplate') tortelliniTemplate: TemplatePortalDirective; constructor(public overlay: Overlay, public viewContainerRef: ViewContainerRef) { } @@ -75,6 +78,21 @@ export class OverlayDemo { overlayRef.attach(new ComponentPortal(SpagettiPanel, this.viewContainerRef)); } + openTortelliniPanel() { + let strategy = this.overlay.position() + .connectedTo( + this.tortelliniOrigin.elementRef, + {originX: 'start', originY: 'bottom'}, + {overlayX: 'end', overlayY: 'top'} ); + + let config = new OverlayState(); + config.positionStrategy = strategy; + + let overlayRef = this.overlay.create(config); + + overlayRef.attach(this.tortelliniTemplate); + } + openPanelWithBackdrop() { let config = new OverlayState(); diff --git a/src/lib/core/portal/dom-portal-host.ts b/src/lib/core/portal/dom-portal-host.ts index d44b9e0a901c..7a2ebf360779 100644 --- a/src/lib/core/portal/dom-portal-host.ts +++ b/src/lib/core/portal/dom-portal-host.ts @@ -64,6 +64,7 @@ export class DomPortalHost extends BasePortalHost { attachTemplatePortal(portal: TemplatePortal): Map { let viewContainer = portal.viewContainerRef; let viewRef = viewContainer.createEmbeddedView(portal.templateRef); + viewRef.detectChanges(); // The method `createEmbeddedView` will add the view as a child of the viewContainer. // But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay Container) diff --git a/src/lib/core/portal/portal.spec.ts b/src/lib/core/portal/portal.spec.ts index 6654647082d2..8fc3c5743e19 100644 --- a/src/lib/core/portal/portal.spec.ts +++ b/src/lib/core/portal/portal.spec.ts @@ -11,6 +11,7 @@ import { Injector, ApplicationRef, } from '@angular/core'; +import {CommonModule} from '@angular/common'; import {TemplatePortalDirective, PortalHostDirective, PortalModule} from './portal-directives'; import {Portal, ComponentPortal} from './portal'; import {DomPortalHost} from './dom-portal-host'; @@ -123,6 +124,28 @@ describe('Portals', () => { expect(hostContainer.textContent).toContain('Mango'); }); + it('should load a portal with an inner template', () => { + let testAppComponent = fixture.debugElement.componentInstance; + + // Detect changes initially so that the component's ViewChildren are resolved. + fixture.detectChanges(); + + // Set the selectedHost to be a TemplatePortal. + testAppComponent.selectedPortal = testAppComponent.portalWithTemplate; + fixture.detectChanges(); + + // Expect that the content of the attached portal is present. + let hostContainer = fixture.nativeElement.querySelector('.portal-container'); + expect(hostContainer.textContent).toContain('Pineapple'); + + // When updating the binding value. + testAppComponent.fruits = ['Mangosteen']; + fixture.detectChanges(); + + // Expect the new value to be reflected in the rendered output. + expect(hostContainer.textContent).toContain('Mangosteen'); + }); + it('should change the attached portal', () => { let testAppComponent = fixture.debugElement.componentInstance; @@ -258,6 +281,15 @@ describe('Portals', () => { expect(someDomElement.textContent).toContain('Cake'); }); + it('should render a template portal with an inner template', () => { + let fixture = TestBed.createComponent(PortalTestApp); + fixture.detectChanges(); + + fixture.componentInstance.portalWithTemplate.attach(host); + + expect(someDomElement.textContent).toContain('Durian'); + }); + it('should attach and detach a template portal with a binding', () => { let fixture = TestBed.createComponent(PortalTestApp); @@ -384,14 +416,21 @@ class ArbitraryViewContainerRefComponent { Cake
Pie
- - {{fruit}} `, + {{fruit}} + + +
    +
  • {{fruitName}}
  • +
+
+ `, }) class PortalTestApp { @ViewChildren(TemplatePortalDirective) portals: QueryList; @ViewChild(PortalHostDirective) portalHost: PortalHostDirective; selectedPortal: Portal; fruit: string = 'Banana'; + fruits = ['Apple', 'Pineapple', 'Durian']; constructor(public injector: Injector) { } @@ -406,13 +445,17 @@ class PortalTestApp { get portalWithBinding() { return this.portals.toArray()[2]; } + + get portalWithTemplate() { + return this.portals.toArray()[3]; + } } // Create a real (non-test) NgModule as a workaround for // https://github.com/angular/angular/issues/10760 const TEST_COMPONENTS = [PortalTestApp, ArbitraryViewContainerRefComponent, PizzaMsg]; @NgModule({ - imports: [PortalModule], + imports: [CommonModule, PortalModule], exports: TEST_COMPONENTS, declarations: TEST_COMPONENTS, entryComponents: TEST_COMPONENTS, diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 1577f252e85c..4036be250a74 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -1040,34 +1040,38 @@ describe('MdSelect', () => { select.style.marginRight = '20px'; }); - it('should adjust for the checkbox in ltr', () => { + it('should adjust for the checkbox in ltr', async(() => { trigger.click(); multiFixture.detectChanges(); - const triggerLeft = trigger.getBoundingClientRect().left; - const firstOptionLeft = - document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().left; + multiFixture.whenStable().then(() => { + const triggerLeft = trigger.getBoundingClientRect().left; + const firstOptionLeft = + document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().left; - // 48px accounts for the checkbox size, margin and the panel's padding. - expect(firstOptionLeft.toFixed(2)) - .toEqual((triggerLeft - 48).toFixed(2), - `Expected trigger label to align along x-axis, accounting for the checkbox.`); - }); + // 48px accounts for the checkbox size, margin and the panel's padding. + expect(firstOptionLeft.toFixed(2)) + .toEqual((triggerLeft - 48).toFixed(2), + `Expected trigger label to align along x-axis, accounting for the checkbox.`); + }); + })); - it('should adjust for the checkbox in rtl', () => { + it('should adjust for the checkbox in rtl', async(() => { dir.value = 'rtl'; trigger.click(); multiFixture.detectChanges(); - const triggerRight = trigger.getBoundingClientRect().right; - const firstOptionRight = - document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right; + multiFixture.whenStable().then(() => { + const triggerRight = trigger.getBoundingClientRect().right; + const firstOptionRight = + document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right; - // 48px accounts for the checkbox size, margin and the panel's padding. - expect(firstOptionRight.toFixed(2)) - .toEqual((triggerRight + 48).toFixed(2), - `Expected trigger label to align along x-axis, accounting for the checkbox.`); - }); + // 48px accounts for the checkbox size, margin and the panel's padding. + expect(firstOptionRight.toFixed(2)) + .toEqual((triggerRight + 48).toFixed(2), + `Expected trigger label to align along x-axis, accounting for the checkbox.`); + }); + })); }); });