14

I have a reactive form where on load no fields are required. If an option is selected that will add additional form elements into the formGroup then the new shown fields will be all required. If the nickname field is hidden then you should be able to submit the form just fine. If the nickname is shown then the nickname field is required and the submit button is disabled until the nickname field is full. Here is a sample of what I want to do.

My question is, how can I enable/disable validation once the form element is shown/hidden?

App.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule, ReactiveFormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

App.component.ts

import { Component, OnInit } from '@angular/core';
import { Validators, FormControl, FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit  {
  name = 'My Reactive Form';

  constructor(
    private fb: FormBuilder
  ) {}

  myForm: FormGroup;
  showNick: boolean = false;

  ngOnInit() {
    this.myForm = this.fb.group({
      'firstName': new FormControl(),
      'nickName': new FormControl('', Validators.required),
      'lastName': new FormControl()
    })
  }

  toggleNick() {
    this.showNick = !this.showNick;
  }
}

app.component.html

<form [formGroup]="myForm">
  <div class="my-box">
    <label>
      First Name
      <input type="text" formControlName="firstName">
    </label>
  </div>
  <div class="my-box nickname">
    Nickname? &nbsp; <a (click)="toggleNick()">yes / no</a>
  </div>
  <div class="my-box" *ngIf="showNick">
    <label>
      Nickname
      <input type="text" formControlName="nickName">
      <span class="validation-message" *ngIf="!myForm.controls['nickName'].valid && myForm.controls['nickName'].dirty">
    This field is invalid
  </span>
    </label>
  </div>
  <div class="my-box">
    <label>
      Last Name
      <input type="text" formControlName="lastName">
    </label>
  </div>
  <button [disabled]="myForm.invalid">Submit</button>
</form>
iChido
  • 3,972
  • 6
  • 26
  • 29
  • It seems you have not written the code to do what you describe. Something stopping you? Where is your problem in all this? – R. Richards Mar 19 '18 at 20:51
  • I haven't been able to accomplish what I need. I will edit my question at the top. Basically if the element is hidden then there is still something required in the formGroup. How can I toggle validation once the form elements is hidden? – iChido Mar 19 '18 at 20:53
  • Here is my working sample with the solution provided by DeborahK: https://stackblitz.com/edit/angular-twjirf – iChido Mar 19 '18 at 22:08

4 Answers4

23

In my application, I have a similar requirement. If the user asks to be notified by text, the phone is required. Otherwise the phone number is optional.

I wrote this method:

setNotification(notifyVia: string): void {
    const phoneControl = this.customerForm.get('phone');
    if (notifyVia === 'text') {
        phoneControl.setValidators(Validators.required);
    } else {
        phoneControl.clearValidators();
    }
    phoneControl.updateValueAndValidity();
}

It is called from this code which is in the ngOnInit:

    this.customerForm.get('notification').valueChanges
                     .subscribe(value => this.setNotification(value));

If the user changes the notification field (which is a radio button), it calls the setNotification method passing in the value. If the value is notification by 'text', it sets the phone's validation to required.

Otherwise it clears the phone field's validation.

Then it must call updateValueAndValidity to update the form info with this new validation.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
  • thanks for such nice answer . one problem is there , I am using some common fields and some diff fields(hidden/show) on base of selected user , how can i clear or reset form along with your answer .thanks – naila naseem May 09 '18 at 12:42
  • 1
    I had seen you do this in your pluralsight course Deborah but had completely spaced it. I'm glad you posted here as well. – Blair Holmes Aug 29 '19 at 16:18
19

Even the fields are hidden from user the fields are active in the reactive from. So simply you need to disable the field from the reactive from by using following code

this.myForm.get("nickName").disable();

Change the function toggleNick() as given below

toggleNick() {
    this.showNick = !this.showNick;
    if(showNick) {
         this.myForm.get("nickName").enable();
    } else {
         this.myForm.get("nickName").disable();
    }
}
vivekkurien
  • 1,524
  • 2
  • 18
  • 29
  • 2
    This should be the excepted answer. I didn't realize disabling pulled the control value from the form value. I spent hours trying to remove the control from the form on hide then add it back to the parent group on show, but the field template would lose it's connection to it's value in the parent group. So, it wouldn't update the form value after it had been removed and then added back to the form. Thanks!!!!!!! – Nayfin Jun 15 '19 at 21:30
  • 1
    This is the only option that worked for me as well. I had a dynamic formArray with each element having validators. While clear validators call worked. I cant use the set validators as no such function exists for formArray. – dreamerkumar Jul 08 '20 at 06:26
2

I think the best option for to do this it's make fields part of inital form with all theirs validations and when you want disable and enable fields or nested forms programmatically.

Example: https://stackblitz.com/edit/angular-ojebff

https://stackblitz.com/edit/angular-ojebff?embed=1&file=app/input-errors-example.html

andy
  • 2,362
  • 2
  • 18
  • 19
1

I didn't find those solutions viable if you have a lot of optional fields in a complex form.

What I did is somtehing like that :

export class MyComponent implements AfterViewChecked {

 @ViewChildren(FormControlName) allFormControlInDOM: QueryList<FormControlName>;

 // ...

  ngAfterViewChecked(): void {
    // We must disable the controls that are not in the DOM
    // If we don't, they are used for validating the form
    if (this.allFormControlInDOM) {
      const controls: { [p: string]: AbstractControl } = this.myForm.controls;
      for (const control in controls) {
        if (controls.hasOwnProperty(control) && controls[control] instanceof FormControl) {
          const found = this.allFormControlInDOM.find((item: FormControlName) => item.name === control);
          if (found) {
            controls[control].enable();
          } else {
            controls[control].disable();
            controls[control].setValue(null);
          }
        }
      }
    }
  }

// ...

}

I hope that can help :)

Klem231188
  • 106
  • 1
  • 3