简体   繁体   English

如何为 angular 反应式表单中的表单值添加自定义唯一验证器?

[英]How to add custom unique validator for form values in angular reactive form?

I want to add a custom unique validator that will validate that all label fields values are unique.我想添加一个自定义唯一验证器,该验证器将验证所有 label 字段值都是唯一的。 (I) When I change the form values, the value of this.form changes after it is passed in CustomValidator.uniqueValidator(this.form) . (I) 当我更改表单值时, this.form 的值在传入CustomValidator.uniqueValidator(this.form)后会发生变化。 How to fix this?如何解决这个问题? (II) Is there any way of doing this without using any package? (II) 有没有什么方法可以在不使用任何 package 的情况下做到这一点?

Note: Forms have default values on load.注意:Forms 在加载时具有默认值。 Here is the screenshot.这是屏幕截图。

在此处输入图像描述

this.form = this.fb.group({
      fields: this.fb.array([])
    });

private addFields(fieldControl?) {
return this.fb.group({
  label: [
    {value: fieldControl ? fieldControl.label : '', disabled: this.makeComponentReadOnly}, [
    Validators.maxLength(30), CustomValidator.uniqueValidator(this.form)
    ]],
  isRequired: [
    {value: fieldControl ? fieldControl.isRequired : false, disabled: this.makeComponentReadOnly}],
  type: [fieldControl ? fieldControl.type : 'text']
});

} }

  static uniqueValidator(form: any): ValidatorFn | null {
return (control: AbstractControl): ValidationErrors | null => {
  console.log('control..: ', control);
  const name = control.value;

  if (form.value.fields.filter(v => v.label.toLowerCase() === control.value.toLowerCase()).length > 1) {
    return {
      notUnique: control.value
    };
  } else {
    return null;
  }

}; }; } }

in real life, username or email properties are checked to be unique.在现实生活中,用户名或 email 属性被检查为唯一。 This will be very long answer I hope you can follow along.这将是一个很长的答案,我希望你能跟进。 I will show how to check uniqueness of username.我将展示如何检查用户名的唯一性。

to check the database, you have to create a service to make a request.要检查数据库,您必须创建一个服务来发出请求。 so this validator will be async validator and it will be written in class.所以这个验证器将是异步验证器,它将被写入 class。 this class will be communicate with the service via the dependency injection technique.这个 class 将通过依赖注入技术与服务通信。

First thing you need to setup HttpClientModule .首先你需要设置HttpClientModule in app.module.ts在 app.module.ts

import { HttpClientModule } from '@angular/common/http';
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, YourOthersModule , HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})

then create a service然后创建一个服务

 ng g service Auth //named it Auth

in this auth.service.ts在这个 auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(private http: HttpClient) {}
  userNameAvailable(username: string) {
 // avoid type "any". check the response obj and put a clear type
    return this.http.post<any>('https://api.angular.com/username', {
      username:username,
    });
  }
}

now create a class ng g class UniqueUsername and in this class:现在创建一个 class ng g class UniqueUsername并在此 class 中:

import { Injectable } from '@angular/core';
import { AsyncValidator, FormControl } from '@angular/forms';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { AuthService } from './auth.service';
// this class needs to use the dependency injection to reach the http client to make an api request
// we can only access to http client with dependecny injection system
// now we need to decorate this class with Injectable to access to AuthService
@Injectable({
  providedIn: 'root',
})
export class UniqueUsername implements AsyncValidator {
  constructor(private authService: AuthService) {}
  //this will be used by the usernamae FormControl
  //we use arrow function cause this function will be called by a 
  different context, but we want it to have this class' context 
  because this method needs to reach `this.authService`. in other 
  context `this.authService` will be undefined.
  // if this validator would be used by the FormGroup, you could use 
  "FormGroup" type.
  //if you are not sure you can  use type "control: AbstractControl"
  //In this case you use it for a FormControl
  
    validate = (control: FormControl) => {
    const { value } = control;
    return this.authService.userNameAvailable(value).pipe(
    //errors skip the map(). if we return null, means we got 200 response 
    code, our request will indicate that username is available
    //catchError will catch the error
      map(() => {
        return null;
      }),
      catchError((err) => {
        console.log(err);
     //you have to console the error to see what the error object is. so u can 
     set up your logic based on properties of the error object.
   // i set as err.error.username as an  example. your api server might 
    return an error object with different properties.
        if (err.error.username) {
   //catchError has to return a new Observable and "of" is a shortcut
   //if err.error.username exists, i will attach `{ nonUniqueUsername: true }`
   to the formControl's error object.
           return of({ nonUniqueUsername: true });
        }
        return of({ noConnection: true });
      })
    );
  };
}

