Skip to content

Commit 32ff21c

Browse files
karahansl
authored andcommitted
fix(forms): re-assigning options should not clear select
Fixes #18330
1 parent c65f18a commit 32ff21c

File tree

2 files changed

+59
-3
lines changed

2 files changed

+59
-3
lines changed

packages/forms/src/directives/select_control_value_accessor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
129129

130130
registerOnChange(fn: (value: any) => any): void {
131131
this.onChange = (valueString: string) => {
132-
this.value = valueString;
133-
fn(this._getOptionValue(valueString));
132+
this.value = this._getOptionValue(valueString);
133+
fn(this.value);
134134
};
135135
}
136136
registerOnTouched(fn: () => any): void { this.onTouched = fn; }

packages/forms/test/value_accessor_integration_spec.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Component, Directive, EventEmitter, Input, Output, Type} from '@angular/core';
1010
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
11-
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, ReactiveFormsModule, Validators} from '@angular/forms';
11+
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms';
1212
import {By} from '@angular/platform-browser/src/dom/debug/by';
1313
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
1414

@@ -210,6 +210,31 @@ export function main() {
210210
expect(sfOption.nativeElement.selected).toBe(true);
211211
});
212212

213+
it('should support re-assigning the options array with compareWith', () => {
214+
const fixture = initTest(FormControlSelectWithCompareFn);
215+
fixture.detectChanges();
216+
217+
// Option IDs start out as 0 and 1, so setting the select value to "1: Object"
218+
// will select the second option (NY).
219+
const select = fixture.debugElement.query(By.css('select'));
220+
select.nativeElement.value = '1: Object';
221+
dispatchEvent(select.nativeElement, 'change');
222+
fixture.detectChanges();
223+
224+
expect(fixture.componentInstance.form.value).toEqual({city: {id: 2, name: 'NY'}});
225+
226+
fixture.componentInstance.cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
227+
fixture.detectChanges();
228+
229+
// Now that the options array has been re-assigned, new option instances will
230+
// be created by ngFor. These instances will have different option IDs, subsequent
231+
// to the first: 2 and 3. For the second option to stay selected, the select
232+
// value will need to have the ID of the current second option: 3.
233+
const nyOption = fixture.debugElement.queryAll(By.css('option'))[1];
234+
expect(select.nativeElement.value).toEqual('3: Object');
235+
expect(nyOption.nativeElement.selected).toBe(true);
236+
});
237+
213238
});
214239

215240
describe('in template-driven forms', () => {
@@ -335,6 +360,37 @@ export function main() {
335360
expect(sfOption.nativeElement.selected).toBe(true);
336361
}));
337362

363+
it('should support re-assigning the options array with compareWith', fakeAsync(() => {
364+
const fixture = initTest(NgModelSelectWithCustomCompareFnForm);
365+
fixture.componentInstance.selectedCity = {id: 1, name: 'SF'};
366+
fixture.componentInstance.cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
367+
fixture.detectChanges();
368+
tick();
369+
370+
// Option IDs start out as 0 and 1, so setting the select value to "1: Object"
371+
// will select the second option (NY).
372+
const select = fixture.debugElement.query(By.css('select'));
373+
select.nativeElement.value = '1: Object';
374+
dispatchEvent(select.nativeElement, 'change');
375+
fixture.detectChanges();
376+
377+
const model = fixture.debugElement.children[0].injector.get(NgModel);
378+
expect(model.value).toEqual({id: 2, name: 'NY'});
379+
380+
fixture.componentInstance.cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
381+
fixture.detectChanges();
382+
tick();
383+
384+
// Now that the options array has been re-assigned, new option instances will
385+
// be created by ngFor. These instances will have different option IDs, subsequent
386+
// to the first: 2 and 3. For the second option to stay selected, the select
387+
// value will need to have the ID of the current second option: 3.
388+
const nyOption = fixture.debugElement.queryAll(By.css('option'))[1];
389+
expect(select.nativeElement.value).toEqual('3: Object');
390+
expect(nyOption.nativeElement.selected).toBe(true);
391+
}));
392+
393+
338394
});
339395

340396
});

0 commit comments

Comments
 (0)