Edon
Edon

Reputation: 1216

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

Upvotes: 2

Views: 2011

Answers (2)

Todd Skelton
Todd Skelton

Reputation: 7239

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.

Upvotes: 3

yurzui
yurzui

Reputation: 214095

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

Upvotes: 1

Related Questions