Angular 2: Iterate over reactive form controls and mark as dirty
Asked Answered
R

16

171

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

Rating answered 14/2, 2017 at 20:1 Comment(0)
S
64

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();
        }
    });
}
Salaam answered 22/6, 2018 at 16:44 Comment(2)
will instanceof always work after being transpiled by Typescript?Tremolant
@Tremolant instanceof is not a TypeScript-specific keyword (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) Neither is the class data type.Salaam
R
286

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();
    });
Rating answered 14/2, 2017 at 20:4 Comment(7)
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?Orson
Object.keys( this.registerForm.controls).forEach(key => { this.registerForm.controls[key].markAsDirty(); });Knitting
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.Martinamartindale
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.Infirm
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...Lienlienhard
Oh, you are absolutely welcome, thank you for your contribution to this question.Rating
does this handle formbuilders within formbuilders, it does not seem to be working for meVisually
L
101

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  

}
Lazurite answered 25/9, 2017 at 21:18 Comment(4)
how to get index of the loop?Evenhanded
For those using TSLint, the code works, but TSLint complains with "for (... in ...) statements must be filtered with an if statement (forin)".Robb
tslint is pointing out, a quote from the JavaScript documentation of the for...in statement #40770925Astronaut
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 typeOquinn
S
64

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();
        }
    });
}
Salaam answered 22/6, 2018 at 16:44 Comment(2)
will instanceof always work after being transpiled by Typescript?Tremolant
@Tremolant instanceof is not a TypeScript-specific keyword (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…) Neither is the class data type.Salaam
L
12

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);
}
Lienlienhard answered 26/6, 2019 at 20:43 Comment(3)
Are you sure? API doc has the get method already for Abstract Control (angular.io/api/forms/AbstractControl#get). I didn't migrate yet. Now I'm scared (⊙_◎)Baryta
@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!Lienlienhard
I don't think they removed it, get works for me in Angular 8. Also it is still in the documentation angular.io/api/forms/AbstractControl#getIncogitant
S
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 ;)

Suffruticose answered 19/3, 2018 at 10:53 Comment(1)
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!Longeron
K
6

    Object.keys( this.registerForm.controls).forEach(key => {
       this.registerForm.controls[key].markAsDirty();
    });
Knitting answered 23/8, 2017 at 15:25 Comment(0)
L
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);
    }
  });
}
Lawmaker answered 27/9, 2019 at 11:30 Comment(0)
M
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();
    }
Milone answered 1/6, 2021 at 5:9 Comment(1)
And remove the Oject.keys from the loop so that it doesn't get called on every iteration.Moonlight
D
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);
    });
Dragrope answered 5/9, 2021 at 23:59 Comment(0)
T
2

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

Transom answered 21/12, 2021 at 7:20 Comment(0)
E
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.

Ellata answered 28/5, 2023 at 11:14 Comment(0)
C
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; 
 })
Cherry answered 8/7, 2019 at 22:6 Comment(0)
C
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);
Cheep answered 17/11, 2020 at 19:24 Comment(0)
N
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>
Numb answered 10/9, 2021 at 18:47 Comment(1)
what is ClrFormOconnell
S
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();
})    
Slavish answered 5/12, 2021 at 11:20 Comment(2)
Property '_forEachChild' does not exist on type 'FormGroup'.Crouse
"_" prefix would indicate a private APIPhilippopolis
J
0

What the other answer lack is a way to do it without generating any issues with ts-lint or typescript.

Here is a way to do it.

Object.keys(this.form.controls).forEach((key) => {
  const control = this.form.get(key);
  if (control) {
    control.markAsDirty();
    //or if we wanted to change the value
    //control.setValue(value);
  }
});
Jeuz answered 29/4 at 23:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.