at least one field is required in angular 4 forms
Asked Answered
M

5

11

I'm using angular 4 forms and I have some fields. and first_name, last_name and company are really important for me. I want to force the user to fill one of these 3 fields. how can I do it?

here are .ts codes:

this.contactForm = this.fb.group({
        first_name: [null, Validators.compose([Validators.required])],
        last_name: [null, Validators.compose([Validators.required])],
        email: [null, Validators.compose([this.validateEmail])],
        company: [null],
        position: [null],
      });

an html:

 <form [formGroup]="contactForm" fxLayout="column">
            <md-input-container class="data_light">
              <input class="data_font capital" mdInput placeholder="{{'Contacts.FirstName' | translate}}" [formControl]="contactForm.controls['first_name']">
            </md-input-container>
            <small *ngIf="contactForm.controls['first_name'].hasError('required') && contactForm.controls['first_name'].touched" class="mat-text-warn data_light">{{'Contacts.firstNameReq' | translate}}
            </small>
            <md-input-container class="data_light">
              <input class="data_font capital" mdInput placeholder="{{'Contacts.lastName' | translate}}" [formControl]="contactForm.controls['last_name']">
            </md-input-container>
            <small *ngIf="contactForm.controls['last_name'].hasError('required') && contactForm.controls['last_name'].touched" class="mat-text-warn data_light">{{'Contacts.lastNameReq' | translate}}
            </small>
            <md-input-container class="data_light">
              <input class="data_font" mdInput placeholder="{{'Contacts.email' | translate}}" [formControl]="contactForm.controls['email']"
                (blur)="checkContactEmail()">
            </md-input-container>
            <small *ngIf="contactForm.controls['email'].hasError('validateEmail') && contactForm.controls['email'].dirty" class="mat-text-warn data_light">{{'Contacts.emailValid' | translate}}
            </small>
            <small *ngIf="!emailValid" class="mat-text-warn data_light">{{emailMessage}}
            </small>
            <md-input-container class="data_light">
              <input class="data_font capital" mdInput placeholder="{{'Contacts.Company' | translate}}" [formControl]="contactForm.controls['company']">
            </md-input-container>
            <md-input-container class="data_light">
              <input class="data_font capital" mdInput placeholder="{{'Contacts.Position' | translate}}" [formControl]="contactForm.controls['position']">
            </md-input-container>
          </form>
Manchineel answered 26/4, 2018 at 7:15 Comment(1)
You can use custom validation, something shown here: medium.com/front-end-hacking/…Piteous
W
3

Disable your button until required fields are not fileld by user

<button type='submit' [disabled]='!contactForm.valid'> Submit</button>

Then call function to check disable like this

<button type='submit' [disabled]='checkValid()'> Submit</button>
checkValid() {
  if(this.contactForm.get('first_name').valid || this.contactForm.get('last_name').valid || this.contactForm.get('email').valid) {
    return false;
  } else {
    return true;
  }
}
Waechter answered 26/4, 2018 at 7:20 Comment(7)
I know it but I set required validators for three of them, the user should fill them. but a user should be able to activate the form by filling only one of themManchineel
well, it's working but the emailValidator in the form is not working. I have a regexp for email validationManchineel
might be the case regx you are using is wrong, please check it againWaechter
no it was working true, but now I delete !contactForm.valid from HTML and it doesn't check the email anymore and if I add !contactForm.valid, as you see in my codes, the first_naem and last_name are both required. and I don't set the field as required, the button is enabledManchineel
@pardeep-jainI create a function to check my mail format, but is it true? or I should validate data in the forms?Manchineel
Milads answer is more elegant and is a broader solution since it's dynamicCrosslet
You should also checkout this answer: #40321533Crosslet
K
35
private atLeastOneValidator = () => {
    return (controlGroup) => {
        let controls = controlGroup.controls;
        if ( controls ) {
            let theOne = Object.keys(controls).find(key=> controls[key].value!=='');
            if ( !theOne ) {
                return {
                    atLeastOneRequired : {
                        text : 'At least one should be selected'
                    }
                }
            }
        }
        return null;
    };
};

Above is a reusable validator and you can use it like this :

 this.contactForm.setValidators(this.atLeastOneValidator())

This makes the whole contactForm invalid if none of the fields have a value.

Note that the contactForm's default validation will still work nicely and you don't have to care about the number of fields in your form and everything is dynamically handled

EDIT :

Note that the atLeastOneValidator is checking for the values to not to be empty , but if you wanted to say :

