3

I'm working on a form with Angular Forms and I have hit a deadend where I want to add validation to a second input field only if the first is not empty.

Let's say I have two input fields: name and age. So when something is typed into name, only then age will be set to required. I'm using FormGroup and FormControl for my form and this is how the component file looks like now without validators for age:

class Component implements OnChanges {
  @Input() name: string;
  @Input() age: string; 

  form = new FormGroup({
    name: new FormControl(''),
    age: new FormControl('')
  });

  ngOnChanges(changes) {

    if (changes.name?.currentValue !== changes.name?.previousValue) {
      this.setName(changes.name.currentValue);
    }

    if (changes.age?.currentValue !== changes.age?.previousValue) {
      this.setAge(changes.age.currentValue);
    }
  }

  setName(name) {

    this.form.patchValue({
      name,
    });

    if (name) {
      this.form.get('age').setValidators([
         Validators.required,
         this.form.get('age').validator
      ]);
    } 
    
  }

  setAge(age) {
    this.form.patchValue({
      age,
    });
  }

}

Here's the template:

<custom-input
      label="Name"
      placeholder="name"
      name="name"
      formControlName="name"
></custom-input>

<custom-input
      label="Age"
      placeholder="age"
      name="age"
      formControlName="age"
></custom-input>
1
  • add *ngIf condition to your second input field. Commented Aug 25, 2022 at 13:14

2 Answers 2

5

You can use the below method!

listen to change event of the form fields you want,then check if the field is filled, then we can toggle the required with the command setValidators then after its updated we can ensure the form is in sync by running the updateValueAndValidity which will revalidate the form!

ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import {
  FormBuilder,
  FormControl,
  FormGroup,
  Validators,
} from '@angular/forms';
import { Subscription } from 'rxjs';
import { pairwise, startWith } from 'rxjs/operators';

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

  form: FormGroup;
  subscription: Subscription;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.formBuilder.group({
      control1: [''],
      control2: [''],
    });
  }
  control1Change() {
    const control1 = <FormControl>this.form.get('control1');
    const control2 = <FormControl>this.form.get('control2');
    if (control1.value) {
      control2.setValidators([Validators.required]);
    } else {
      control2.setValidators(null);
    }

    control2.updateValueAndValidity();
  }
  control2Change() {
    const control1 = <FormControl>this.form.get('control1');
    const control2 = <FormControl>this.form.get('control2');
    if (control2.value) {
      control1.setValidators([Validators.required]);
    } else {
      control1.setValidators(null);
    }

    control1.updateValueAndValidity();
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

HTML

<hello name="{{ name }}"></hello>
<form [formGroup]="form">
  <div style="margin-bottom: 1rem">
    <label for="control1">Make Field 2 required</label>
    <input
      id="control1"
      type="text"
      formControlName="control1"
      (change)="control1Change()"
    />
    <span
      *ngIf="form.controls.control1.hasError('required')"
      style="color: red; display: block; margin-top: 0.25rem;"
      >Field 2 is required</span
    >
  </div>
  <div style="margin-bottom: 1rem">
    <label for="control2"> Field 2 </label>
    <input
      id="control2"
      type="text"
      formControlName="control2"
      (change)="control2Change()"
    />
    <span
      *ngIf="form.controls.control2.hasError('required')"
      style="color: red; display: block; margin-top: 0.25rem;"
      >Field 2 is required</span
    >
  </div>
  <div>
    <button type="submit" [disabled]="!form.valid">
      Only enabled when form is valid
    </button>
  </div>
</form>

forked stackblitz

Sign up to request clarification or add additional context in comments.

4 Comments

Hey, is there any way of setting validators without using rxjs? I dont want to make it overly complicated.
@Zak If your going to use reactive forms, this is the standard way of coding, sorry but this is best for your requirement!
Okay it worked but I wanted to do the same thing the other way around. So if age is filled first then name will get validators. But right now I'm getting 'Maximum call stack size exceeded'.
@Zak Sorry I work on a framework so value changes works great, I have updated my answer with change event so that it updates either way!
0

I had the same need, here's my solution based on a custom validator.

In this case, if formControlA's value is not null, then formControlB is required.

Here's my custom validator :

/**
 * Validator function that checks if the reference control is not null, then the current control must not be null.
 *
 * @param {string} referenceControlName - The name of the control in the same form group that holds the reference value.
 * @return {ValidatorFn} A validator function
 */
export function RequiredIfControlIsNotNullValidator(referenceControlName: string): ValidatorFn {
  return (currentControl: AbstractControl): { [key: string]: any } => {
    // Get the reference control from the parent formGroup
    const referenceControl = currentControl.parent ? currentControl.parent.get(referenceControlName) : null;
    
    // Throw an error if the reference control is null or undefined
    if (referenceControl == null)
      throw Error("Reference formControl is null or undefined");
    
    // Check if the reference control value is null or undefined
    const refValueIsNullOrUndefined = referenceControl.value == null || referenceControl.value == undefined;
    // Check if the current control value is null or undefined
    const currentControlValueIsNullOrUndefined = currentControl.value == null || currentControl.value == undefined;
    
     // Return the validation result. 
     // Return a validation error if the reference control is not null or undefined and the current control is null
    return refValueIsNullOrUndefined == false && currentControlValueIsNullOrUndefined === true ?  { [`${referenceControlName}IsNotNull`]: true } : null ;
  };
}

In your component, assign it to your formControl this way :

this.myForm = new FormGroup({
  formControlA: new FormControl<string | null>({ value: null, disabled: false }, [Validators.nullValidator]),
  formControlB: new FormControl<string | null>({ value: null, disabled: false }, [RequiredIfControlIsNotNullValidator('formControlA')])
});

However, we need to run the validation of formControlB every time formControlA's value is updated. So in your component's ngOnInit react to the valueChanges of formControlA

public ngOnInit(): void {
// [...]
  this.myForm.get("formControlA").valueChanges.subscribe(vc => {
    this.visitForm.get('formControlB').updateValueAndValidity();
  });
// [...]
}

Then, the validator generates a custom error key, so you can use it to show an appropriate error message in the template

<form [formGroup]="myForm">
  <div>
    <input formControlName="formControlA" />
  </div>
  <div>
    <input formControlName="formControlB" />
    <span class="error" *ngIf="myForm.get('formControlB').hasError('formControlAIsNotNull')">
      The value of A is not null, hence B is required
    </span>
  </div>
</form>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.