Skip to content

Commit

Permalink
fix(select): avoid going into infinite loop under certain conditions
Browse files Browse the repository at this point in the history
Currently `md-select` calls `writeValue` recursively until the `QueryList` with all of the options is initialized. This usually means 1-2 iterations max, however in certain conditions (e.g. a sibling component throws an error on init) this may not happen and the browser would get thrown into an infinite loop.

This change switches to saving the attempted value assignments in a property and applying it after initialization.

Fixes angular#2950.
  • Loading branch information
crisbeto committed Feb 7, 2017
1 parent 4b830d3 commit a8cbd0a
Showing 1 changed file with 18 additions and 8 deletions.
26 changes: 18 additions & 8 deletions src/lib/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
/** The placeholder displayed in the trigger of the select. */
private _placeholder: string;

/** Holds a value that was attempted to be assigned before the component was initialized. */
private _tempValue: any;

/** The animation state of the placeholder. */
_placeholderState = '';

Expand Down Expand Up @@ -242,6 +245,15 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
ngAfterContentInit() {
this._initKeyManager();
this._resetOptions();

// Assign any values that were deferred until the component is initialized.
if (this._tempValue) {
Promise.resolve(null).then(() => {
this.writeValue(this._tempValue);
this._tempValue = null;
});
}

this._changeSubscription = this.options.changes.subscribe(() => {
this._resetOptions();

Expand Down Expand Up @@ -290,16 +302,14 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
* @param value New value to be written to the model.
*/
writeValue(value: any): void {
if (!this.options) {
if (this.options) {
this._setSelectionByValue(value);
} else {
// In reactive forms, writeValue() will be called synchronously before
// the select's child options have been created. It's necessary to call
// writeValue() again after the options have been created to ensure any
// initial view value is set.
Promise.resolve(null).then(() => this.writeValue(value));
return;
// the select's child options have been created. We save the value and
// assign it after everything is set up, in order to avoid lost data.
this._tempValue = value;
}

this._setSelectionByValue(value);
}

/**
Expand Down

0 comments on commit a8cbd0a

Please sign in to comment.