159

I would like to markAsDirty all the controls inside of a FormGroup.

Marcos J.C Kichel
  • 6,887
  • 8
  • 38
  • 78

15 Answers15

276

Found out that Object.keys can handle this..

    Object.keys(this.form.controls).forEach(key => {
      this.form.get(key).markAsDirty();
    });

For Angular 8+, use the following (based on Michelangelo answer):

    Object.keys(this.form.controls).forEach(key => {
      this.form.controls[key].markAsDirty();
    });
Luca Ritossa
  • 1,118
  • 11
  • 22
Marcos J.C Kichel
  • 6,887
  • 8
  • 38
  • 78
  • 2
    When I use this function in onSubmit I get the error `Cannot invoke an expression whose type lacks a call signature. Type 'AbstractControl' has no compatible call signatures.` Does anyone know why? – maidi Aug 12 '17 at 13:48
  • 1
    Object.keys( this.registerForm.controls).forEach(key => { this.registerForm.controls[key].markAsDirty(); }); – Foad Aug 23 '17 at 15:26
  • When I try Object.keys or even the "for in", I get nothing. Yet, if I console.log(form.controls) I can SEE all the various form controls contained with the object. I'm baffled. – Jake Shakesworth Nov 02 '17 at 19:27
  • Using Angular 5, the markAsDirty() / markAsTouched() doesn't recurse into any sub-FormGroups. I broke out the code above into a recursive function and call it on any sub-FormGroups. Works better w/ the current Angular Material UI project in case a user never touches a required element, I call it when the user tries to submit the form to mark any at that point. – Robert Jan 10 '18 at 19:19
  • 3
    Thnx for reading my post and updating your own answer. Official docs are also outdated so I had to figure this out by printing every line... – Michelangelo Jun 26 '19 at 22:28
  • Oh, you are absolutely welcome, thank you for your contribution to this question. – Marcos J.C Kichel Jun 26 '19 at 22:49
  • does this handle formbuilders within formbuilders, it does not seem to be working for me –  Dec 27 '19 at 20:00
96

For what it's worth, there's another way to do this without having to use Object.keys(...) magic:

for (const field in this.form.controls) { // 'field' is a string

  const control = this.form.get(field); // 'control' is a FormControl  

}
j3ff
  • 5,719
  • 8
  • 38
  • 51
Liviu Ilea
  • 1,494
  • 12
  • 13
  • how to get index of the loop? – SVK Mar 08 '19 at 09:55
  • 4
    For those using TSLint, the code works, but TSLint complains with "for (... in ...) statements must be filtered with an if statement (forin)". – Yennefer Apr 08 '19 at 13:10
  • 3
    tslint is pointing out, a quote from the JavaScript documentation of the for...in statement https://stackoverflow.com/questions/40770425/tslint-codelyzer-ng-lint-error-for-in-statements-must-be-filtere/45959874#45959874 – Egle Kreivyte May 01 '19 at 08:26
  • 1
    This works with Angular 14 whereas using `Object.keys(form).forEach` throws an error that : `Element implicitly has an 'any' type because expression of type 'string' can't be used to index type` – Raphael Pinel Sep 13 '22 at 11:40
61

The accepted answer is correct for a flat form structure, but does not completely answer the original question. A web page may require nested FormGroups and FormArrays, and we must account for this to create a robust solution.

public markControlsDirty(group: FormGroup | FormArray): void {
    Object.keys(group.controls).forEach((key: string) => {
        const abstractControl = group.controls[key];

        if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) {
            this.markControlsDirty(abstractControl);
        } else {
            abstractControl.markAsDirty();
        }
    });
}
grg
  • 5,023
  • 3
  • 34
  • 50
Keenan Diggs
  • 2,287
  • 16
  • 17
11

Seems that get function is not working anymore for retrieving specific values in your form in Angular 8, so this is how I solved it based on the answer of @Liviu Ilea.

for (const field in this.myForm.controls) { // 'field' is a string
  console.log(this.myForm.controls[field].value);
}
grg
  • 5,023
  • 3
  • 34
  • 50
Michelangelo
  • 5,888
  • 5
  • 31
  • 50
  • Are you sure? API doc has the get method already for Abstract Control (https://angular.io/api/forms/AbstractControl#get). I didn't migrate yet. Now I'm scared (⊙_◎) – Alan Grosz Aug 14 '19 at 13:34
  • @AlanGrosz Yeah, I saw that too when I was rewriting it but even when printing all the lines in the console I couldn't find any get method on the object. I think the documentation is behind. Good luck migrating! – Michelangelo Aug 14 '19 at 15:12
  • 1
    I don't think they removed it, get works for me in Angular 8. Also it is still in the documentation https://angular.io/api/forms/AbstractControl#get – Laszlo Sarvold Jun 24 '20 at 05:22
8

Using @Marcos answer I created a function that can be called passing a formGroup as parameter and it marks every formGroup children controls to dirty, just to make it usable from more places around the code putting it inside a service, for example.

public touchAllFormFields(formGroup: FormGroup): void {
    Object.keys(formGroup.controls).forEach((key) => {
        formGroup.get(key).markAsDirty();
    });
}

hope it helps ;)

Richard Dallaway
  • 4,250
  • 1
  • 28
  • 39
