Пользовательский валидатор угловых реактивных форм с асинхронным вызовом RxJS - PullRequest
1 голос
/ 23 мая 2019

См. Обновления ниже

Я понимаю, что моя проблема в том, что я очень зеленый в наблюдаемых и RxJS.

У меня есть пользовательскийвалидатор, подобный этому:

export function ValidateFinalQty(service: MyService) {
    return (control: AbstractControl): { [key: string]: any } | null => {
        let qty = service.GetQty();
        if (control.value != qty) {
            return { FinalQuantityNotMatching: true };
        } else {
            return null;
        }
    };
}

GetQty возвращает RxJS Observable.Так как я могу настроить это так, чтобы мой синхронный валидатор возвращал правильное значение в зависимости от асинхронного вызова?Мне нужно, чтобы тип возвращаемого значения валидатора оставался как { [key: string]: any } | null.

Я увидел предложение, похожее на qty = await service.GetQty().first().toPromise();, но затем я возвращаю обещание и не могу вернуть обещание для валидатораработать, как я понимаю.

Как мне справиться с этим?

Из моего package.json:

"@angular/core": "7.1.0",
"@angular/forms": "7.1.0",
"rxjs": "6.3.3",
"rxjs-compat": "^6.4.0",

ОБНОВЛЕНИЕ 23.05.199 Попытка реализовать ответ @ Sachin.Мои контрольные точки внутри карты никогда не будут поражены.Я не получаю никаких журналов консоли, и даже если я удаляю логику на карте и возвращаю ноль, он все равно всегда возвращает недействительным.Очень смущен тем, что здесь происходит.На самом деле мне звонят, я это подтвердил.

Есть мысли?

export class CustomAsyncValidator {
    static ValidateFinalQty(qtyService: FinalQtyService, brf: BatchRecordForm): AsyncValidatorFn {
        return (control: AbstractControl) => {
            return qtyService.postCalcFinalQuanity(brf)
                .pipe(
                    map((qty) => {
                        console.log("running qty validator. value:", qty);
                        if (control.value !== qty) {
                            return { FinalQuantityNotMatching: true };
                        } else {
                            return null;
                        }
                    }),
                    catchError((err) => {
                        console.log("Error in final quantity validator", err);
                        return null;
                    }),
                    finalize(() => console.log("finished"))
                );
        };
    }
}

ОБНОВЛЕНИЕ 6/7/2019

При подписке я получаю правильный ответ (null или {FinalQuantityNotMatching: true}), но мой элемент управления формы остается недействительным.Что я делаю не так?

Validator.ts

export class CustomAsyncValidator {
    static ValidateFinalQty(fqs: FinalQtyService, brf: BatchRecordForm) {
        return (control: AbstractControl) => {
            return fqs.postCalcFinalQuanity(brf).pipe(
                debounceTime(500),
                tap((action) => console.log("final qty", action)),
                tap((action) => console.log("control.value", control.value)),
                map(arr => (arr.Value !== `${control.value}`) ? { FinalQuantityNotMatching: true } : null)
            ).subscribe(x => console.log("subscribe output", x));
        };
    }
}

component.ts

 this.noteForm.addControl(this.finalQtyFormControlName, new FormControl(this.noteSubModuleForm.Value,
        [Validators.required, CustomAsyncValidator.ValidateFinalQty(this.finalQtyService, this.embrService.batchRecordForm)]));

ОБНОВЛЕНИЕ 6/7/2019 # 2

После https://www.youtube.com/watch?v=zeX5CtFqkXQ Я смог сделать валидатор на основе директив, но все равно предпочел бы иметь валидатор в моем TS, если вы видите, что я сделал неправильно в предыдущемОбновить.

@ Directive ({селектор: "[validFinalQty]", поставщики: [{Обеспечить: NG_ASYNC_VALIDATORS, useExisting: ValidateFinalQtyDirective, multi: true}]})

export class ValidateFinalQtyDirective implements AsyncValidator {

    constructor(private fqs: FinalQtyService, private embrService: EmbrService) { }

    validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
        return this.fqs.postCalcFinalQuanity(this.embrService.batchRecordForm).pipe(
            tap(x => {
                console.log("final qty", x);
                console.log("control.value", control.value);
            }),
            map(arr => (arr.Value !== `${control.value}`) ? { FinalQuantityNotMatching: true } : null)
        );
    }

