fix(expansion-panel): toggle not being updated when set programmatically (#5650)

* fix(expansion-panel): toggle not being updated when set programmatically

Fixes the toggle arrow not being flipped when the `expanded` is set programmatically, in addition to the `hideToggle` input not working either. This is a regression from #5549 and is a consequence of the fact that the panel header reaches into the panel to determine what to do with the arrow.

Fixes #5623.

* chore: add a couple of unit tests for the fixes
This commit is contained in:
Kristiyan Kostadinov
2017-07-28 17:59:15 +03:00
committed by Andrew Seguin
parent 66e222fc2e
commit 41c804b36a
4 changed files with 83 additions and 4 deletions
+3 -1
View File
@@ -35,8 +35,10 @@ export class AccordionItem implements OnDestroy {
@Output() destroyed = new EventEmitter<void>();
/** 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) {
+28 -2
View File
@@ -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;
}
}
}
/**
+16 -1
View File
@@ -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<SimpleChanges>();
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({
+36
View File
@@ -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: `
<md-expansion-panel [expanded]="expanded"
[hideToggle]="hideToggle"
(opened)="openCallback()"
(closed)="closeCallback()">
<md-expansion-panel-header>Panel Title</md-expansion-panel-header>
@@ -118,6 +153,7 @@ describe('MdExpansionPanel', () => {
})
class PanelWithContent {
expanded: boolean = false;
hideToggle: boolean = false;
openCallback = jasmine.createSpy('openCallback');
closeCallback = jasmine.createSpy('closeCallback');
}