Angular 响应式表单:是否可以创建包含 'must have' 验证的自定义表单控件组件?
Angular Reactive Forms: is it possible to create custom form control component with 'must have' validation included?
我已经为 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,
useExisting: forwardRef(() => CascadingPicklistComponent),
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() 错误是在 forEach
函数中使用 return
。
创建一个局部变量并仅在您有一个空 select:
时才为其赋值
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
我已经为 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,
useExisting: forwardRef(() => CascadingPicklistComponent),
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() 错误是在 forEach
函数中使用 return
。
创建一个局部变量并仅在您有一个空 select:
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