So far we handled the service and async class validator, now we implement this on the form.到目前为止,我们处理了服务和异步 class 验证器,现在我们在表单上实现它。 I ll have only username field.我将只有用户名字段。

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { UniqueUsername } from '../validators/unique-username';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.css'],
})
export class SignupComponent implements OnInit {
  authForm = new FormGroup(
    {
      // async validators are the third arg
      username: new FormControl(
        '',
        [
          Validators.required,
          Validators.minLength(3),
          Validators.maxLength(20),
          Validators.pattern(/^[a-z0-9]+$/),
        ],
     // async validators are gonna run after all sync validators 
     successfully completed running because async operations are 
     expensive.
        this.uniqueUsername.validate
      ),
    },
    { validators: [this.matchPassword.validate] }
  );
  constructor(
    private uniqueUsername: UniqueUsername
  ) {}


//this is used inside the template file. you will see down below
showErrors() {
    const { dirty, touched, errors } = this.control;
    return dirty && touched && errors;
  }
  ngOnInit(): void {}
}

Final step is to show the error to the user: in the form component's template file:最后一步是向用户显示错误:在表单组件的模板文件中:

<div class="field">
  <input  formControl="username"  />
  <!-- this is where you show the error to the client -->
  <!-- showErrors() is a method inside the class -->
  
  <div *ngIf="showErrors()" class="ui pointing red basic label">
    <!-- authForm.get('username') you access to the "username" formControl -->
    <p *ngIf="authForm.get('username').errors.required">Value is required</p>
    <p *ngIf="authForm.get('username').errors.minlength">
      Value must be longer
      {{ authForm.get('username').errors.minlength.actualLength }} characters
    </p>
    <p *ngIf="authForm.get('username').errors.maxlength">
      Value must be less than {{ authForm.get('username').errors.maxlength.requiredLength }}
    </p>
    <p *ngIf="authForm.get('username').errors.nonUniqueUsername">Username is taken</p>
    <p *ngIf="authForm.get('username').errors.noConnection">Can't tell if username is taken</p>
  </div>
</div>

You could create a validator directive that goes on the parent element (an ngModelGroup or the form itself):您可以在父元素(ngModelGroup 或表单本身)上创建一个验证器指令:

import { Directive } from '@angular/core';
import { FormGroup, ValidationErrors, Validator, NG_VALIDATORS } from '@angular/forms';

@Directive({
  selector: '[validate-uniqueness]',
  providers: [{ provide: NG_VALIDATORS, useExisting: UniquenessValidator, multi: true }]
})
export class UniquenessValidator implements Validator {

  validate(formGroup: FormGroup): ValidationErrors | null {
    let firstControl = formGroup.controls['first']
    let secondControl = formgroup.controls['second']
    // If you need to reach outside current group use this syntax:
    let thirdControl =  (<FormGroup>formGroup.root).controls['third']

    // Then validate whatever you want to validate
    // To check if they are present and unique:
    if ((firstControl && firstControl.value) &&
        (secondControl && secondControl.value) &&
        (thirdContreol && thirdControl.value) &&
        (firstControl.value != secondControl.value) &&
        (secondControl.value != thirdControl.value) &&
        (thirdControl.value != firstControl.value)) {
      return null;
    }

    return { validateUniqueness: false }
  }

}

You can probably simplify that check, but I think you get the point.您可能可以简化该检查,但我认为您明白了。 I didn't test this code, but I recently did something similar with just 2 fields in this project if you want to take a look:我没有测试这段代码,但如果你想看一下,我最近在这个项目中只用了 2 个字段做了类似的事情:

https://github.com/H3AR7B3A7/EarlyAngularProjects/blob/master/modelForms/src/app/advanced-form/validate-about-or-image.directive.ts https://github.com/H3AR7B3A7/EarlyAngularProjects/blob/master/modelForms/src/app/advanced-form/validate-about-or-image.directive.ts

Needless to say, custom validators like this are fairly business specific and hard to make reusable in most cases.不用说,像这样的自定义验证器是相当特定于业务的,在大多数情况下很难使其可重用。 Change to the form might need change to the directive.更改表格可能需要更改指令。 There is other ways to do this, but this does work and it is a fairly simple option.还有其他方法可以做到这一点,但这确实有效,而且是一个相当简单的选择。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Angular 6反应形式验证不适用于自定义验证器 - Angular 6 reactive form validation not working with custom validator 自定义验证器在以角度 2 重置反应形式时抛出错误 - Custom Validator throw error upon resetting reactive form in angular 2 自定义验证器无法访问 Angular 中的表单 6 反应性 forms - Custom validator not being able to access form in Angular 6 Reactive forms Angular 4:使用自定义异步验证器,反应式表单控件卡在挂起的 state - Angular 4: reactive form control is stuck in pending state with a custom async validator 测试在Angular中具有自定义验证器的反应形式字段 - Test a Reactive Form field that has a custom validator in Angular 在以角4反应形式进行控件初始化后,如何添加自定义验证? - How to add custom validation after control initialization in angular 4 reactive form? 如何在 angular 反应式表单构建器中调用异步验证器 function 模糊? - How call async validator function on blur in angular reactive form builder? Angular反应形式验证器中的密码验证错误 - Password Validation error in Angular Reactive Form Validator Angular 具有自定义表单级验证器和 updateOn 'blur' 选项的反应式表单不起作用 - Angular reactive form with custom form-level validator AND updateOn 'blur' option doesn't works 如何在Angular的FormArray反应形式中获得唯一值? - How to get unique value in FormArray reactive form in Angular?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM