10

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>
fariba.j
  • 1,737
  • 7
  • 23
  • 42
  • 1
    You can use custom validation, something shown here: https://medium.com/front-end-hacking/angular-how-to-implement-conditional-custom-validation-1ec14b0feb45 – Rajeev Ranjan Apr 26 '18 at 07:27

5 Answers5

34
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>
Milad
  • 27,506
  • 11
  • 76
  • 85
  • @PardeepJain yes, look at my answer ! – Milad Nov 22 '18 at 04:54
  • 2
    This answer is more elegant and robust than the accepted answer. It handles a more general case without affecting existing validators. – Han K Mar 23 '20 at 14:04
  • @Milad I tried your solution, but didn't know where i'm doing wrong. Can you please check it [stackblitz](https://stackblitz.com/edit/angular-one-of-required-validator-jjb5ap?file=src%2Fapp%2Fapp.component.html) – Teja Aug 21 '20 at 05:00
  • Nice, I wrote mine based on this. Thanks! – Sampgun Jan 22 '21 at 08:28
  • 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() {` – Random May 14 '21 at 14:30
4

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>
Davideas
  • 3,226
  • 2
  • 33
  • 51
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;
  }
}
Pardeep Jain
  • 84,110
  • 37
  • 165
  • 215
  • 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 them – fariba.j Apr 26 '18 at 07:25
  • well, it's working but the emailValidator in the form is not working. I have a regexp for email validation – fariba.j Apr 26 '18 at 08:03
  • might be the case regx you are using is wrong, please check it again – Pardeep Jain Apr 26 '18 at 08:37
  • 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 enabled – fariba.j Apr 26 '18 at 08:43
  • @pardeep-jainI create a function to check my mail format, but is it true? or I should validate data in the forms? – fariba.j Apr 26 '18 at 09:08
  • 2
    Milads answer is more elegant and is a broader solution since it's dynamic – Boban Stojanovski Jan 23 '19 at 09:39
  • 2
    You should also checkout this answer: https://stackoverflow.com/questions/40321033/angular2-formbuilder-validatiors-require-at-least-one-field-in-a-group-to-be-fi/45514884 – Boban Stojanovski Jan 23 '19 at 09:58
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>
Leo
  • 621
  • 6
  • 9
  • this works really well. I have a very complex form that is generated dynamically based on a JSON file – Johansrk Dec 22 '22 at 14:02
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;

}