【问题标题】:Use advanced custom validation across multiple mat-steps在多个 mat-steps 中使用高级自定义验证
【发布时间】:2019-06-12 17:15:18
【问题描述】:

My rather complex Stackblitz

通常,当我在响应式表单中进行复杂验证时,我会为那些相互依赖的控件定义一个 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_answerMaterial_docs 描述的技术

我的解决方案目前正在运行,但我对它不满意:

  1. 在启动时Unique Validation 运行一千次(30-40 次)(hacky)
  2. 在整个步进器中 ANY 输入的 EVERY 变化时,Unique Validation 被触发。 (这是因为我必须将它添加到整个 formGroup)。
  3. 一个简单的任务,因为这 3 个输入字段需要是唯一的,这已经变成了一个样板和复杂的混乱。 (请关注function Unique(arr: string[])

  4. UNIQUE Validator 使正确的步骤无效或STEP 再次有效时,不会调用STEPPER-VALIDATION。 (例如:firstUnique = "a", secondUnique "b", thirdUnique = "a" (再次)

我的表单

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);
      }

    })
}

【问题讨论】:

    标签: angular forms angular-material mat-stepper


    【解决方案1】:

    使用 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.htmlthird-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 的事情

    【讨论】:

    • 您的解决方案将验证添加到很好的外观。但我也希望 Stepper Headers 和 Form Inputs 反映错误以获得完整的用户体验。
    猜你喜欢
    • 2016-08-22
    • 2018-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-03
    • 2014-04-22
    • 2012-11-30
    相关资源
    最近更新 更多