Skip to content

Commit 707a90a

Browse files
committed
fix: more elegant approach to initial value and add unit test
1 parent 8ee1edc commit 707a90a

File tree

2 files changed

+45
-21
lines changed

2 files changed

+45
-21
lines changed

src/lib/select/select.spec.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {TestBed, async, ComponentFixture, fakeAsync, tick} from '@angular/core/testing';
22
import {By} from '@angular/platform-browser';
3-
import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core';
3+
import {Component, DebugElement, QueryList, ViewChild, ViewChildren, OnInit} from '@angular/core';
44
import {MdSelectModule} from './index';
55
import {OverlayContainer} from '../core/overlay/overlay-container';
66
import {MdSelect} from './select';
@@ -26,7 +26,9 @@ describe('MdSelect', () => {
2626
SelectInitWithoutOptions,
2727
SelectWithChangeEvent,
2828
CustomSelectAccessor,
29-
CompWithCustomSelect
29+
CompWithCustomSelect,
30+
SelectWithErrorSibling,
31+
ThrowsErrorOnInit,
3032
],
3133
providers: [
3234
{provide: OverlayContainer, useFactory: () => {
@@ -1221,6 +1223,14 @@ describe('MdSelect', () => {
12211223
});
12221224
}));
12231225

1226+
it('should not crash the browser when a sibling throws an error on init', async(() => {
1227+
// Note that this test can be considered successful if the error being thrown didn't
1228+
// end up crashing the testing setup altogether.
1229+
expect(() => {
1230+
TestBed.createComponent(SelectWithErrorSibling).detectChanges();
1231+
}).toThrowError(new RegExp('Oh no!', 'g'));
1232+
}));
1233+
12241234
});
12251235

12261236
describe('change event', () => {
@@ -1433,6 +1443,28 @@ class CompWithCustomSelect {
14331443
@ViewChild(CustomSelectAccessor) customAccessor: CustomSelectAccessor;
14341444
}
14351445

1446+
@Component({
1447+
selector: 'select-infinite-loop',
1448+
template: `
1449+
<md-select [(ngModel)]="value"></md-select>
1450+
<throws-error-on-init></throws-error-on-init>
1451+
`
1452+
})
1453+
class SelectWithErrorSibling {
1454+
value: string;
1455+
}
1456+
1457+
1458+
@Component({
1459+
selector: 'throws-error-on-init',
1460+
template: ''
1461+
})
1462+
export class ThrowsErrorOnInit implements OnInit {
1463+
ngOnInit() {
1464+
throw new Error('Oh no!');
1465+
}
1466+
}
1467+
14361468

14371469
/**
14381470
* TODO: Move this to core testing utility until Angular has event faking

src/lib/select/select.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {ControlValueAccessor, NgControl} from '@angular/forms';
2424
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
2525
import {ConnectedOverlayDirective} from '../core/overlay/overlay-directives';
2626
import {ViewportRuler} from '../core/overlay/position/viewport-ruler';
27+
import 'rxjs/add/operator/startWith';
28+
2729

2830
/**
2931
* The following style constants are necessary to save here in order
@@ -123,9 +125,6 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
123125
/** The placeholder displayed in the trigger of the select. */
124126
private _placeholder: string;
125127

126-
/** Holds a value that was attempted to be assigned before the component was initialized. */
127-
private _tempValue: any;
128-
129128
/** The animation state of the placeholder. */
130129
_placeholderState = '';
131130

@@ -246,15 +245,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
246245
this._initKeyManager();
247246
this._resetOptions();
248247

249-
// Assign any values that were deferred until the component is initialized.
250-
if (this._tempValue) {
251-
Promise.resolve(null).then(() => {
252-
this.writeValue(this._tempValue);
253-
this._tempValue = null;
254-
});
255-
}
256-
257-
this._changeSubscription = this.options.changes.subscribe(() => {
248+
this._changeSubscription = this.options.changes.startWith(null).subscribe(() => {
258249
this._resetOptions();
259250

260251
if (this._control) {
@@ -267,8 +258,14 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
267258

268259
ngOnDestroy() {
269260
this._dropSubscriptions();
270-
this._changeSubscription.unsubscribe();
271-
this._tabSubscription.unsubscribe();
261+
262+
if (this._changeSubscription) {
263+
this._changeSubscription.unsubscribe();
264+
}
265+
266+
if (this._tabSubscription) {
267+
this._tabSubscription.unsubscribe();
268+
}
272269
}
273270

274271
/** Toggles the overlay panel open or closed. */
@@ -304,11 +301,6 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
304301
writeValue(value: any): void {
305302
if (this.options) {
306303
this._setSelectionByValue(value);
307-
} else {
308-
// In reactive forms, writeValue() will be called synchronously before
309-
// the select's child options have been created. We save the value and
310-
// assign it after everything is set up, in order to avoid lost data.
311-
this._tempValue = value;
312304
}
313305
}
314306

0 commit comments

Comments
 (0)