Ответы [ 3 ]

1 голос
/ 23 мая 2019

Надеюсь, это поможет

export class CustomAsyncValidator {
  static ValidateFinalQty(apiService: ApiService):AsyncValidatorFn {
    return (control: AbstractControl) => {
      const value = control.value;
      return apiService.GetQty()
        .pipe(
          take(1),
          map(qty => {
            if (value!=qty) {
              return { FinalQuantityNotMatching: true };
            } else {
              return null;
            }
          })
        );
    };
  }
}

Как использовать это

this.yourForm = this.fb.group({
  yourField: ["",CustomValidator.ValidateFinalQty(this.apiService)]
});
1 голос
/ 12 июня 2019

Поскольку ваш Validator отправляет асинхронный вызов RxJS, будет хорошей идеей использовать AsyncValidator

(см. Также рабочую демонстрацию Iсоздан для вас)

Из документов:

конструктор (formState: any = null, validatorOrOpts ?: ValidatorFn | AbstractControlOptions | ValidatorFn [], asyncValidator ?: AsyncValidatorFn |AsyncValidatorFn [])

Как вы видите, вы можете передать AsyncValidatorFn в качестве третьего аргумента в свой FormControl.

Предполагая, что getQty() возвращает Observable, который испускает значение, котороевам нужно сравнить.Ваш пользовательский валидатор будет выглядеть так:

custom.validator.ts

import { AbstractControl } from '@angular/forms';
import { MyService } from './my.service';
import { map } from 'rxjs/operators';
export class ValidateFinalQty {
  static createValidator(myService: MyService) {
    return (control: AbstractControl) => {
      return myService.getQty().pipe(map(res => {
        return control.value == res ? null :  { FinalQuantityNotMatching: true };
      }));
    };
  }
}

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

Одна вещь, которую я заметил в вашем коде, состоит в том, что вместо подписки на Observable, возвращаемую getQty(), вы присваиваете значение переменной qty, которая являетсяне правильный способ борьбы с Observable.Вы можете узнать больше о Observables здесь

Теперь в вашем компоненте:

import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms'
import { ValidateFinalQty } from './custom.validator';
import { MyService } from './my.service';
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  implements OnInit{
  name = 'Angular';
  myForm: FormGroup

  constructor(private myService: MyService, private fb: FormBuilder) {

  }
  ngOnInit() {
    this.myForm = this.fb.group({
    quantity: [
      '',
      [Validators.required],
      ValidateFinalQty.createValidator(this.myService)
    ]
  });
}
}

И в вашем HTML:

<form [formGroup]="myForm">
  <label>Quantity</label>
  <input type="number" formControlName="quantity" placeholder="Enter quantity to validate">

  <div *ngIf="myForm.get('quantity').status === 'PENDING'">
    Checking...
  </div>
  <div *ngIf="myForm.get('quantity').status === 'VALID'">
    ? Quantity is valid!
  </div>

  <div *ngIf="myForm.get('quantity').errors && myForm.get('quantity').errors.FinalQuantityNotMatching">
    ? Oh noes, your quantity is not valid!
  </div>
</form>


<small>5 is valid quantity for the sake of example</small>

Надеюсь, это поможет.

1 голос
/ 23 мая 2019

У меня есть аналогичная директива Validator, позвольте мне настроить ее в соответствии с вашим кодом:

Проверьте, может ли она работать на вас

import { Directive } from '@angular/core';
import { NG_ASYNC_VALIDATORS, AsyncValidator, AbstractControl, ValidationErrors } from '@angular/forms';
import { MyService } from './MyService';
import { Observable,  of as observableOf} from 'rxjs';


@Directive({
  selector: '[qty-valid]',
  providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: QuantityValidatorDirective , multi: true}]
})
export class QuantityValidatorDirective implements AsyncValidator {    
   constructor(private service : MyService ) { }

    validate(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {

        return new Promise((resolve, reject) => {                
                this.service.GetQty()
                    .subscribe( (qty: any) => {
                       if (control.value != qty) {
                          resolve({ FinalQuantityNotMatching: true })
                       } else {
                          resolve(null)
                       }

                },error => resolve(null));
        });
    }
}
...