Angular Reactive Forms:是否可以创建包含“必须”验证的自定义表单控件组件?

问题描述

我已经为 tree 个选择创建了我的自定义表单控件,我已经对其进行了测试并且值设置正确。有一个问题,只有当所有选择都被选中时,我才想将此控件设置为 VALID。所以我也实现了 Validators 但肯定有问题,因为控件每次都设置为 valid。它正在触发我的 validate() 方法并返回预期值。有人可以看看我的代码并给我一些如何实现的提示吗?

代码

  selector: 'app-cascading-picklist',templateUrl: './cascading-picklist.component.html',styleUrls: ['./cascading-picklist.component.scss'],providers: [{
    provide: NG_VALUE_ACCESSOR,useExisting: forwardRef(() => CascadingPicklistComponent),multi: true
  },{
    provide: NG_VALIDATORS,multi: true
  }]
})
export class CascadingPicklistComponent implements ControlValueAccessor,Validators,OnInit {
  @input() parentList: Formlist;
  expanded = false;
  flatLists: Formlist[];
  selectedValues: string[] = [];
  hierarchicalForm = new FormGroup({});
  public value = '';
  onChange: (value) => void = () => {
  };
  onTouched: () => void = () => {
  };
  onValidationChange: any = () => {
  };

  constructor() {
  }

  ngOnInit() {
    this.flatLists = this.getFlatPickLists();
    this.generateForm();
    this.selectedValues.push(this.flatLists[0].ID);
  }

  getFlatPickLists() {
    let flatList: Formlist[] = [];
    flatList = [...this.getAllLists(this.parentList)];
    return flatList;
  }

  getAllLists(formlist: Formlist) {
    let lists: Formlist[] = [formlist];
    if (formlist.ChildPickLists) {
      for (const list of formlist.ChildPickLists) {
        if (list) {
          lists = [...lists,...this.getAllLists(list)];
        }
      }
    }
    return lists;
  }

  generateForm() {
    this.flatLists.forEach(list => {
      this.hierarchicalForm.addControl(list.ID,new FormControl());
    });
  }

  onValueChange() {
    const rawValue = Object.values(this.hierarchicalForm.getRawValue()) as PickListItem[];
    const nullIndex = rawValue.findindex(element => !!!element);
    this.selectedValues = [this.flatLists[0].ID,...rawValue.splice(0,nullIndex).map(el => el.Value)];
    this.resetHiddenControls();
    this.writeValue(this.getHierarchicalPath());
  }

  resetHiddenControls() {
    Object.keys(this.hierarchicalForm.controls).forEach(control => {
      if (!this.selectedValues.includes(control)) {
        if (this.hierarchicalForm.controls[control].value) {
          this.hierarchicalForm.controls[control].setValue(null,{emitEvent: false});
        }
      }
    });
  }

  getHierarchicalPath() {
    let path = '';
    this.selectedValues.forEach(value => {
      const selectedValue = this.hierarchicalForm.controls[value] ? this.hierarchicalForm.controls[value].value : null;
      if (selectedValue) {
        path = `${path}${selectedValue.Label}/`;
      }
    });
    return path.slice(0,-1);
  }

  public registerOnChange(fn: (value) => void): void {
    this.onChange = fn;
  }

  public registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  public writeValue(newValue): void {
    this.value = newValue;
    this.onChange(newValue);
    this.onValidationChange();
  }

  registerOnValidatorChange?(fn: () => void): void {
    this.onValidationChange = fn;
  }

  validate(control: AbstractControl): ValidationErrors {
    this.selectedValues.forEach(controlName => {
      if (this.hierarchicalForm.controls[controlName] && !this.hierarchicalForm.controls[controlName].value) {
        return {cascadingPicklist: true};
      }
    });
    return null;
  }
}

模板:

       (click)="expanded = !expanded" readonly>
<ng-container *ngIf="expanded">
  <form [formGroup]="hierarchicalForm">
    <ng-select *ngFor="let list of flatLists"
               [items]="list.PickListItems"
               [placeholder]="list.Placeholder"
               bindLabel="Label"
               appendTo="body"
               dropdownPosition="bottom"
               [class.d-none]="!(list.ID | isSelectVisible : selectedValues)"
               (ngModelChange)="onValueChange()"
               [formControlName]="list.ID">
    </ng-select>
  </form>
</ng-container>

解决方法

您的 validate() 错误是在 return 函数内使用 forEach。 创建一个局部变量并仅在您的选择为空时为其分配值:

  validate(control?: AbstractControl): ValidationErrors {
    let result = null;
    this.selectedValues.forEach(controlName => {
      if (
        this.hierarchicalForm[controlName] &&
        !this.hierarchicalForm[controlName].value
      ) {
        result = { cascadingPicklist: true };
      }
    });
    return result;
  }

Stackblitz 示例: https://stackblitz.com/edit/angular-custom-validators-with-select?file=src%2Fapp%2Fapp.component.ts

更聪明的解决方案可能是使用 every 函数:

return this.selectedValues.every(controlName => 
      !!(this.hierarchicalForm[controlName] &&
        this.hierarchicalForm[controlName].value
      )) ? null : { cascadingPicklist: true };

Stackblitz 示例: https://stackblitz.com/edit/angular-custom-validators-with-select-w2rvwb?file=src%2Fapp%2Fapp.component.ts