【问题标题】:Angular Async Validator FormControls don't update until blurredAngular Async Validator FormControls 在模糊之前不会更新
【发布时间】:2019-06-10 11:28:24
【问题描述】:

我的异步验证器如下所示:

asyncValidator(service:ApiCallsService):AsyncValidatorFn{
    return (control:FormControl):Promise<ValidationErrors | null> | Observable<ValidationErrors | null> =>{
    let timer$ = timer(2000);
     return timer$.pipe(
      take(1),
      switchMap(()=> {
        let videoId = service.checkUrl(control.value);
        return service.getVideoDescription(videoId).toPromise().then((val:any)=>{

          return (!val.id) ? {"invalidUrl": true} : null;
        })
      })
    )
    }
  }

我的 Async Validator 的问题在于,添加到我的 FormArray 中的 FormControl 在被模糊之前不会获取它们自身的当前“状态”。

这是我的 FormArray 和里面的 FormControl:

<div class="url-con" formArrayName="urls" >
    <div *ngFor="let url of urls.controls; let i=index" class="url-input-con">
        <input  minLength="5" placeholder="Video Url" class="url-input" [formControlName]="i">
        <div class="url-pending" *ngIf="urls.controls[i].pending && !urls.controls[i].valid">Validating...</div>
    </div>
</div>

具有“url-pending”类的 div 出现,然后它不会消失 - 即使它所依赖的 FormControl 由后端验证 - 直到用户模糊了 div 所依赖的 FormControl。

唯一与此类似的其他 stackoverflow 问题是 link。我无法完全理解该链接的说明,并且与链接的海报相比,我遇到的另一个复杂情况是我的表单中有一个形状为加号的图标,以便用户可以向 FormArray 添加更多的 FormControl。我不知道如何向用户通过与表单交互添加的 FormControls 添加指令。

我将回答我自己的问题,因为我想出了如何解决这个问题,但我以一种“hackish”的方式解决了它。如果其他人对此有更好的答案,请回复。

【问题讨论】:

    标签: angular typescript angular-forms


    【解决方案1】:

    我在 formArray (#formArray) 中添加了一个标识符:

    <div #formArray class="url-con" formArrayName="urls" >
        <div *ngFor="let url of urls.controls; let i=index" class="url-input-con">
            <input  minLength="5" placeholder="Video Url" class="url-input" [formControlName]="i">
            <div class="url-pending" *ngIf="urls.controls[i].pending && !urls.controls[i].valid">Validating...</div>
        </div>
    </div>
    

    然后我在 Async Validator 中将 finalize() 添加到 timer$ 的返回中。在操作符的回调里面,我把FormArray的每一个FormControl都做了焦点,然后模糊了。

    asyncValidator(service:ApiCallsService):AsyncValidatorFn{
       return (control:FormControl):Promise<ValidationErrors | null> | Observable<ValidationErrors | null> =>{
       let timer$ = timer(2000);
        return timer$.pipe(
         take(1),
         switchMap(()=> {
           let videoId = service.checkUrl(control.value);
           return service.getVideoDescription(videoId).toPromise().then((val:any)=>{
    
             return (!val.id) ? {"invalidUrl": true} : null;
           })
         }),
         finalize(()=>{
             Array.from(this.formArray.nativeElement.children)
                  .forEach((val:HTMLElement,ind)=>{
                       (Array.from(val.children)[0] as HTMLElement).focus();
                       (Array.from(val.children)[0] as HTMLElement).blur();
                   })         
         })
       )
    }
    }
    

    每个 FormControl 必须首先获得焦点,因为如果用户在验证结束之前模糊,那么 FormControl 永远不会模糊,并且“待定”状态会永远持续显示(虽然不是功能上)。

    【讨论】:

      【解决方案2】:

      我遇到了类似的问题;但是,我可以通过将statusChanges 传递到async 来解决它

      {{field.statusChanges | async}}
      or 
      ...*ngIf="(field.statusChanges | async) === 'PENDING'"...
      

      所以在你的情况下:

      
      <div #formArray class="url-con" formArrayName="urls" >
          <div *ngFor="let url of urls.controls; let i=index" class="url-input-con">
              <input  minLength="5" placeholder="Video Url" class="url-input" [formControlName]="i">
              <div class="url-pending" 
                   *ngIf="(urls.controls[i].statusChanges | async) === 'PENDING'">
                      Validating...
              </div>
          </div>
      </div>
      

      来源

      【讨论】:

        【解决方案3】:

        这与表单的touched 状态有关。直到控件为untouched,它被视为“可能用户尚未完成输入”。

        所以您对症状的看法几乎是正确的,但是还有一种更简单更好的“触摸”控件的方法。 关键部分是使用默认的 ReactiveForm API 来使控件被触摸。看看我的finalize 部分是怎样的:

        component.ts

        
        public emailControl = new FormControl('',
          /* sync */
          [
            control => control.value && /.+@.+/.test(control.value) ? null : { invalidEmail: true },
          ],
          /* async */
          [
            control => control.value ? this.debouncedCheck(control.value) : of(null)
          ]
        );
        
        private debouncedCheck(value): Observable<ValidationErrors> {
          // debounce (although it will be PENDING even during those 500ms!!)
          return timer(500).pipe(
        
            // Handle validation
            switchMap(     () => this.doAsyncCheck(value)),
            tap(checkResponse => /* ... handle validation response ... */),
            catchError( error => /* ... handle runtime errors ... */),
        
            // "Touch" the control since its data was already sent and used somewhere
            finalize(() => this.lookupEmail.markAsTouched()),
        
          );
        }
        

        template.html

        <mat-form-field>
            <mat-label>Find by email</mat-label>
        
            <input matInput [formControl]="emailControl">
            <mat-spinner matSuffix *ngIf="emailControl.pending" [diameter]="15"></mat-spinner>
        
            <mat-error *ngIf="emailControl.hasError('invalidEmail')">Email is invalid</mat-error>
            <mat-error *ngIf="emailControl.hasError('noRecords')">No records found for email</mat-error>
        </mat-form-field>
        

        我个人在尝试实现简单的/.+@.+/ 电子邮件验证时遇到了这个问题。 问题是输入不应该对用户尖叫“无效!!”后第一封信成书面。它应该等到输入大概“完成”:当用户继续下一个输入字段(邮政地址、昵称等)时。 当用户离开控件时,它会得到touched: true 并呈现所有验证(它仍在后台计算,但由于touched: false 状态而没有完全呈现)。现在用户可能会回到触摸的输入(并且它一直保持触摸,直到手动重置状态!)并且将看到所有验证实时更新。

        在上面的示例中,控件在不是“有效”电子邮件之前是无效的,它甚至不会尝试进行异步检查(因为同步失败),但在控制它时它不会显示它touched: false。 但当它最终something@like.this 时,触发异步检查,出现“待处理操作”-spinner,控件本身变为touched: true,并在解析时显示验证结果。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-05
          相关资源
          最近更新 更多