diff --git a/src/lib/expansion/accordion-item.ts b/src/lib/expansion/accordion-item.ts index 107f799c2377..785a79755f71 100644 --- a/src/lib/expansion/accordion-item.ts +++ b/src/lib/expansion/accordion-item.ts @@ -35,8 +35,10 @@ export class AccordionItem implements OnDestroy { @Output() destroyed = new EventEmitter(); /** The unique MdAccordionChild id. */ readonly id = `cdk-accordion-child-${nextId++}`; + /** Whether the MdAccordionChild is expanded. */ - @Input() get expanded(): boolean { return this._expanded; } + @Input() + get expanded(): boolean { return this._expanded; } set expanded(expanded: boolean) { // Only emit events and update the internal value if the value changes. if (this._expanded !== expanded) { diff --git a/src/lib/expansion/expansion-panel-header.ts b/src/lib/expansion/expansion-panel-header.ts index 1ca826cd5f68..18d7311e7bec 100644 --- a/src/lib/expansion/expansion-panel-header.ts +++ b/src/lib/expansion/expansion-panel-header.ts @@ -12,6 +12,8 @@ import { Host, ViewEncapsulation, ChangeDetectionStrategy, + ChangeDetectorRef, + OnDestroy, } from '@angular/core'; import { trigger, @@ -22,6 +24,9 @@ import { } from '@angular/animations'; import {SPACE, ENTER} from '../core/keyboard/keycodes'; import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-panel'; +import {filter} from '../core/rxjs/index'; +import {merge} from 'rxjs/observable/merge'; +import {Subscription} from 'rxjs/Subscription'; /** @@ -62,8 +67,22 @@ import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-pa ]), ], }) -export class MdExpansionPanelHeader { - constructor(@Host() public panel: MdExpansionPanel) {} +export class MdExpansionPanelHeader implements OnDestroy { + private _parentChangeSubscription: Subscription | null = null; + + constructor( + @Host() public panel: MdExpansionPanel, + private _changeDetectorRef: ChangeDetectorRef) { + + // Since the toggle state depends on an @Input on the panel, we + // need to subscribe and trigger change detection manually. + this._parentChangeSubscription = merge( + panel.opened, + panel.closed, + filter.call(panel._inputChanges, changes => !!changes.hideToggle) + ) + .subscribe(() => this._changeDetectorRef.markForCheck()); + } /** Toggles the expanded state of the panel. */ _toggle(): void { @@ -103,6 +122,13 @@ export class MdExpansionPanelHeader { return; } } + + ngOnDestroy() { + if (this._parentChangeSubscription) { + this._parentChangeSubscription.unsubscribe(); + this._parentChangeSubscription = null; + } + } } /** diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts index 4b2b83717187..33cf4babd66d 100644 --- a/src/lib/expansion/expansion-panel.ts +++ b/src/lib/expansion/expansion-panel.ts @@ -16,6 +16,9 @@ import { forwardRef, ChangeDetectionStrategy, ChangeDetectorRef, + SimpleChanges, + OnChanges, + OnDestroy, } from '@angular/core'; import { trigger, @@ -27,6 +30,7 @@ import { import {MdAccordion, MdAccordionDisplayMode} from './accordion'; import {AccordionItem} from './accordion-item'; import {UniqueSelectionDispatcher} from '../core'; +import {Subject} from 'rxjs/Subject'; /** MdExpansionPanel's states. */ @@ -72,10 +76,13 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2, ]), ], }) -export class MdExpansionPanel extends AccordionItem { +export class MdExpansionPanel extends AccordionItem implements OnChanges, OnDestroy { /** Whether the toggle indicator should be hidden. */ @Input() hideToggle: boolean = false; + /** Stream that emits for changes in `@Input` properties. */ + _inputChanges = new Subject(); + constructor(@Optional() @Host() accordion: MdAccordion, _changeDetectorRef: ChangeDetectorRef, _uniqueSelectionDispatcher: UniqueSelectionDispatcher) { @@ -104,6 +111,14 @@ export class MdExpansionPanel extends AccordionItem { _getExpandedState(): MdExpansionPanelState { return this.expanded ? 'expanded' : 'collapsed'; } + + ngOnChanges(changes: SimpleChanges) { + this._inputChanges.next(changes); + } + + ngOnDestroy() { + this._inputChanges.complete(); + } } @Directive({ diff --git a/src/lib/expansion/expansion.spec.ts b/src/lib/expansion/expansion.spec.ts index 89e4470958af..67c1b2363007 100644 --- a/src/lib/expansion/expansion.spec.ts +++ b/src/lib/expansion/expansion.spec.ts @@ -103,12 +103,47 @@ describe('MdExpansionPanel', () => { expect(styles.marginLeft).toBe('37px'); expect(styles.marginRight).toBe('37px'); })); + + it('should be able to hide the toggle', () => { + const fixture = TestBed.createComponent(PanelWithContent); + const header = fixture.debugElement.query(By.css('.mat-expansion-panel-header')).nativeElement; + + fixture.detectChanges(); + + expect(header.querySelector('.mat-expansion-indicator')) + .toBeTruthy('Expected indicator to be shown.'); + + fixture.componentInstance.hideToggle = true; + fixture.detectChanges(); + + expect(header.querySelector('.mat-expansion-indicator')) + .toBeFalsy('Expected indicator to be hidden.'); + }); + + it('should update the indicator rotation when the expanded state is toggled programmatically', + fakeAsync(() => { + const fixture = TestBed.createComponent(PanelWithContent); + + fixture.detectChanges(); + tick(250); + + const arrow = fixture.debugElement.query(By.css('.mat-expansion-indicator')).nativeElement; + + expect(arrow.style.transform).toBe('rotate(0deg)', 'Expected no rotation.'); + + fixture.componentInstance.expanded = true; + fixture.detectChanges(); + tick(250); + + expect(arrow.style.transform).toBe('rotate(180deg)', 'Expected 180 degree rotation.'); + })); }); @Component({ template: ` Panel Title @@ -118,6 +153,7 @@ describe('MdExpansionPanel', () => { }) class PanelWithContent { expanded: boolean = false; + hideToggle: boolean = false; openCallback = jasmine.createSpy('openCallback'); closeCallback = jasmine.createSpy('closeCallback'); }