Angular 2: Add validators to ngModelGroup

2020-03-24 08:27发布

问题:

I am using a ngModelGroup directive to group several form inputs together.

In the docs (https://angular.io/docs/ts/latest/api/forms/index/NgModelGroup-directive.html) I read that there is a validators: any[] property.

Does this mean I can add a custom validator function which validates only that ngModelGroup ? If so, how do I use it?

That would be awesome because I would like to check if at least one of the checkboxes in the ngModelGroup is checked. I can't use required because that would mean all checkboxes are required. I can't find anything about this in the documentation or am I looking in the wrong place?

回答1:

This is entirely possible with a ngModelGroup and a custom directive for validation. Key to understanding why this works is that ngModelGroup

Creates and binds a FormGroup instance to a DOM element.

First, we'll build out our directive which is pretty boilerplate with nothing special going on:

@Directive({
  selector: '[hasRequiredCheckboxInGroup]',
  providers: [{provide: NG_VALIDATORS, useExisting: HasRequiredCheckBoxInGroup, multi: true}]
})
export class HasRequiredCheckBoxInGroup implements Validator, OnChanges {
  private valFn = Validators.nullValidator;

  constructor() {
    this.valFn = validateRequiredCheckboxInGroup();
  }

  validate(control: AbstractControl): {[key: string]: any} {
    return this.valFn(control);
  }
}

Our validation function is where we take our key knowledge that ngModelGroup creates a FormGroup and apply it:

function validateRequiredCheckboxInGroup() : ValidatorFn {
      return (group) => { //take the group we declare in the template as a parameter
        let isValid = false; //default to invalid for this case
        if(group) {
          for(let ctrl in group.controls) {
            if(group.controls[ctrl].value && typeof group.controls[ctrl].value === 'boolean') { // including a radio button set might blow this up, but hey, let's be careful with the directives
              isValid = group.controls[ctrl].value;
            }
          }
        }

        if(isValid) {
          return null;
        } else {
          return { checkboxRequired: true };
        }
      }
    }

And finally, having included and declared in our Module the directive, we return to the template (needs to be in a form):

<form #f="ngForm">
      <div ngModelGroup="checkboxes" #chks="ngModelGroup" hasRequiredCheckboxInGroup>
          <input type="checkbox" name="chk1" [(ngModel)]="checks['1']"/>
          <input type="checkbox" name="chk2" [(ngModel)]="checks['2']"/>
      </div>
      <div>
      {{chks.valid}}
      </div>
</form>

And here's a plunker with all this available to play with: http://plnkr.co/edit/AXWGn5XwRo60fkqGBU3V?p=preview



回答2:

Thanks all for helping me out! I've accepted Silentsod's answer because it was most helpful.

My final solution was simply building the form using FormBuilder.

In my component, create the form and add the validator function to it:

ngOnInit(): void {
    // Validator function:
    let validateMemberList = (group: FormGroup) => {
        let checked = Object.keys(group.controls).reduce((count, key: string) => {
            return count + (group.controls[key].value ? 1 : 0);
        }, 0);

        return checked === 0 ? {'minchecked': 1} : null;
    };

    this.jobForm = this.formBuilder.group({
        jobTitle: ['', Validators.required],
        // more inputs...
        members: this.formBuilder.group({}, {
            // The 2nd argument is an object with a validator property:
            validator: validateMemberList
        }),
        supportMembers: this.formBuilder.group({}, {
            validator: validateMemberList
        })
    });

Then in my template:

<form [formGroup]="jobForm">
    <ul formGroupName="members">
        <li *ngFor="let user of teamMembers.members">
            <label class="e-label">
                <input class="e-input" type="checkbox" [formControlName]="user.$key">
                <user-badge [user]="user"></user-badge>
            </label>
        </li>
    </ul>
...

This validates the form allowing me to display errors and disable the submit button.

Thanks all for the help!



回答3:

Looks like each control in your ModelGroup should have is own validator. once this is done you might have access to each one through the validators array. so add the required attribute to the inputs that are required