通常,当我对反应形式进行复杂的验证时,我会为相互依赖的那些控件定义一个formGroup。
在上述解决方案中这是不可能的,因为我们有3 steps = 3 groups
和相关的3个字段firstUnique, secondUnique, thirdUnique
。
<form [formGroup]="myForm">
<mat-horizontal-stepper formArrayName="formArray" #stepper>
<mat-step formGroupName="0" [stepControl]="formArray?.get([0])" errorMessage="Name is required.">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
<input matInput placeholder="Last name, First name" formControlName="firstCtrl" required>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="UNIQUE1" formControlName="firstUnique" required>
</mat-form-field>
<div>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step formGroupName="1" [stepControl]="formArray?.get([1])" errorMessage="Address is required.">
<ng-template matStepLabel>Fill out your address</ng-template>
<mat-form-field>
<input matInput placeholder="Address" formControlName="secondCtrl" required>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="UNIQUE2" formControlName="secondUnique" required>
</mat-form-field>
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button matStepperNext>Next</button>
</div>
</mat-step>
<mat-step formGroupName="2" [stepControl]="formArray?.get([2])" errorMessage="Error!">
<ng-template matStepLabel>Done</ng-template>
You are now done.
<div>
<mat-form-field>
<input matInput placeholder="UNIQUE3" formControlName="thirdUnique" required>
</mat-form-field>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-horizontal-stepper>
我使用SO_answer和Material_docs中描述的技术
到目前为止,我的解决方案正在运行,但对此不满意:
Unique Validation
时运行一千次(30-40次)(骇客)Unique Validation
的整个步进中,在任何输入中 ANY 的更改都会触发。 (这是因为我必须将其添加到整个formGroup中)。
一个简单的任务,因为这3个输入字段必须是UNIQUE,已经变得很简单,而且很复杂。 (请注意function Unique(arr: string[])
)
当正确的步骤被UNIQUE Validator
无效或STEP再次有效时,将不调用STEPPER-VALIDATION。
(例如:firstUnique =“ a”,secondUnique“ b”,thirdUnique =“ a”(再次))
MyForm
this.myForm = this._formBuilder.group({
formArray:
this._formBuilder.array([
this._formBuilder.group({
firstCtrl: [''],
firstUnique: [''],
}),
this._formBuilder.group({
secondCtrl: [''],
secondUnique: [''],
}),
this._formBuilder.group({
thirdUnique: [''],
})
])
}, {
validator: [Unique(['0;firstUnique', '1;secondUnique', '2;thirdUnique'])]
});
独特的验证器乐趣
function Unique(arr: string[]) {
const validKey = "uniqueValid";
return (formGroup: FormGroup) => {
const myValues =
arr.map(path => {
const s = path.split(';');
return (<FormArray>formGroup.get('formArray'))
.controls[parseInt(s[0])]
.controls[s[1]];
});
const myKeys = arr.map(path => path.split(';')[1] )
const obj = {};
myKeys.forEach(function (k, i) {
obj[k] = myValues[i];
})
myKeys.forEach((item, index) => {
debugger
console.log('unique validation function runs')
const control = obj[item];
const tmp = myKeys.slice();
tmp.splice(index,1);
const ans = tmp
.filter( el => obj[item].value === obj[el].value)
if ( ans.length && control.value ) {
const err = {}
err[validKey] = `identicial to: ${ans.join(', ')}`
control.setErrors(err);
} else if ( obj[item].errors && !obj[item].errors[validKey] ) {
return;
} else {
control.setErrors(null);
}
})
}
答案 0 :(得分:2)
使用ngx-sub-form库,这是Stackblitz上的实时演示:
https://stackblitz.com/edit/ngx-sub-form-stepper-form-demo
有点解释,它看起来像以下内容:
首先,我们需要定义一些接口,以使我们的代码更健壮且类型安全
stepper-form.interface.ts
export interface Part1 {
firstCtrl: string;
firstUnique: string;
}
export interface Part2 {
secondCtrl: string;
secondUnique: string;
}
export interface Part3 {
thirdUnique: string;
}
export interface StepperForm {
part1: Part1;
part2: Part2;
part3: Part3;
}
在顶层组件中,我们甚至不想知道有一个表单。 我们只想在保存新值时得到警告。
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public stepperFormUpdated(stepperForm: StepperForm): void {
console.log(stepperForm);
}
}
app.component.html
<app-stepper-form (stepperFormUpdated)="stepperFormUpdated($event)"></app-stepper-form>
现在,我们开始使用该库并创建顶层表单(根),并将结果显示为输出。我们还定义了3个唯一输入不应具有相同值的约束。
stepper-form.component.ts
@Component({
selector: 'app-stepper-form',
templateUrl: './stepper-form.component.html',
styleUrls: ['./stepper-form.component.css']
})
export class StepperFormComponent extends NgxRootFormComponent<StepperForm> {
@DataInput()
@Input('stepperForm')
public dataInput: StepperForm | null | undefined;
@Output('stepperFormUpdated')
public dataOutput: EventEmitter<StepperForm> = new EventEmitter();
public send() {
this.manualSave();
}
protected getFormControls(): Controls<StepperForm> {
return {
part1: new FormControl(),
part2: new FormControl(),
part3: new FormControl(),
}
}
public getFormGroupControlOptions(): FormGroupOptions<StepperForm> {
return {
validators: [
formGroup => {
if (!formGroup || !formGroup.value || !formGroup.value.part1 || !formGroup.value.part2 || !formGroup.value.part3) {
return null;
}
const values: string[] = [
formGroup.value.part1.firstUnique,
formGroup.value.part2.secondUnique,
formGroup.value.part3.thirdUnique,
].reduce((acc, curr) => !!curr ? [...acc, curr] : acc, []);
const valuesSet: Set<string> = new Set(values);
if (values.length !== valuesSet.size) {
return {
sameValues: true
};
}
return null;
},
],
};
}
}
是时候使用lib提供的实用程序来创建模板了
stepper-form.component.html
<form [formGroup]="formGroup">
<mat-horizontal-stepper>
<mat-step>
<ng-template matStepLabel>First control</ng-template>
<app-first-part [formControlName]="formControlNames.part1"></app-first-part>
<button mat-button matStepperNext>Next</button>
</mat-step>
<mat-step>
<ng-template matStepLabel>Second control</ng-template>
<app-second-part [formControlName]="formControlNames.part2"></app-second-part>
<button mat-button matStepperNext>Next</button>
</mat-step>
<mat-step>
<ng-template matStepLabel>Third control</ng-template>
<app-third-part [formControlName]="formControlNames.part3"></app-third-part>
<button mat-button (click)="send()">Send the form</button>
</mat-step>
</mat-horizontal-stepper>
</form>
<div *ngIf="formGroupErrors?.formGroup?.sameValues">
Same values, please provide different ones
</div>
现在,让我们创建第一个子组件
first-part.component.ts
@Component({
selector: 'app-first-part',
templateUrl: './first-part.component.html',
styleUrls: ['./first-part.component.css'],
providers: subformComponentProviders(FirstPartComponent)
})
export class FirstPartComponent extends NgxSubFormComponent<Part1> {
protected getFormControls(): Controls<Part1> {
return {
firstCtrl: new FormControl(),
firstUnique: new FormControl(),
}
}
}
及其模板
first-part.component.html
<div [formGroup]="formGroup">
<mat-form-field>
<input matInput placeholder="First" type="text" [formControlName]="formControlNames.firstCtrl">
</mat-form-field>
<mat-form-field>
<input matInput type="text" placeholder="First unique" [formControlName]="formControlNames.firstUnique">
</mat-form-field>
</div>
然后second-part.component.html
和third-part.component.html
几乎相同,因此我在这里跳过。
在这种情况下,我假设您确实不需要FormArray
,并且不确定您所拥有的整个验证代码,所以我只建立了一个如果至少有2个唯一值是错误的代码,一样。
https://stackblitz.com/edit/ngx-sub-form-stepper-form-demo
编辑:
如果您想走得更远,我刚刚在https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9
上发布了一篇博客文章,以解释有关表单和ngx-sub-form的许多内容。