Используйте расширенную пользовательскую проверку в несколько шагов - PullRequest
1 голос
/ 12 июня 2019

Мой довольно сложный Stackblitz

Обычно, когда у меня сложная проверка в реактивных формах, я определяю formGroup для тех элементов управления, которые зависят друг от друга.

Это невозможно в вышеприведенном сенарио, где у нас есть 3 steps = 3 groups и три зависимых поля 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

Мое решение работает, но я не удовлетворен им:

  1. При запуске Unique Validation запускается тысячу раз (30-40 раз) (хакер)
  2. Вкл. КАЖДЫЙ изменение ЛЮБОГО входа во всем шаговом двигателе - Unique Validation - триггер. (это потому, что мне пришлось добавить его во всю группу Form).
  3. Простая задача, поскольку эти три поля ввода должны быть УНИКАЛЬНЫМИ, превратилась в сложный и сложный беспорядок. (соблюдайте function Unique(arr: string[]))

  4. Когда правильный шаг становится недействительным с помощью 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'])]
});

Уникальное удовольствие от Validator

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

    })
}

1 Ответ

2 голосов
/ 12 июня 2019

Используя библиотеку ngx-sub-form , вот живая демонстрация Stackblitz:

https://stackblitz.com/edit/ngx-sub-form-stepper-form-demo

Чтобы объяснить немного, это будет выглядеть следующим образом:

Во-первых, нам нужно определить некоторые интерфейсы, чтобы наш код был устойчивым и безопасным для типов

шагового 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>

Теперь мы начинаем использовать библиотеку, создаем форму верхнего уровня (root) и выставляем результат в качестве вывода. Мы также определяем ограничение, что 3 уникальных входа не должны иметь одинаковые значения.

шагового 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

шагового 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>

Теперь давайте создадим наш первый субкомпонент

первый-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(),
    }
  }
}

и его шаблон

первый-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

Edit:

Если вы хотите пойти дальше, я только что опубликовал пост в блоге, который объясняет много вещей о формах и ngx-sub-форме здесь https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...