Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Require one from two fields using Angular 2

I'm trying to create a contact form. The form looks like this:

<form novalidate [formGroup]="contact" (ngSubmit)="send()">
  <p>
    <label>Name
      <br>
      <input type="text" class="input" value="" formControlName="name">
      <span class="error">Enter your name</span>
    </label>
  </p>
  <p>
    <label>E-mail
      <br>
      <input type="email" class="input" value="" formControlName="email">
      <span class="error">It looks like this email is invalid</span>
    </label>
  </p>
  <p>
    <label>Phone
      <br>
      <input type="text" class="input" value="" formControlName="telefone">
      <span class="error">It looks like this phone number is invalid</span>
    </label>
  </p>
  <p>
    <label>Message
      <br>
      <textarea type="text" class="input" value="" formControlName="message"></textarea>
      <span class="error">The message can't be empty</span>
    </label>
  </p>
  <p class="submit">
    <input type="submit" name="submit" class="bt" value="Send">
  </p>
</form>

In this form only the message and the name and email or the phone number fields should be required.

I'm using a formBuilder class, so here's the TypeScript code:

this.contact = this.formBuilder.group({
  name: ['', Validators.required],
  email: ['', Validators.compose([/*Use custom validador??*/])],
  phone: ['', Validators.compose([/*Use custom validador??*/]],
  message: ['', Validators.required]
});

I tried using custom validators as a solution, but I could not figure out a solution. Any suggestions?

like image 887
Filipe Costa Avatar asked Dec 07 '16 14:12

Filipe Costa


3 Answers

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.form = this.formBuilder.group({
            name: ['', Validators.required],
            email:[''],
            telefone:[''],
            message:['', Validators.required],
        }, { validator: atLeastOne(Validators.required, ['email','telefone']) });

So email or telefone 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.

like image 137
Todd Skelton Avatar answered Oct 16 '22 20:10

Todd Skelton


Yes, a custom validator is the way to go.

Make your form group like this:

this.contact = this.formBuilder.group({
  name: ['', Validators.required],
  email: ['', Validators.required],
  phone: ['', Validators.required],
  message: ['', Validators.required]
}, {validator: this.customValidationFunction})

Then have the customValidationFunction check for validation. Made up validation just for example:

customValidationFunction(formGroup): any {
   let nameField = formGroup.controls['name'].value; //access any of your form fields like this
   return (nameField.length < 5) ? { nameLengthFive: true } : null;
}

Change each input like this (changing your p tags to divs. Substitute the control name for each and change syntax for the hidden span tag validation where appropriate):

<div [ngClass]="{'has-error':!contact.controls['name'].valid && contact.controls['name'].touched}">
    <label>Name</label>
    <input class="input" type="text" [formControl]="contact.controls['name']">
    <span [hidden]="!contact.hasError('nameLengthFive')" class="error">Enter your name</span>
</div>
like image 14
Drew13 Avatar answered Oct 16 '22 20:10

Drew13


I have updated my validator snippet to support string and number types

I was inspired by Todd Skelton. This is a very simple Validator that does just what you ask for and nothing else:

/**
 * Validates if at least one of the provided fields has a value.
 * Fields can only be of type number or string.
 * @param fields name of the form fields that should be checked
 */
export function atLeastOne(...fields: string[]) {
  return (fg: FormGroup): ValidationErrors | null => {
    return fields.some(fieldName => {
      const field = fg.get(fieldName).value;
      if (typeof field === 'number') return field && field >= 0 ? true : false;
      if (typeof field === 'string') return field && field.length > 0 ? true : false;
    })
      ? null
      : ({ atLeastOne: 'At least one field has to be provided.' } as ValidationErrors);
  };
}

And here is how I use it:

  ngOnInit(): void {
    this.form = this.formBuilder.group(
      {
        field: [this.field],
        anotherField: [this.anotherField],
      },
      { validator: atLeastOne('field','anotherField') },
    );
  }
like image 11
Florian Leitgeb Avatar answered Oct 16 '22 20:10

Florian Leitgeb