From 45bf911df8c2767df75701216a1140306ad803f0 Mon Sep 17 00:00:00 2001 From: alsami Date: Mon, 29 Oct 2018 09:36:17 +0100 Subject: [PATCH] feat(forms): add `markAllAsTouched()` to `AbstractControl` (#26812) Add functionality to mark a control and its descendant controls as touched Closes #19400 PR Close #26812 --- packages/forms/src/model.ts | 10 ++++ packages/forms/test/form_array_spec.ts | 52 ++++++++++++++++++++ packages/forms/test/form_control_spec.ts | 9 ++++ packages/forms/test/form_group_spec.ts | 61 ++++++++++++++++++++++++ tools/public_api_guard/forms/forms.d.ts | 1 + 5 files changed, 133 insertions(+) diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index 0fb5282f22..e4c1665ecf 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -364,6 +364,16 @@ export abstract class AbstractControl { } } + /** + * Marks the control and all its descendant controls as `touched`. + * @see `markAsTouched()` + */ + markAllAsTouched(): void { + this.markAsTouched({onlySelf: true}); + + this._forEachChild((control: AbstractControl) => control.markAllAsTouched()); + } + /** * Marks the control as `untouched`. * diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index 95c608828c..ec40306ce5 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -96,6 +96,58 @@ import {of } from 'rxjs'; }); }); + describe('markAllAsTouched', () => { + it('should mark all descendants as touched', () => { + const formArray: FormArray = new FormArray([ + new FormControl('v1'), new FormControl('v2'), + new FormGroup({'c1': new FormControl('v1')}), + new FormArray([new FormGroup({'c2': new FormControl('v2')})]) + ]); + + expect(formArray.touched).toBe(false); + + const control1 = formArray.at(0) as FormControl; + + expect(control1.touched).toBe(false); + + const group1 = formArray.at(2) as FormGroup; + + expect(group1.touched).toBe(false); + + const group1Control1 = group1.get('c1') as FormControl; + + expect(group1Control1.touched).toBe(false); + + const innerFormArray = formArray.at(3) as FormArray; + + expect(innerFormArray.touched).toBe(false); + + const innerFormArrayGroup = innerFormArray.at(0) as FormGroup; + + expect(innerFormArrayGroup.touched).toBe(false); + + const innerFormArrayGroupControl1 = innerFormArrayGroup.get('c2') as FormControl; + + expect(innerFormArrayGroupControl1.touched).toBe(false); + + formArray.markAllAsTouched(); + + expect(formArray.touched).toBe(true); + + expect(control1.touched).toBe(true); + + expect(group1.touched).toBe(true); + + expect(group1Control1.touched).toBe(true); + + expect(innerFormArray.touched).toBe(true); + + expect(innerFormArrayGroup.touched).toBe(true); + + expect(innerFormArrayGroupControl1.touched).toBe(true); + }); + }); + describe('setValue', () => { let c: FormControl, c2: FormControl, a: FormArray; diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 2bc06e1171..c125700cd3 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -47,6 +47,15 @@ import {FormArray} from '@angular/forms/src/model'; expect(c.value).toBe(null); }); + describe('markAllAsTouched', () => { + it('should mark only the control itself as touched', () => { + const control = new FormControl(''); + expect(control.touched).toBe(false); + control.markAllAsTouched(); + expect(control.touched).toBe(true); + }); + }); + describe('boxed values', () => { it('should support valid boxed values on creation', () => { const c = new FormControl({value: 'some val', disabled: true}, null !, null !); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index 3105c00ff1..2996cfc010 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -86,6 +86,67 @@ import {of } from 'rxjs'; }); + describe('markAllAsTouched', () => { + it('should mark all descendants as touched', () => { + const formGroup: FormGroup = new FormGroup({ + 'c1': new FormControl('v1'), + 'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), + 'array': new FormArray([ + new FormControl('v4'), new FormControl('v5'), + new FormGroup({'c4': new FormControl('v4')}) + ]) + }); + + expect(formGroup.touched).toBe(false); + + const control1 = formGroup.get('c1') as FormControl; + + expect(control1.touched).toBe(false); + + const innerGroup = formGroup.get('group') as FormGroup; + + expect(innerGroup.touched).toBe(false); + + const innerGroupFirstChildCtrl = innerGroup.get('c2') as FormControl; + + expect(innerGroupFirstChildCtrl.touched).toBe(false); + + formGroup.markAllAsTouched(); + + expect(formGroup.touched).toBe(true); + + expect(control1.touched).toBe(true); + + expect(innerGroup.touched).toBe(true); + + expect(innerGroupFirstChildCtrl.touched).toBe(true); + + const innerGroupSecondChildCtrl = innerGroup.get('c3') as FormControl; + + expect(innerGroupSecondChildCtrl.touched).toBe(true); + + const array = formGroup.get('array') as FormArray; + + expect(array.touched).toBe(true); + + const arrayFirstChildCtrl = array.at(0) as FormControl; + + expect(arrayFirstChildCtrl.touched).toBe(true); + + const arraySecondChildCtrl = array.at(1) as FormControl; + + expect(arraySecondChildCtrl.touched).toBe(true); + + const arrayFirstChildGroup = array.at(2) as FormGroup; + + expect(arrayFirstChildGroup.touched).toBe(true); + + const arrayFirstChildGroupFirstChildCtrl = arrayFirstChildGroup.get('c4') as FormControl; + + expect(arrayFirstChildGroupFirstChildCtrl.touched).toBe(true); + }); + }); + describe('adding and removing controls', () => { it('should update value and validity when control is added', () => { const g = new FormGroup({'one': new FormControl('1')}); diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index ede0fdf7d9..f76319ff16 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -32,6 +32,7 @@ export declare abstract class AbstractControl { get(path: Array | string): AbstractControl | null; getError(errorCode: string, path?: Array | string): any; hasError(errorCode: string, path?: Array | string): boolean; + markAllAsTouched(): void; markAsDirty(opts?: { onlySelf?: boolean; }): void;