From a8cbd0a076b2915df146a81e8b9b7d5dc8ea3ac4 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 6 Feb 2017 20:53:05 +0100 Subject: [PATCH] fix(select): avoid going into infinite loop under certain conditions 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 #2950. --- src/lib/select/select.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 1a3dabfd5fc6..93a757e27c1d 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -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 = ''; @@ -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(); @@ -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); } /**