Skip to content

Commit 41c804b

Browse files
crisbetoandrewseguin
authored andcommitted
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
1 parent 66e222f commit 41c804b

File tree

4 files changed

+83
-4
lines changed

4 files changed

+83
-4
lines changed

src/lib/expansion/accordion-item.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ export class AccordionItem implements OnDestroy {
3535
@Output() destroyed = new EventEmitter<void>();
3636
/** The unique MdAccordionChild id. */
3737
readonly id = `cdk-accordion-child-${nextId++}`;
38+
3839
/** Whether the MdAccordionChild is expanded. */
39-
@Input() get expanded(): boolean { return this._expanded; }
40+
@Input()
41+
get expanded(): boolean { return this._expanded; }
4042
set expanded(expanded: boolean) {
4143
// Only emit events and update the internal value if the value changes.
4244
if (this._expanded !== expanded) {

src/lib/expansion/expansion-panel-header.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
Host,
1313
ViewEncapsulation,
1414
ChangeDetectionStrategy,
15+
ChangeDetectorRef,
16+
OnDestroy,
1517
} from '@angular/core';
1618
import {
1719
trigger,
@@ -22,6 +24,9 @@ import {
2224
} from '@angular/animations';
2325
import {SPACE, ENTER} from '../core/keyboard/keycodes';
2426
import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-panel';
27+
import {filter} from '../core/rxjs/index';
28+
import {merge} from 'rxjs/observable/merge';
29+
import {Subscription} from 'rxjs/Subscription';
2530

2631

2732
/**
@@ -62,8 +67,22 @@ import {MdExpansionPanel, EXPANSION_PANEL_ANIMATION_TIMING} from './expansion-pa
6267
]),
6368
],
6469
})
65-
export class MdExpansionPanelHeader {
66-
constructor(@Host() public panel: MdExpansionPanel) {}
70+
export class MdExpansionPanelHeader implements OnDestroy {
71+
private _parentChangeSubscription: Subscription | null = null;
72+
73+
constructor(
74+
@Host() public panel: MdExpansionPanel,
75+
private _changeDetectorRef: ChangeDetectorRef) {
76+
77+
// Since the toggle state depends on an @Input on the panel, we
78+
// need to subscribe and trigger change detection manually.
79+
this._parentChangeSubscription = merge(
80+
panel.opened,
81+
panel.closed,
82+
filter.call(panel._inputChanges, changes => !!changes.hideToggle)
83+
)
84+
.subscribe(() => this._changeDetectorRef.markForCheck());
85+
}
6786

6887
/** Toggles the expanded state of the panel. */
6988
_toggle(): void {
@@ -103,6 +122,13 @@ export class MdExpansionPanelHeader {
103122
return;
104123
}
105124
}
125+
126+
ngOnDestroy() {
127+
if (this._parentChangeSubscription) {
128+
this._parentChangeSubscription.unsubscribe();
129+
this._parentChangeSubscription = null;
130+
}
131+
}
106132
}
107133

108134
/**

src/lib/expansion/expansion-panel.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
forwardRef,
1717
ChangeDetectionStrategy,
1818
ChangeDetectorRef,
19+
SimpleChanges,
20+
OnChanges,
21+
OnDestroy,
1922
} from '@angular/core';
2023
import {
2124
trigger,
@@ -27,6 +30,7 @@ import {
2730
import {MdAccordion, MdAccordionDisplayMode} from './accordion';
2831
import {AccordionItem} from './accordion-item';
2932
import {UniqueSelectionDispatcher} from '../core';
33+
import {Subject} from 'rxjs/Subject';
3034

3135

3236
/** MdExpansionPanel's states. */
@@ -72,10 +76,13 @@ export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,
7276
]),
7377
],
7478
})
75-
export class MdExpansionPanel extends AccordionItem {
79+
export class MdExpansionPanel extends AccordionItem implements OnChanges, OnDestroy {
7680
/** Whether the toggle indicator should be hidden. */
7781
@Input() hideToggle: boolean = false;
7882

83+
/** Stream that emits for changes in `@Input` properties. */
84+
_inputChanges = new Subject<SimpleChanges>();
85+
7986
constructor(@Optional() @Host() accordion: MdAccordion,
8087
_changeDetectorRef: ChangeDetectorRef,
8188
_uniqueSelectionDispatcher: UniqueSelectionDispatcher) {
@@ -104,6 +111,14 @@ export class MdExpansionPanel extends AccordionItem {
104111
_getExpandedState(): MdExpansionPanelState {
105112
return this.expanded ? 'expanded' : 'collapsed';
106113
}
114+
115+
ngOnChanges(changes: SimpleChanges) {
116+
this._inputChanges.next(changes);
117+
}
118+
119+
ngOnDestroy() {
120+
this._inputChanges.complete();
121+
}
107122
}
108123

109124
@Directive({

src/lib/expansion/expansion.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,47 @@ describe('MdExpansionPanel', () => {
103103
expect(styles.marginLeft).toBe('37px');
104104
expect(styles.marginRight).toBe('37px');
105105
}));
106+
107+
it('should be able to hide the toggle', () => {
108+
const fixture = TestBed.createComponent(PanelWithContent);
109+
const header = fixture.debugElement.query(By.css('.mat-expansion-panel-header')).nativeElement;
110+
111+
fixture.detectChanges();
112+
113+
expect(header.querySelector('.mat-expansion-indicator'))
114+
.toBeTruthy('Expected indicator to be shown.');
115+
116+
fixture.componentInstance.hideToggle = true;
117+
fixture.detectChanges();
118+
119+
expect(header.querySelector('.mat-expansion-indicator'))
120+
.toBeFalsy('Expected indicator to be hidden.');
121+
});
122+
123+
it('should update the indicator rotation when the expanded state is toggled programmatically',
124+
fakeAsync(() => {
125+
const fixture = TestBed.createComponent(PanelWithContent);
126+
127+
fixture.detectChanges();
128+
tick(250);
129+
130+
const arrow = fixture.debugElement.query(By.css('.mat-expansion-indicator')).nativeElement;
131+
132+
expect(arrow.style.transform).toBe('rotate(0deg)', 'Expected no rotation.');
133+
134+
fixture.componentInstance.expanded = true;
135+
fixture.detectChanges();
136+
tick(250);
137+
138+
expect(arrow.style.transform).toBe('rotate(180deg)', 'Expected 180 degree rotation.');
139+
}));
106140
});
107141

108142

109143
@Component({
110144
template: `
111145
<md-expansion-panel [expanded]="expanded"
146+
[hideToggle]="hideToggle"
112147
(opened)="openCallback()"
113148
(closed)="closeCallback()">
114149
<md-expansion-panel-header>Panel Title</md-expansion-panel-header>
@@ -118,6 +153,7 @@ describe('MdExpansionPanel', () => {
118153
})
119154
class PanelWithContent {
120155
expanded: boolean = false;
156+
hideToggle: boolean = false;
121157
openCallback = jasmine.createSpy('openCallback');
122158
closeCallback = jasmine.createSpy('closeCallback');
123159
}

0 commit comments

Comments
 (0)