2

I've been trying to use the model form in an Ionic/Angular2. I have a form that I want to do some conditional validation. The user starts with 6 fields that are required and then can choose between 'manual' and 'automatic' processing. If 'automatic' is chosen, 3 more form inputs/selects are rendered. I'd like these 3 fields to go from being not required to required.

Here's the model to start:

this.myForm = this.formBuilder.group({
  name: ['', Validators.compose([Validators.required, Validators.maxLength(45), Validators.minLength(2)])],
  img: [''],
  longDescription: ['', Validators.compose([Validators.required, Validators.maxLength(200)])],
  shortDescription: ['', Validators.compose([Validators.required, Validators.maxLength(45)])],
  processingType: [this.PROCESSING_TYPE.MANUAL],
  discountType: ['' ],
  discountRule: ['' ],
  discountAmount: ['' ],  // money, isNumbersOnly handled by custom validator
  product: [''],
  dateRuleDays: ['' ],
  dateRuleTimeStart: ['' ],
  dateRuleTimeEnd: ['' ],

  startDate: ['', Validators.required],
  expiryDate: ['', Validators.required ]
});

discountType, discountRule, discountAmount are the 3 fields that I'd like to toggle to being required.

Here's what I've tried:

markup where setProcessingType fn gets called (i can provide the full page if needed)

<!-- automatic or manual -->
<h4 margin-top text-center>Processing Type</h4>
<ion-row text-center>
  <ion-col width-50>
      <button class="width-80" color="primary" (click)="setProcessingType(PROCESSING_TYPE.MANUAL)" ion-button margin-top>{{ PROCESSING_TYPE.MANUAL }}</button>
  </ion-col>
  <ion-col width-50>
      <button class="width-80" color="primary" (click)="setProcessingType(PROCESSING_TYPE.AUTOMATIC)" ion-button margin-top>{{ PROCESSING_TYPE.AUTOMATIC }}</button>
  </ion-col>
</ion-row>

setProcessingType gets called when the user clicks between 'manual' and 'automatic' buttons. type: string is the new value that i will set to processingType.

setProcessingType(type: string): void {

this.myForm.patchValue({   // i welcome comments to correct this to 'setValue()' if needed
  processingType: type
});

let formCtrls = ['discountType', 'discountRule', 'discountAmount', 'dateRuleDays', 'dateRuleTimeStart', 'dateRuleTimeEnd'];
let add = [Validators.required];
let empty = [];

formCtrls.forEach((key, index) => {
  if (this.myForm.controls && this.myForm.controls[key]) {
    if (type === this.PROCESSING_TYPE.AUTOMATIC) {
        this.myForm.controls[key].setValidators([Validators.required]);
    } else {
      console.log('resetting validators...');
      this.myForm.controls[key].setValidators([]);   
      console.log('errors on : ', key,  this.myForm.controls[key].errors);
    //  this.myForm.controls[key].markAsUntouched();     // attempt 3928
    //  this.myForm.controls[key].markAsPristine()      // attempt 3929
      //this.myForm.controls.updateValueAndValidty();   // this throws an error
    }
  }
})

  console.log('new form controls: ', this.myForm.controls); 

}

I've also subscribed to the 'processingType' valueChanges where i ran the same logic as the function above

    this.myForm.get('processingType').valueChanges.subscribe(data => this.onProcessingTypeChanged(data));

Here are some screenshots from my logs of the values i'm trying to add a validator to:

The start (how it should be)...

After clicking 'Automatic'...why are they still null?

After clicking 'Manual'... now they are required? enter image description here

Based on the above suspicion, i suspect

this.myForm.controls.updateValueAndValidty();

or this.myForm.controls[key].updateValueAndValidity();

is the issue, but as I commented within the code, both of those threw 'not a function' errors.

I've googled nearly every combination of conditional validation, angular2 form model, dynamically adding validators, etc... too many links to list here, haha.

Hopefully this is enough for someone to help diagnose the problem.

Thanks in advance.

Sampath
  • 63,341
  • 64
  • 307
  • 441
drlff
  • 256
  • 3
  • 12

1 Answers1

1

UPDATE: 4/3/2017

Thanks to a comment on this post, the correct answer here is to explicitly set the errors on the control back to null.

example:

formCtrls.forEach((key, index) => {
  if (this.myForm.controls && this.myForm.controls[key]) {
    if (data === this.PROCESSING_TYPE.AUTOMATIC) 
       this.myForm.controls[key].setValidators([Validators.required]);
    else {
      this.myForm.controls[key].validator = [];
      this.myForm.controls[key].setErrors(null);   // CORRECT WAY- NOW VALIDATORS ARE GONE AND ERRORS ARE NULL  
    }
  }
})

OLD answer

It seems that I fixed the issue, but am not comfortable with it. Feels like:

1.) I'm missing the obvious right way to do this.

2.) This is a huge hack.

3.) This is a bug (i highly doubt this- I usually assume I'm wrong in cases like this).

Looping through the controls and using setValidators() actually was working, I just didn't realize it until I tried @Sampath's answer above (which worked the same way as setValidators(), but didn't fix the issue).

To fix it, each time the valueChanges observer's callback was fired, I explicityly set each input's value to an empty string.

The code:

  onProcessingTypeChanged(data) {

let formCtrls = ['discountType', 'discountRule', 'discountAmount', 'dateRuleDays', 'dateRuleTimeStart', 'dateRuleTimeEnd'];
let add = [Validators.required];
let empty = [];
let newValue;

if (data === this.PROCESSING_TYPE.AUTOMATIC) newValue = "";
else newValue = " ";  // SET TO AN EMPTY STRING

this.myForm.patchValue({ 
    discountType: newValue,
    discountRule: newValue,
    discountAmount: newValue, 
    product: newValue,
    dateRuleDays: newValue,
    dateRuleTimeStart: newValue,
    dateRuleTimeEnd: newValue,
});


formCtrls.forEach((key, index) => {
  if (this.myForm.controls && this.myForm.controls[key]) {
    if (data === this.PROCESSING_TYPE.AUTOMATIC) 
       this.myForm.controls[key].setValidators([Validators.required]);
    else 
      this.myForm.controls[key].setValidators([]); 
  }
})

}

After a good bit of exploring, it seems the errors (myForm.controls[key].errors) were being processed internally against this returned closure:

function(control) {
   return isEmptyInput(control.value) : { 'required': true } ? null;
}

So, to the best of my knowledge, by not resetting each value to an empty string (which I find odd, b/c an empty string is falsy, but in the closure it passes as not being an empty input), angular registered each input as empty, and set the errors object as { required: true }, therefore registering the form as still invalid even though there was no required validators on each input.

Not going to mark this as correct- would love feedback on this if anyone corrects this logic or has similar issues.

Community
  • 1
  • 1
drlff
  • 256
  • 3
  • 12