Angular 2 requiring one of two items in a formgroup

I am trying to validate a reactive form that has three fields. Username is always a required field, but I only want the user to have to enter either a phone number or an email in addition to username, and I cannot figure out how to do so.

I've tried nested form groups, and custom validators, I also tried the solution here, but no luck.

Here is my current html form:

<div class="container"><br>
  <form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="username">Username</label>
      <input type="text" id="username" formControlName="username" class="form-control">
      <span *ngIf="!signupForm.get('username').valid && hasBeenSubmitted == true" style="color: red">Please enter a valid username!</span>
    </div>
    <br><hr>
    <div formGroupName="userdata">
      <div class="form-group">
        <label for="email">email</label>
        <input type="text" id="email" formControlName="email" class="form-control">
      </div>
or <br>
      <div>
       <label for="phoneNumber">phone</label>
        <input type="text" formControlName="phoneNumber">
      </div>
    </div>
    <hr>
    <button class="btn btn-primary" type="submit">Submit</button>
        <span *ngIf="!signupForm.get('userdata.email').valid && hasBeenSubmitted == true && hasBeenSubmitted == true && !signupForm.get('userdata.email').valid 
        && hasBeenSubmitted == true" style="color: red">Please enter a valid email or phone number!</span>
  </form>
</div>

And my typescript:

export class AppComponent implements OnInit {
  signupForm: FormGroup;
  hasBeenSubmitted = false;
  ngOnInit() {
    this.signupForm = new FormGroup({
      'username': new FormControl(null, Validators.required),
      'nickname': new FormControl(null, Validators.required),
      // Nested formgroup:
      'userdata': new FormGroup({
        'email': new FormControl(null, [Validators.required, Validators.email]),
        'phoneNumber': new FormControl(null, [Validators.required]),
      }),
    });
  }

  onSubmit() {
    this.hasBeenSubmitted = true; // I use this variable to validate after submission
  }
}

When the user submits a form without an email or phone number, I want the error text to read: Please enter a valid email or phone number! . Which will resolve when the user starts to enter a valid email or phone number.

How can I validate my form so that the user only needs to enter a username and a phone number, or a username and an email, for the form to be valid?

Plunkr for my current code: https://plnkr.co/edit/1IyVQblRX1bZxOXIpyQF?p=preview

Answers:

Answer

I created a custom validator directive:

import {
    FormGroup,
    ValidationErrors,
    ValidatorFn,
    Validators,
  } from '@angular/forms';

  export const atLeastOne = (validator: ValidatorFn, controls:string[] = null) => (
    group: FormGroup,
  ): ValidationErrors | null => {
    if(!controls){
      controls = Object.keys(group.controls)
    }

    const hasAtLeastOne = group && group.controls && controls
      .some(k => !validator(group.controls[k]));

    return hasAtLeastOne ? null : {
      atLeastOne: true,
    };
  };

To use it, you just do this:

this.signupForm = new FormGroup({
    'username': new FormControl(null, Validators.required),
    'nickname': new FormControl(null, Validators.required),
    'userdata': new FormGroup({
        'email': new FormControl(null),
        'phoneNumber': new FormControl(null),
    }, { validator: atLeastOne(Validators.required, ['email','phoneNumber']) })
});

So email or phoneNumber would be required here. If you leave it empty then any control with a value is fine and you can use it with any type of validator, not just Validators.required.

This is reusable in any form.

Answer

As you have said you can use custom validator for group:

this.signupForm = new FormGroup({
  'username': new FormControl(null, Validators.required),
  'nickname': new FormControl(null, Validators.required),
   // Nested formgroup:
   'userdata': new FormGroup({
     'email': new FormControl(null),
     'phoneNumber': new FormControl(null),
    }, this.validateUserData)
});

where validateUserData method looks like:

validateUserData(formGroup) {
  const emailCtrl = formGroup.get('email');
  const phoneCtr = formGroup.get('phoneNumber');

  if (emailCtrl.value && !Validators.email(emailCtrl) || phoneCtr.value) {
    return null;
  }

  return { required: true };
}

and last thing you need to do is change template like:

<span *ngIf="!signupForm.get('userdata').valid && hasBeenSubmitted" ...>
  Please enter a valid email or phone number!
</span>

Forked Plunker

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.