Skip to content

Commit fad99cc

Browse files
karajasonaden
authored andcommitted
fix(forms): inserting and removing controls should work in re-bound form arrays (#21822)
Closes #21501 PR Close #21822
1 parent 3f5ead3 commit fad99cc

File tree

2 files changed

+104
-8
lines changed

2 files changed

+104
-8
lines changed

packages/forms/src/model.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -1311,25 +1311,19 @@ export class FormArray extends AbstractControl {
13111311
this._onCollectionChange();
13121312
}
13131313

1314-
/**
1315-
* Insert a new {@link AbstractControl} at the given `index` in the array.
1316-
*/
1314+
/** Insert a new {@link AbstractControl} at the given `index` in the array. */
13171315
insert(index: number, control: AbstractControl): void {
13181316
this.controls.splice(index, 0, control);
13191317

13201318
this._registerControl(control);
13211319
this.updateValueAndValidity();
1322-
this._onCollectionChange();
13231320
}
13241321

1325-
/**
1326-
* Remove the control at the given `index` in the array.
1327-
*/
1322+
/** Remove the control at the given `index` in the array. */
13281323
removeAt(index: number): void {
13291324
if (this.controls[index]) this.controls[index]._registerOnCollectionChange(() => {});
13301325
this.controls.splice(index, 1);
13311326
this.updateValueAndValidity();
1332-
this._onCollectionChange();
13331327
}
13341328

13351329
/**

packages/forms/test/reactive_integration_spec.ts

+102
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,108 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
321321
expect(input.nativeElement.value).toEqual('LA');
322322
});
323323

324+
it('should remove controls correctly after re-binding a form array', () => {
325+
const fixture = initTest(FormArrayComp);
326+
const cityArray =
327+
new FormArray([new FormControl('SF'), new FormControl('NY'), new FormControl('LA')]);
328+
const form = new FormGroup({cities: cityArray});
329+
fixture.componentInstance.form = form;
330+
fixture.componentInstance.cityArray = cityArray;
331+
fixture.detectChanges();
332+
333+
const newArr =
334+
new FormArray([new FormControl('SF'), new FormControl('NY'), new FormControl('LA')]);
335+
fixture.componentInstance.cityArray = newArr;
336+
form.setControl('cities', newArr);
337+
fixture.detectChanges();
338+
339+
newArr.removeAt(0);
340+
fixture.detectChanges();
341+
342+
let inputs = fixture.debugElement.queryAll(By.css('input'));
343+
expect(inputs[0].nativeElement.value).toEqual('NY');
344+
expect(inputs[1].nativeElement.value).toEqual('LA');
345+
346+
let firstInput = inputs[0].nativeElement;
347+
firstInput.value = 'new value';
348+
dispatchEvent(firstInput, 'input');
349+
fixture.detectChanges();
350+
351+
expect(newArr.value).toEqual(['new value', 'LA']);
352+
353+
newArr.removeAt(0);
354+
fixture.detectChanges();
355+
356+
firstInput = fixture.debugElement.query(By.css('input')).nativeElement;
357+
firstInput.value = 'last one';
358+
dispatchEvent(firstInput, 'input');
359+
fixture.detectChanges();
360+
361+
expect(newArr.value).toEqual(['last one']);
362+
363+
newArr.get([0]) !.setValue('set value');
364+
fixture.detectChanges();
365+
366+
firstInput = fixture.debugElement.query(By.css('input')).nativeElement;
367+
expect(firstInput.value).toEqual('set value');
368+
});
369+
370+
it('should submit properly after removing controls on a re-bound array', () => {
371+
const fixture = initTest(FormArrayComp);
372+
const cityArray =
373+
new FormArray([new FormControl('SF'), new FormControl('NY'), new FormControl('LA')]);
374+
const form = new FormGroup({cities: cityArray});
375+
fixture.componentInstance.form = form;
376+
fixture.componentInstance.cityArray = cityArray;
377+
fixture.detectChanges();
378+
379+
const newArr =
380+
new FormArray([new FormControl('SF'), new FormControl('NY'), new FormControl('LA')]);
381+
fixture.componentInstance.cityArray = newArr;
382+
form.setControl('cities', newArr);
383+
fixture.detectChanges();
384+
385+
newArr.removeAt(0);
386+
fixture.detectChanges();
387+
388+
const formEl = fixture.debugElement.query(By.css('form'));
389+
expect(() => dispatchEvent(formEl.nativeElement, 'submit')).not.toThrowError();
390+
});
391+
392+
it('should insert controls properly on a re-bound array', () => {
393+
const fixture = initTest(FormArrayComp);
394+
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
395+
const form = new FormGroup({cities: cityArray});
396+
fixture.componentInstance.form = form;
397+
fixture.componentInstance.cityArray = cityArray;
398+
fixture.detectChanges();
399+
400+
const newArr = new FormArray([new FormControl('SF'), new FormControl('NY')]);
401+
fixture.componentInstance.cityArray = newArr;
402+
form.setControl('cities', newArr);
403+
fixture.detectChanges();
404+
405+
newArr.insert(1, new FormControl('LA'));
406+
fixture.detectChanges();
407+
408+
let inputs = fixture.debugElement.queryAll(By.css('input'));
409+
expect(inputs[0].nativeElement.value).toEqual('SF');
410+
expect(inputs[1].nativeElement.value).toEqual('LA');
411+
expect(inputs[2].nativeElement.value).toEqual('NY');
412+
413+
const lastInput = inputs[2].nativeElement;
414+
lastInput.value = 'Tulsa';
415+
dispatchEvent(lastInput, 'input');
416+
fixture.detectChanges();
417+
418+
expect(newArr.value).toEqual(['SF', 'LA', 'Tulsa']);
419+
420+
newArr.get([2]) !.setValue('NY');
421+
fixture.detectChanges();
422+
423+
expect(lastInput.value).toEqual('NY');
424+
});
425+
324426
});
325427

326428
});

0 commit comments

Comments
 (0)