At least one of them fields must be valid, then you can simply adjust the conditions to below

 let theOne = Object.keys(controls).find(key=> controls[key].valid );

You can then use the error in your template like this :

 <small *ngIf="contactForm.hasError('atLeastOneRequired')" class="mat-text-warn data_light">{{contactForm.getError('atLeastOneRequired')}}
        </small>
Kassi answered 26/4, 2018 at 7:28 Comment(5)
@PardeepJain yes, look at my answer !Kassi
This answer is more elegant and robust than the accepted answer. It handles a more general case without affecting existing validators.Pickings
@Kassi I tried your solution, but didn't know where i'm doing wrong. Can you please check it stackblitzTlingit
Nice, I wrote mine based on this. Thanks!Mcauley
Note you may prefere use some instead of find, which will return a boolean instead of the object (which you don't use). You also don't need to use an arrow function, since you don't use any this, so you could simply write private atLeastOneValidator() {Shadowgraph
V
6

I provide a better customized way for some fields only, and not for all fields of the form. Also,

  • Not like this <button [disabled]="checkValid()">,

  • But to use directly <button [disabled]="form.invalid || form.pristine || loading">

Fields can be provided via Array param as following:

export const atLeastOneHasValue = (fields: Array<string>) => {
    return (group: FormGroup) => {
        for (const fieldName of fields) {
            if (group.get(fieldName).value) {
                return null;
            }
        }
        return { paymentMethodRequired: true };
    };
};

// Form group
form = this.formBuilder.group({
   field1: [],
   field2: [],
   field3: [],
   field4: []
}, {
    validators: atLeastOneHasValue(['field2', 'field3'])
});

You then, can check and show error as following in the template, saving button will be disabled thanks to form.invalid boolean:

<mat-error *ngIf="form.hasError('paymentMethodRequired')">
    {{"ERROR_PAYMENT_METHOD_REQUIRED" | translate}}
</mat-error>
Villa answered 22/8, 2021 at 21:46 Comment(1)
It works on angular 10.Marzi
W
3

Disable your button until required fields are not fileld by user

<button type='submit' [disabled]='!contactForm.valid'> Submit</button>

Then call function to check disable like this

<button type='submit' [disabled]='checkValid()'> Submit</button>
checkValid() {
  if(this.contactForm.get('first_name').valid || this.contactForm.get('last_name').valid || this.contactForm.get('email').valid) {
    return false;
  } else {
    return true;
  }
}
Waechter answered 26/4, 2018 at 7:20 Comment(7)
I know it but I set required validators for three of them, the user should fill them. but a user should be able to activate the form by filling only one of themManchineel
well, it's working but the emailValidator in the form is not working. I have a regexp for email validationManchineel
might be the case regx you are using is wrong, please check it againWaechter
no it was working true, but now I delete !contactForm.valid from HTML and it doesn't check the email anymore and if I add !contactForm.valid, as you see in my codes, the first_naem and last_name are both required. and I don't set the field as required, the button is enabledManchineel
@pardeep-jainI create a function to check my mail format, but is it true? or I should validate data in the forms?Manchineel
Milads answer is more elegant and is a broader solution since it's dynamicCrosslet
You should also checkout this answer: #40321533Crosslet
M
1

You might use the following validator


  AtLeastOneInputHasValue = () => {
    return (group: FormGroup) => {
      if (!Object.values(group.value).find(value => value !== '')) {
        return { message: 'Please input at least one value' }
      }
      return null
    }
  }

And add it to you form group validators

this.contactForm = this.fb.group({
      ...
      }, 
      this.AtLeastOneInputHasValue()
    );

and on your template use disabled on the button if any validator is invalid on the form

<button type='submit' [disabled]='!contactForm.valid'> Submit</button>
Maieutic answered 29/1, 2020 at 2:54 Comment(1)
this works really well. I have a very complex form that is generated dynamically based on a JSON fileSubstitutive
D
0

Absolutly works for any from, for any length, for any type AND short and clean code Disable your button until required fields are not fileld by user

<button type='submit' [disabled]='!contactForm.valid'> Submit</button>

Then call function to check disable like this

<button type='submit' [disabled]='checkValidAtleatOne(contactForm)'> Submit</button>
checkValidAtleatOne(form: FormGroup): boolean {
for (const item in form.controls) {
  if (form.get(item)?.valid) {
    return true
  }
}
return false;

}

Discrete answered 9/8, 2023 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.