[英]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.