Hugo
  • 1,662
  • 18
  • 35
  • Perfect! Added to service, along w/similar functions to clearValidators, untouch, etc. Might want to add recursive check for nested controls, but this works for now. Thanks! – mc01 Apr 14 '18 at 02:13
6

    Object.keys( this.registerForm.controls).forEach(key => {
       this.registerForm.controls[key].markAsDirty();
    });
Foad
  • 390
  • 3
  • 6
5

This is what working for me

private markFormGroupTouched(formGroup: FormGroup) {
  Object.keys(formGroup.controls).forEach((key) => {
    const control = formGroup.controls[key];
    control.markAsDirty();
    if ((control instanceof FormGroup)) {
      this.markFormGroupTouched(control);
    }
  });
}
j3ff
  • 5,719
  • 8
  • 38
  • 51
omyfish
  • 291
  • 3
  • 4
4

Here, is my solution to your problem, I'm using for loop with an index, hope this helps.

    for (const key of Object.keys(this.forms.controls)) {
      this.forms.controls[key].markAsDirty();
    }
Synth Sloth
  • 126
  • 4
  • 14
  • 1
    And remove the Oject.keys from the loop so that it doesn't get called on every iteration. – zeezor May 25 '23 at 14:48
3

I was looking for a similar solution for a project with multiple forms having file uploads. I needed to create a Form Data object and copy all fields of form in multiple pages. This worked fine for Angular 11.

const formData : FormData = new FormData();

 Object.keys(this.staffForm.controls).forEach(key => {
      console.log("Control Key => "+key); 
      console.log("Control Val => "+this.staffForm.controls[key].value); 
      formData.append(key, this.staffForm.controls[key].value);
    });
Vishal Kumar
  • 4,419
  • 1
  • 25
  • 31
2

What worked for me is the following:

Object.values(this.myFormGroup.controls).forEach((myFormControl: FormControl) => {
    myFormControl.markAsDirty();
});

Not sure why everyone is using Object.keys() instead - .values is more direct.

Lawl
  • 21
  • 2
1

I create this function to make it* I have a control with name 'order', and pass index to him.

{"conditionGroups": [
   {
     "order": null,
     "conditions": []
   }
  ]
}


updateFormData() {
    const control = <FormArray>this.form.controls['conditionGroups'];  
    control.value.map((x,index)=>{
    x.order = index; 
 })
1

Based on @Keenan Diggs answer I wrote a generic function to traverse a flat or nested form, which accepts an operation to be performed against each form control:

export function traverseForm(
    form: FormGroup | FormArray, 
    fn: ((c: AbstractControl, name: string, path: string) => void),
    initialPath: string = '') {
  Object.keys(form.controls).forEach((key: string) => {
    const abstractControl = form.controls[key];
    const path = initialPath ? (initialPath + '.' + key) : key;
    fn(abstractControl, key, path);
    if (abstractControl instanceof FormGroup || abstractControl instanceof FormArray) {
        traverseForm(abstractControl, fn, path);
    }
  });
} 

To be used like this:

const markAsDirty = (ctrl: AbstractControl) => {
   if (!(abstractControl instanceof FormGroup) && !(abstractControl instanceof FormArray)) {
      abstractControl.markAsDirty();
   }
}
traverseForm(form, markAsDirty);
Alex Che
  • 6,659
  • 4
  • 44
  • 53
0

If you don't want to loop over your form control, you can use this way also...

export class NewComponent implements OnInit {
  @ViewChild(ClrForm) clrForm: ClrForm;

  form: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.buildForm();
  }

  onFormSubmit() {
    if (this.form.invalid) {
      this.clrForm.markAsDirty();
      return;
    }
  }

  private buildForm() {
    this.form = this.formBuilder.group({
      sender: [null, [Validators.required]],
      sentAt: ['', [Validators.required]]
    });
  }
}
<form clrForm [formGroup]="form" (submit)="onFormSubmit()">
  <div class="clr-form-control clr-row">
    <label for="sender" class="clr-control-label clr-col-4">Sender</label>
    <div class="clr-control-container clr-col-8">
      <app-custom-form-control formControlName="sender"></app-custom-form-control>
    </div>
  </div>

  <clr-date-container>
    <label class="clr-col-4">Data wysłania</label>
    <input
      type="date"
      class="clr-col-8"
      clrDate
      formControlName="sentAt"
    />
  </clr-date-container>

  <input type="submit" value="Save" />
</form>
MD. RAKIB HASAN
  • 3,670
  • 4
  • 22
  • 35
0

you can loop over a FormGroup's children using the _forEachChild() method of a formGroup. This worked for me for patching values in nested formGroups.

this.myForm.myFormGroup._forEachChild( control => {
  control.markAsDirty();
})    
Ali_Sh
  • 2,667
  • 3
  • 43
  • 66
0

Simple Solution to iterate formGroup controls to get values of each form controls:


formGroup: FormGroup;

this.formGroup = this.formBuilder.group({
      control1: new FormControl('value1'),
      control2: new FormControl(`value2`),
      control3: new FormControl('value3')
    });

Object.keys(this.formGroup.controls).forEach(key => {
      console.log(this.formGroup.controls[key].value)
});

// Output:

value1

value2

value4

khizer
  • 1,242
  • 15
  • 13