Skip to content

Commit 7816752

Browse files
mmalerbatinayuangao
authored andcommitted
fix(sidenav): resolve promise as false rather than (#1930)
* fix(sidenav): resolve promise as false rather than rejecting it when the toggle animation is canceled. also clean up the overly complex promise logic * use a result object instead of boolean as promise result
1 parent 78464a2 commit 7816752

File tree

2 files changed

+64
-85
lines changed

2 files changed

+64
-85
lines changed

src/lib/sidenav/sidenav.spec.ts

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {fakeAsync, async, tick, ComponentFixture, TestBed} from '@angular/core/testing';
22
import {Component} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4-
import {MdSidenav, MdSidenavModule} from './sidenav';
4+
import {MdSidenav, MdSidenavModule, MdSidenavToggleResult} from './sidenav';
55

66

77
function endSidenavTransition(fixture: ComponentFixture<any>) {
@@ -129,27 +129,25 @@ describe('MdSidenav', () => {
129129
let sidenav: MdSidenav = fixture.debugElement
130130
.query(By.directive(MdSidenav)).componentInstance;
131131

132-
let openCalled = false;
133-
let openCancelled = false;
134-
let closeCalled = false;
132+
let openResult: MdSidenavToggleResult;
133+
let closeResult: MdSidenavToggleResult;
135134

136-
sidenav.open().then(() => {
137-
openCalled = true;
138-
}, () => {
139-
openCancelled = true;
135+
sidenav.open().then((result) => {
136+
openResult = result;
140137
});
141138

142139
// We do not call transition end, close directly.
143-
sidenav.close().then(() => {
144-
closeCalled = true;
140+
sidenav.close().then((result) => {
141+
closeResult = result;
145142
});
146143

147144
endSidenavTransition(fixture);
148145
tick();
149146

150-
expect(openCalled).toBe(false);
151-
expect(openCancelled).toBe(true);
152-
expect(closeCalled).toBe(true);
147+
expect(openResult.type).toBe('open');
148+
expect(openResult.animationFinished).toBe(false);
149+
expect(closeResult.type).toBe('close');
150+
expect(closeResult.animationFinished).toBe(true);
153151
tick();
154152
}));
155153

@@ -158,32 +156,30 @@ describe('MdSidenav', () => {
158156
let sidenav: MdSidenav = fixture.debugElement
159157
.query(By.directive(MdSidenav)).componentInstance;
160158

161-
let closeCalled = false;
162-
let closeCancelled = false;
163-
let openCalled = false;
159+
let closeResult: MdSidenavToggleResult;
160+
let openResult: MdSidenavToggleResult;
164161

165162
// First, open the sidenav completely.
166163
sidenav.open();
167164
endSidenavTransition(fixture);
168165
tick();
169166

170167
// Then close and check behavior.
171-
sidenav.close().then(() => {
172-
closeCalled = true;
173-
}, () => {
174-
closeCancelled = true;
168+
sidenav.close().then((result) => {
169+
closeResult = result;
175170
});
176171
// We do not call transition end, open directly.
177-
sidenav.open().then(() => {
178-
openCalled = true;
172+
sidenav.open().then((result) => {
173+
openResult = result;
179174
});
180175

181176
endSidenavTransition(fixture);
182177
tick();
183178

184-
expect(closeCalled).toBe(false);
185-
expect(closeCancelled).toBe(true);
186-
expect(openCalled).toBe(true);
179+
expect(closeResult.type).toBe('close');
180+
expect(closeResult.animationFinished).toBe(false);
181+
expect(openResult.type).toBe('open');
182+
expect(openResult.animationFinished).toBe(true);
187183
tick();
188184
}));
189185

@@ -219,7 +215,7 @@ describe('MdSidenav', () => {
219215
expect(sidenavEl.classList).not.toContain('md-sidenav-closed');
220216
expect(sidenavEl.classList).toContain('md-sidenav-opened');
221217

222-
expect((testComponent as any)._openPromise).toBeNull();
218+
expect((testComponent as any)._toggleAnimationPromise).toBeNull();
223219
});
224220

225221
it('should remove align attr from DOM', () => {

src/lib/sidenav/sidenav.ts

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ export class MdDuplicatedSidenavError extends MdError {
2525
}
2626
}
2727

28+
29+
/** Sidenav toggle promise result. */
30+
export class MdSidenavToggleResult {
31+
constructor(public type: 'open' | 'close', public animationFinished: boolean) {}
32+
}
33+
34+
2835
/**
2936
* <md-sidenav> component.
3037
*
@@ -106,6 +113,15 @@ export class MdSidenav implements AfterContentInit {
106113
/** Event emitted when the sidenav alignment changes. */
107114
@Output('align-changed') onAlignChanged = new EventEmitter<void>();
108115

116+
/** The current toggle animation promise. `null` if no animation is in progress. */
117+
private _toggleAnimationPromise: Promise<MdSidenavToggleResult> = null;
118+
119+
/**
120+
* The current toggle animation promise resolution function.
121+
* `null` if no animation is in progress.
122+
*/
123+
private _resolveToggleAnimationPromise: (animationFinished: boolean) => void = null;
124+
109125
/**
110126
* @param _elementRef The DOM element reference. Used for transition and width calculation.
111127
* If not available we do not hook on transitions.
@@ -115,9 +131,9 @@ export class MdSidenav implements AfterContentInit {
115131
ngAfterContentInit() {
116132
// This can happen when the sidenav is set to opened in the template and the transition
117133
// isn't ended.
118-
if (this._openPromise) {
119-
this._openPromiseResolve();
120-
this._openPromise = null;
134+
if (this._toggleAnimationPromise) {
135+
this._resolveToggleAnimationPromise(true);
136+
this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null;
121137
}
122138
}
123139

@@ -134,15 +150,15 @@ export class MdSidenav implements AfterContentInit {
134150

135151
/** Open this sidenav, and return a Promise that will resolve when it's fully opened (or get
136152
* rejected if it didn't). */
137-
open(): Promise<void> {
153+
open(): Promise<MdSidenavToggleResult> {
138154
return this.toggle(true);
139155
}
140156

141157
/**
142158
* Close this sidenav, and return a Promise that will resolve when it's fully closed (or get
143159
* rejected if it didn't).
144160
*/
145-
close(): Promise<void> {
161+
close(): Promise<MdSidenavToggleResult> {
146162
return this.toggle(false);
147163
}
148164

@@ -151,47 +167,35 @@ export class MdSidenav implements AfterContentInit {
151167
* close() when it's closed.
152168
* @param isOpen
153169
*/
154-
toggle(isOpen: boolean = !this.opened): Promise<void> {
155-
if (!this.valid) { return Promise.resolve(null); }
170+
toggle(isOpen: boolean = !this.opened): Promise<MdSidenavToggleResult> {
171+
if (!this.valid) {
172+
return Promise.resolve(new MdSidenavToggleResult(isOpen ? 'open' : 'close', true));
173+
}
156174

157175
// Shortcut it if we're already opened.
158176
if (isOpen === this.opened) {
159-
if (!this._transition) {
160-
return Promise.resolve(null);
161-
} else {
162-
return isOpen ? this._openPromise : this._closePromise;
163-
}
177+
return this._toggleAnimationPromise ||
178+
Promise.resolve(new MdSidenavToggleResult(isOpen ? 'open' : 'close', true));
164179
}
165180

166181
this._opened = isOpen;
167-
this._transition = true;
168182

169183
if (isOpen) {
170184
this.onOpenStart.emit();
171185
} else {
172186
this.onCloseStart.emit();
173187
}
174188

175-
if (isOpen) {
176-
if (this._openPromise == null) {
177-
this._openPromise = new Promise<void>((resolve, reject) => {
178-
this._openPromiseResolve = resolve;
179-
this._openPromiseReject = reject;
180-
});
181-
}
182-
return this._openPromise;
183-
} else {
184-
if (this._closePromise == null) {
185-
this._closePromise = new Promise<void>((resolve, reject) => {
186-
this._closePromiseResolve = resolve;
187-
this._closePromiseReject = reject;
188-
});
189-
}
190-
return this._closePromise;
189+
if (this._toggleAnimationPromise) {
190+
this._resolveToggleAnimationPromise(false);
191191
}
192+
this._toggleAnimationPromise = new Promise<MdSidenavToggleResult>(resolve => {
193+
this._resolveToggleAnimationPromise = animationFinished =>
194+
resolve(new MdSidenavToggleResult(isOpen ? 'open' : 'close', animationFinished));
195+
});
196+
return this._toggleAnimationPromise;
192197
}
193198

194-
195199
/**
196200
* When transition has finished, set the internal state for classes and emit the proper event.
197201
* The event passed is actually of type TransitionEvent, but that type is not available in
@@ -201,43 +205,30 @@ export class MdSidenav implements AfterContentInit {
201205
if (transitionEvent.target == this._elementRef.nativeElement
202206
// Simpler version to check for prefixes.
203207
&& transitionEvent.propertyName.endsWith('transform')) {
204-
this._transition = false;
205208
if (this._opened) {
206-
if (this._openPromise != null) {
207-
this._openPromiseResolve();
208-
}
209-
if (this._closePromise != null) {
210-
this._closePromiseReject();
211-
}
212-
213209
this.onOpen.emit();
214210
} else {
215-
if (this._closePromise != null) {
216-
this._closePromiseResolve();
217-
}
218-
if (this._openPromise != null) {
219-
this._openPromiseReject();
220-
}
221-
222211
this.onClose.emit();
223212
}
224213

225-
this._openPromise = null;
226-
this._closePromise = null;
214+
if (this._toggleAnimationPromise) {
215+
this._resolveToggleAnimationPromise(true);
216+
this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null;
217+
}
227218
}
228219
}
229220

230221
get _isClosing() {
231-
return !this._opened && this._transition;
222+
return !this._opened && !!this._toggleAnimationPromise;
232223
}
233224
get _isOpening() {
234-
return this._opened && this._transition;
225+
return this._opened && !!this._toggleAnimationPromise;
235226
}
236227
get _isClosed() {
237-
return !this._opened && !this._transition;
228+
return !this._opened && !this._toggleAnimationPromise;
238229
}
239230
get _isOpened() {
240-
return this._opened && !this._transition;
231+
return this._opened && !this._toggleAnimationPromise;
241232
}
242233
get _isEnd() {
243234
return this.align == 'end';
@@ -258,14 +249,6 @@ export class MdSidenav implements AfterContentInit {
258249
}
259250
return 0;
260251
}
261-
262-
private _transition: boolean = false;
263-
private _openPromise: Promise<void>;
264-
private _openPromiseResolve: () => void;
265-
private _openPromiseReject: () => void;
266-
private _closePromise: Promise<void>;
267-
private _closePromiseResolve: () => void;
268-
private _closePromiseReject: () => void;
269252
}
270253

271254
/**

0 commit comments

Comments
 (0)