Skip to content

Commit a07ee60

Browse files
aberonnithePunderWoman
authored andcommitted
feat(forms): add markAllAsDirty to AbstractControl (#58663)
Adds the `markAllAsDirty` method to the `AbstractControl` class. This method will mark the control and all its descendants as dirty. I pretty much just duplicated the behaviour and tests of `markAllAsTouched`. Fixes #55990 PR Close #58663
1 parent a611b23 commit a07ee60

File tree

6 files changed

+177
-8
lines changed

6 files changed

+177
-8
lines changed

goldens/public-api/forms/index.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
5050
hasError(errorCode: string, path?: Array<string | number> | string): boolean;
5151
hasValidator(validator: ValidatorFn): boolean;
5252
get invalid(): boolean;
53+
markAllAsDirty(opts?: {
54+
emitEvent?: boolean;
55+
}): void;
5356
markAllAsTouched(opts?: {
5457
emitEvent?: boolean;
5558
}): void;

packages/forms/src/model/abstract_model.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,22 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
990990
}
991991
}
992992

993+
/**
994+
* Marks the control and all its descendant controls as `dirty`.
995+
* @see {@link markAsDirty()}
996+
*
997+
* @param opts Configuration options that determine how the control propagates changes
998+
* and emits events after marking is applied.
999+
* * `emitEvent`: When true or not supplied (the default), the `events`
1000+
* observable emits a `PristineChangeEvent` with the `pristine` property being `false`.
1001+
* When false, no events are emitted.
1002+
*/
1003+
markAllAsDirty(opts: {emitEvent?: boolean} = {}): void {
1004+
this.markAsDirty({onlySelf: true, emitEvent: opts.emitEvent, sourceControl: this});
1005+
1006+
this._forEachChild((control: AbstractControl) => control.markAllAsDirty(opts));
1007+
}
1008+
9931009
/**
9941010
* Marks the control and all its descendant controls as `touched`.
9951011
* @see {@link markAsTouched()}

packages/forms/test/form_array_spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,59 @@ import {asyncValidator} from './util';
198198
});
199199
});
200200

201+
describe('markAllAsDirty', () => {
202+
it('should mark all descendants as dirty', () => {
203+
const formArray: FormArray = new FormArray([
204+
new FormControl('v1') as AbstractControl,
205+
new FormControl('v2'),
206+
new FormGroup({'c1': new FormControl('v1')}),
207+
new FormArray([new FormGroup({'c2': new FormControl('v2')})]),
208+
]);
209+
210+
expect(formArray.dirty).toBe(false);
211+
212+
const control1 = formArray.at(0) as FormControl;
213+
214+
expect(control1.dirty).toBe(false);
215+
216+
const group1 = formArray.at(2) as FormGroup;
217+
218+
expect(group1.dirty).toBe(false);
219+
220+
const group1Control1 = group1.get('c1') as FormControl;
221+
222+
expect(group1Control1.dirty).toBe(false);
223+
224+
const innerFormArray = formArray.at(3) as FormArray;
225+
226+
expect(innerFormArray.dirty).toBe(false);
227+
228+
const innerFormArrayGroup = innerFormArray.at(0) as FormGroup;
229+
230+
expect(innerFormArrayGroup.dirty).toBe(false);
231+
232+
const innerFormArrayGroupControl1 = innerFormArrayGroup.get('c2') as FormControl;
233+
234+
expect(innerFormArrayGroupControl1.dirty).toBe(false);
235+
236+
formArray.markAllAsDirty();
237+
238+
expect(formArray.dirty).toBe(true);
239+
240+
expect(control1.dirty).toBe(true);
241+
242+
expect(group1.dirty).toBe(true);
243+
244+
expect(group1Control1.dirty).toBe(true);
245+
246+
expect(innerFormArray.dirty).toBe(true);
247+
248+
expect(innerFormArrayGroup.dirty).toBe(true);
249+
250+
expect(innerFormArrayGroupControl1.dirty).toBe(true);
251+
});
252+
});
253+
201254
describe('markAllAsTouched', () => {
202255
it('should mark all descendants as touched', () => {
203256
const formArray: FormArray = new FormArray([

packages/forms/test/form_control_spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ import {asyncValidator, asyncValidatorReturningObservable} from './util';
2626
expect(c.value).toBe(null);
2727
});
2828

29+
describe('markAllAsDirty', () => {
30+
it('should mark only the control itself as dirty', () => {
31+
const control = new FormControl('');
32+
expect(control.dirty).toBe(false);
33+
control.markAllAsDirty();
34+
expect(control.dirty).toBe(true);
35+
});
36+
});
37+
2938
describe('markAllAsTouched', () => {
3039
it('should mark only the control itself as touched', () => {
3140
const control = new FormControl('');

packages/forms/test/form_group_spec.ts

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,86 @@ import {FormControlStatus, StatusChangeEvent} from '../src/model/abstract_model'
8282
});
8383
});
8484

85+
describe('markAllAsDirty', () => {
86+
let form: FormGroup;
87+
let logger: any[];
88+
89+
beforeEach(() => {
90+
form = new FormGroup({
91+
'c1': new FormControl('v1'),
92+
'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
93+
'array': new FormArray([
94+
new FormControl('v4') as any,
95+
new FormControl('v5'),
96+
new FormGroup({'c4': new FormControl('v4')}),
97+
]),
98+
});
99+
logger = [];
100+
});
101+
102+
it('should mark all descendants as dirty', () => {
103+
expect(form.dirty).toBe(false);
104+
105+
const control1 = form.get('c1') as FormControl;
106+
107+
expect(control1.dirty).toBe(false);
108+
109+
const innerGroup = form.get('group') as FormGroup;
110+
111+
expect(innerGroup.dirty).toBe(false);
112+
113+
const innerGroupFirstChildCtrl = innerGroup.get('c2') as FormControl;
114+
115+
expect(innerGroupFirstChildCtrl.dirty).toBe(false);
116+
117+
form.markAllAsDirty();
118+
119+
expect(form.dirty).toBe(true);
120+
121+
expect(control1.dirty).toBe(true);
122+
123+
expect(innerGroup.dirty).toBe(true);
124+
125+
expect(innerGroupFirstChildCtrl.dirty).toBe(true);
126+
127+
const innerGroupSecondChildCtrl = innerGroup.get('c3') as FormControl;
128+
129+
expect(innerGroupSecondChildCtrl.dirty).toBe(true);
130+
131+
const array = form.get('array') as FormArray;
132+
133+
expect(array.dirty).toBe(true);
134+
135+
const arrayFirstChildCtrl = array.at(0) as FormControl;
136+
137+
expect(arrayFirstChildCtrl.dirty).toBe(true);
138+
139+
const arraySecondChildCtrl = array.at(1) as FormControl;
140+
141+
expect(arraySecondChildCtrl.dirty).toBe(true);
142+
143+
const arrayFirstChildGroup = array.at(2) as FormGroup;
144+
145+
expect(arrayFirstChildGroup.dirty).toBe(true);
146+
147+
const arrayFirstChildGroupFirstChildCtrl = arrayFirstChildGroup.get('c4') as FormControl;
148+
149+
expect(arrayFirstChildGroupFirstChildCtrl.dirty).toBe(true);
150+
});
151+
152+
it('should not emit events when `FormGroup.markAllAsDirty` is called with `emitEvent: false`', () => {
153+
form.valueChanges.subscribe(() => logger.push('form'));
154+
form.markAllAsDirty({emitEvent: false});
155+
expect(logger).toEqual([]);
156+
});
157+
});
158+
85159
describe('markAllAsTouched', () => {
86-
it('should mark all descendants as touched', () => {
87-
const formGroup: FormGroup = new FormGroup({
160+
let form: FormGroup;
161+
let logger: any[];
162+
163+
beforeEach(() => {
164+
form = new FormGroup({
88165
'c1': new FormControl('v1'),
89166
'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
90167
'array': new FormArray([
@@ -93,24 +170,27 @@ import {FormControlStatus, StatusChangeEvent} from '../src/model/abstract_model'
93170
new FormGroup({'c4': new FormControl('v4')}),
94171
]),
95172
});
173+
logger = [];
174+
});
96175

97-
expect(formGroup.touched).toBe(false);
176+
it('should mark all descendants as touched', () => {
177+
expect(form.touched).toBe(false);
98178

99-
const control1 = formGroup.get('c1') as FormControl;
179+
const control1 = form.get('c1') as FormControl;
100180

101181
expect(control1.touched).toBe(false);
102182

103-
const innerGroup = formGroup.get('group') as FormGroup;
183+
const innerGroup = form.get('group') as FormGroup;
104184

105185
expect(innerGroup.touched).toBe(false);
106186

107187
const innerGroupFirstChildCtrl = innerGroup.get('c2') as FormControl;
108188

109189
expect(innerGroupFirstChildCtrl.touched).toBe(false);
110190

111-
formGroup.markAllAsTouched();
191+
form.markAllAsTouched();
112192

113-
expect(formGroup.touched).toBe(true);
193+
expect(form.touched).toBe(true);
114194

115195
expect(control1.touched).toBe(true);
116196

@@ -122,7 +202,7 @@ import {FormControlStatus, StatusChangeEvent} from '../src/model/abstract_model'
122202

123203
expect(innerGroupSecondChildCtrl.touched).toBe(true);
124204

125-
const array = formGroup.get('array') as FormArray;
205+
const array = form.get('array') as FormArray;
126206

127207
expect(array.touched).toBe(true);
128208

@@ -142,6 +222,12 @@ import {FormControlStatus, StatusChangeEvent} from '../src/model/abstract_model'
142222

143223
expect(arrayFirstChildGroupFirstChildCtrl.touched).toBe(true);
144224
});
225+
226+
it('should not emit events when `FormGroup.markAllAsTouched` is called with `emitEvent: false`', () => {
227+
form.valueChanges.subscribe(() => logger.push('form'));
228+
form.markAllAsTouched({emitEvent: false});
229+
expect(logger).toEqual([]);
230+
});
145231
});
146232

147233
describe('adding and removing controls', () => {

packages/forms/test/reactive_integration_spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,8 @@ describe('reactive forms integration tests', () => {
13901390
expect(fcEvents.length).toBe(0);
13911391
fc.markAsTouched({emitEvent: false});
13921392
expect(fcEvents.length).toBe(0);
1393+
fc.markAllAsDirty({emitEvent: false});
1394+
expect(fcEvents.length).toBe(0);
13931395
fc.markAllAsTouched({emitEvent: false});
13941396
expect(fcEvents.length).toBe(0);
13951397
fc.markAsUntouched({emitEvent: false});

0 commit comments

Comments
 (0)