【问题标题】:Angular reactive forms custom validator with an async RxJS call带有异步 RxJS 调用的 Angular 响应式表单自定义验证器
【发布时间】:2019-10-10 09:26:34
【问题描述】:

查看下方更新

我意识到我的问题是我对 observables 和 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",

更新 5/23/19 尝试实施 @Sachin 的答案。 我在地图内的断点永远不会被击中。我没有得到任何控制台日志,即使我删除了地图中的逻辑并返回 null,它仍然总是返回无效。对这里发生的事情感到非常困惑。我的服务实际上被调用了,我已经确认了。

有什么想法吗?

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

2019 年 6 月 7 日更新

在订阅日志中,我得到了正确的答案(null 或 { FinalQuantityNotMatching: true }),但我的表单控件仍然无效。我做错了什么?

验证器.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));
        };
    }
}

组件.ts

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

2019 年 6 月 7 日更新 #2

https://www.youtube.com/watch?v=zeX5CtFqkXQ 之后,我能够制作一个基于指令的验证器,但如果您能够看到我在之前的更新中做错了什么,我仍然希望在我的 ts 中使用验证器。

@Directive({ 选择器:“[validFinalQty]”, 提供者:[{提供:NG_ASYNC_VALIDATORS,useExisting:ValidateFinalQtyDirective,多:真}] })

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

【问题讨论】:

  • 你能展示一下getQty()的实现吗?
  • .subscribe(x =&gt; console.log("subscribe output", x));此订阅错误。您应该在不订阅的情况下传递 observable

标签: angular rxjs angular-reactive-forms


【解决方案1】:

我有一个类似的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));
        });
    }
}

【讨论】:

  • 你会如何在指令中使用这样的验证器?我试过类似new FormControl(null, [Validators.required, QuantityValidatorDirective.validate(myService)]
  • 那么我该如何改变它才能像new FormControl(null, [Validators.required, QuantityValidatorDirective.validate(myService)]一样调用它?
  • 我不知道你在尝试什么新的 FormControl... 你可以将此指令用作任何其他指令,只需将 qty-valid 作为元素属性
【解决方案2】:

希望对您有所帮助

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

【讨论】:

  • 我尝试了你的答案,但无法让它工作,地图中的代码似乎没有调用。我已经更新了我上面的问题。
  • @azulBonnet ,我在rxjs 中使用take(1) 运算符更新了我的代码。
  • 它只是在地图上以某种方式窒息,它不会抛出任何错误,也永远不会返回。
  • 好的,我想我刚刚意识到这是因为它是一个“冷”的 observable,需要在最后添加订阅,这就是为什么什么都没有调用。
  • @azulBonnet observable 可以是 finiteinfinite 。只是为了确保您是否尝试过使用take(1)
【解决方案3】:

当您的验证器发送异步 RxJS 调用时,最好使用 AsyncValidator

(另请参阅我为您创建的工作demo

来自文档:

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

在这里,我创建了一个静态方法,它将接受您的服务并执行调用并在条件不匹配时给出错误。

我在您的代码中注意到的一件事是,您没有订阅 getQty() 返回的 Observable,而是将值分配给 qty 变量,这不是处理 Observable 的正确方法。你可以了解更多关于 Observables here

现在在您的组件中:

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>

希望对你有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-09
    • 2017-08-28
    • 2020-04-06
    • 1970-01-01
    • 2022-01-23
    • 2020-10-16
    • 2021-01-12
    • 1970-01-01
    相关资源
    最近更新 更多