Javascript 使用 Angular 组件处理表单错误 - TypeScript
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/49750645/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Handle form errors using components Angular - TypeScript
提问by Lyes CHIOUKH
I am currently working on a form in Angular/Typescriptof several fields (more than 10 fields), and I wanted to manage the errors more properly without duplicating code in my html page.
我目前正在处理多个字段(超过 10 个字段)的Angular/ Typescript表单,我想更正确地管理错误,而无需在我的 html 页面中复制代码。
Here is an example of a form :
这是一个表格的例子:
<form [formGroup]="myForm">
<label>Name</label>
<input type="text" formControlName="name">
<p class="error_message" *ngIf="myForm.get('name').invalid && (myForm.submitted || myForm.get('name').dirty)">Please provide name</p>
<label>Lastname</label>
<input type="text" formControlName="lastname">
<p class="error_message" *ngIf="myForm.get('lastname').invalid && (myForm.submitted || myForm.get('lastname').dirty)">Please provide email</p>
<label>Email</label>
<input type="text" formControlName="email">
<p class="error_message" *ngIf="myForm.get('email').hasError('required') && (myForm.submitted || myForm.get('email').dirty)">Please provide email</p>
<p class="error_message" *ngIf="myForm.get('email').hasError('email') && (myForm.submitted || myForm.get('email').dirty)">Please provide valid email</p>
</form>
In my case, I have two types of validation for my form :
就我而言,我的表单有两种类型的验证:
- Html validation : required, maxSize, ... etc.
- Back validation : For example, invalid account, size of loaded file, ... etc.
- Html 验证:必需、maxSize、...等。
- 返回验证:例如,无效帐户、加载文件的大小等。
I try to using a directive as mentioned here
我尝试使用这里提到的指令
<form [formGroup]="myForm">
<label>Name</label>
<input type="text" formControlName="name">
<div invalidmessage="name">
<p *invalidType="'required'">Please provide name</p>
</div>
<label>Lastname</label>
<input type="text" formControlName="lastname">
<div invalidmessage="lastname">
<p *invalidType="'required'">Please provide lastname</p>
</div>
<label>Email</label>
<input type="text" formControlName="email">
<div invalidmessage="email">
<p *invalidType="'required'">Please provide email</p>
<p *invalidType="'email'">Please provide valid email</p>
</div>
</form>
But even with this solution the code is always duplicated and no ability to handle both types of validation.
但即使使用此解决方案,代码也始终是重复的,并且无法处理这两种类型的验证。
Do you have another approach ? Is use components appropriate in this case ? If yes, how can do it.
你有另一种方法吗?在这种情况下使用组件是否合适?如果是,怎么办。
Thank you in advance for your investment.
预先感谢您的投资。
采纳答案by JayChase
You can move the validation errors into a component and pass in the formControl.errors as an input property. That way all the validation messages can be re-used. Here is an example on StackBlitz. The code is using Angular Material but still should be handy even if you aren't.
您可以将验证错误移动到组件中,并将 formControl.errors 作为输入属性传入。这样,所有验证消息都可以重复使用。这是一个关于StackBlitz的例子。该代码正在使用 Angular Material,但即使您不是,也应该很方便。
validation-errors.component.ts
验证errors.component.ts
import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, ValidationErrors } from '@angular/forms';
@Component({
selector: 'validation-errors',
templateUrl: './validation-errors.component.html',
styleUrls: ['./validation-errors.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ValidationErrorsComponent implements OnInit {
@Input() errors: ValidationErrors;
constructor() {}
ngOnInit() {}
}
validation-errors.component.html
验证errors.component.html
<ng-container *ngIf="errors && errors['required']"> Required</ng-container>
<ng-container *ngIf="errors && errors['notUnique']">Already exists</ng-container>
<ng-container *ngIf="errors && errors['email']">Please enter a valid email</ng-container>
For the back validation messages set the error manually on the form control.
对于返回验证消息,在表单控件上手动设置错误。
const nameControl = this.userForm.get('name');
nameControl.setErrors({
"notUnique": true
});
To use the validation component on the form:
要在表单上使用验证组件:
<form [formGroup]="userForm" (ngSubmit)="submit()">
<mat-form-field>
<input matInput placeholder="name" formControlName="name" required>
<mat-error *ngIf="userForm.get('name').status === 'INVALID'">
<validation-errors [errors]="userForm.get('name').errors"></validation-errors>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="email" formControlName="email" required>
<mat-error *ngIf="userForm.get('email').status === 'INVALID'">
<validation-errors [errors]="userForm.get('email').errors"></validation-errors>
</mat-error>
</mat-form-field>
<button mat-raised-button class="mat-raised-button" color="accent">SUBMIT</button>
</form>
回答by pixelbits
You can inject NgFormand access the FormControlNamedirective through @ContentChildwithin a custom validator component to achieve re-use:
您可以通过在自定义验证器组件中注入NgForm和访问FormControlName指令@ContentChild以实现重用:
@Component({
selector: '[validator]',
template: `
<ng-content></ng-content>
<div *ngIf="formControl.invalid">
<div *ngIf="formControl.errors.required && (form.submitted || formControl.dirty)">
Please provide {{ formControl.name }}
</div>
<div *ngIf="formControl.errors.email && (form.submitted || formControl.dirty)">
Please provide a valid email
</div>
<div *ngIf="formControl.errors.notstring && (form.submitted || formControl.dirty)">
Invalid name
</div>
</div>
`})
export class ValidatorComponent implements OnInit {
@ContentChild(FormControlName) formControl;
constructor(private form: NgForm) {
}
ngOnInit() { }
}
To use it, you would wrap all your form controls (which has a formControlName) with an HTML element and add a validator attribute:
要使用它,你需要用一个 HTML 元素包装所有的表单控件(它有一个 formControlName)并添加一个验证器属性:
<form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
<div [formGroup]="myForm">
<label>Name</label>
<div validator>
<input type="text" formControlName="name">
</div>
<label>Lastname</label>
<div validator>
<input type="text" formControlName="lastname">
</div>
<label>Email</label>
<div validator>
<input type="text" formControlName="email">
</div>
</div>
<button type="submit">Submit</button>
</form>
This will work for synchronous and asynchronous validators.
这适用于同步和异步验证器。
回答by Aakash Uniyal
I had the same requirement , nobody likes to re-write the same code twice.
我有同样的要求,没有人喜欢两次重写相同的代码。
This can be done by creating custom form controls. The idea is you create your custom form controls , have a common service that Generates a custom formControl object and inject appropriate Validators based on the data type provided into the FormControl Object.
这可以通过创建自定义表单控件来完成。这个想法是你创建你的自定义表单控件,有一个通用的服务来生成一个自定义的 formControl 对象并根据提供给 FormControl 对象的数据类型注入适当的验证器。
Where did the Data type come from ?
数据类型从何而来?
Have a file in your assets or anywhere which contains types like this :
在您的资产或任何包含以下类型的文件中放置一个文件:
[{
"nameType" : {
maxLength : 5 ,
minLength : 1 ,
pattern : xxxxxx,
etc
etc
}
}
]
This you can read in your ValidatorServiceand select appropriate DataType with which you can create your Validators and return to your Custom Form Control.
您可以读取ValidatorService并选择适当的数据类型,您可以使用该数据类型创建验证器并返回到自定义表单控件。
For Example ,
例如 ,
<ui-text name="name" datatype="nameType" [(ngModel)]="data.name"></ui-text>
<ui-text name="name" datatype="nameType" [(ngModel)]="data.name"></ui-text>
This is a brief description of it on a high level of what I did to achieve this. If you need additional information with this , do comment. I am out so cannot provide you with code base right now but sometime tomorrow might update the answer.
这是对我为实现这一目标所做的工作的简要描述。如果您需要与此有关的其他信息,请发表评论。我出去了,所以现在无法为您提供代码库,但明天某个时候可能会更新答案。
UPDATE for the Error Showing part
错误显示部分的更新
You can do 2 things for it , bind your formControl's validator with a div within the control and toggle it with *ngIf="formControl.hasError('required)"` , etc.
您可以为它做两件事,将您的 formControl 的验证器与控件中的 div 绑定,并使用*ngIf="formControl.hasError('required)"` 等进行切换。
For a Message / Error to be displayed in another generic place like a Message Board its better to put that Message Board markup somewhere in the ParentComponent which does not get removed while routing (debatable based on requirement) and make that component listen to a MessageEmit event which your ErrorStateMatcherof your formControl will fire whenever necessary(based on requirement).
要将消息/错误显示在另一个通用位置(如消息板)中,最好将该消息板标记放在 ParentComponent 中的某个位置,该标记在路由时不会被删除(根据需求存在争议)并使该组件侦听 MessageEmit 事件您ErrorStateMatcher的 formControl 将在必要时触发(根据要求)。
This is the design we used and it worked pretty well , you can do a lot with these formControls once you start Customising them.
这是我们使用的设计并且效果很好,一旦您开始自定义它们,您就可以使用这些 formControl 做很多事情。
回答by kemsky
The best way is to implement custom ControlValueAccessors for each type of input, combining <label>, <input>and some tags for displaying error message (in my project I simply use titleattribute for this purpose) in a single component.
最好的办法是实现自定义ControlValueAccessorS对于每种类型的输入,结合<label>,<input>有的标签显示错误信息(在我的项目,我只是用title在单个组件属性用于此目的)。
All value accessors should implement the same interface or extend base abstract class, providing methods to set and clear error message and any other methods which you may want to call from validator directives.
所有值访问器都应该实现相同的接口或扩展基抽象类,提供设置和清除错误消息的方法以及您可能希望从验证器指令调用的任何其他方法。
Also, you will need to implement custom validator directives for each validation type (i had to re-implement even requiredand maxlength), validators must return error objects in uniform way i.e. for email validator {email: "Invalid email address"}. Validator directives can get reference to your control value accessors via injection - @Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[](usually array with one element, AbstractFormComponentis your base class for accessors), use this reference to set or clear accessor error message.
此外,您需要为每种验证类型实现自定义验证器指令(我不得不重新实现 evenrequired和maxlength),验证器必须以统一的方式返回错误对象,即电子邮件验证器{email: "Invalid email address"}。验证器指令可以通过注入获取对您的控制值访问器的引用 - @Inject(NG_VALUE_ACCESSOR) controls:AbstractFormComponent<any>[](通常是一个元素的数组,AbstractFormComponent是访问器的基类),使用此引用来设置或清除访问器错误消息。
You can also implement two additional types of validator directives: sync and async, which can receive validator function via @Inputi.e. [async]="loginValidatorFn", where loginValidatorFnis defined in component class and returns Observable<ValidationErrors>.
您还可以实现另外两种类型的验证器指令:sync 和 async,它们可以通过@Inputie接收验证器函数[async]="loginValidatorFn", whereloginValidatorFn在组件类中定义并返回Observable<ValidationErrors>。
This is real code from our application:
这是我们应用程序中的真实代码:
<div class="input" [caption]="'SSN: '" name="ssn" type="text" [(ngModel)]="item.ssn" [async]="memberSsnValidatorFn" required></div>
回答by amit77309
Here is some part of code I used in library to generate dynamic forms.
这是我在库中用于生成动态表单的部分代码。
This is FormError.tswhich is used to get error and custom messages if we want.
如果需要FormError.ts,这用于获取错误和自定义消息。
import { AbstractControl } from "@angular/forms";
type ErrorFunction = (errorName: string, error: object) => string;
export type ErrorGetter =
string | { [key2: string]: string } | ErrorFunction;
export class FormError {
constructor(private errorGetter?: ErrorGetter) { }
hasError(abstractControl: AbstractControl) {
return abstractControl.errors && (abstractControl.dirty || abstractControl.touched);
}
getErrorMsgs(abstractControl: AbstractControl): string[] {
if (!this.hasError(abstractControl))
return null;
let errors = abstractControl.errors;
return Object.keys(errors).map(anyError => this.getErrorValue(anyError, errors[anyError]));
}
getErrorValue(errorName: string, error: object): string {
let errorGetter = this.errorGetter;
if (!errorGetter)
return predictError(errorName, error);
if (isString(errorGetter))
return errorGetter;
else if (isErrorFunction(errorGetter)) {
let errorString = errorGetter(errorName, error);
return this.predictedErrorIfEmpty(errorString, errorName, error)
}
else {
let errorString = this.errorGetter[errorName];
return this.predictedErrorIfEmpty(errorString, errorName, error)
}
}
predictedErrorIfEmpty(errorString: string, errorName: string, error: object) {
if (errorString == null || errorString == undefined)
return predictError(errorName, error);
return errorString;
}
}
function predictError(errorName: string, error: object): string {
if (errorName === 'required')
return 'Cannot be blank';
if (errorName === 'min')
return `Should not be less than ${error['min']}`;
if (errorName === 'max')
return `Should not be more than ${error['max']}`;
if (errorName === 'minlength')
return `Alteast ${error['requiredLength']} characters`;
if (errorName === 'maxlength')
return `Atmost ${error['requiredLength']} characters`;
// console.warn(`Error for ${errorName} not found. Error object = ${error}`);
return 'Error';
}
export function isString(s: any): s is string {
return typeof s === 'string' || s instanceof String;
}
export function isErrorFunction(f: any): f is ErrorFunction {
return typeof f === "function";
}
Custom Messages
自定义消息
class FormError {
constructor(private errorGetter?: ErrorGetter) { }
}
Now ErrorGetteris like
现在ErrorGetter就像
type ErrorFunction = (errorName: string, error: object) => string;
type ErrorGetter =
string | { [key2: string]: string } | ErrorFunction;
If we want constant error for any error then it should be like
new FormError('Password is not right')If we want constant error for specific error then it should be like
new FormError({required:'Address is necessary.'})For other errors it will go in predict error.
If we want use function for specific error then it should be like
new FormError((errorName,errorObject)=>{ if(errorName=='a') return '2';})For other errors it will go in predict error.
Modify predictError function according to your need.
如果我们想要任何错误的常量错误,那么它应该像
new FormError('Password is not right')如果我们想要特定错误的常量错误,那么它应该像
new FormError({required:'Address is necessary.'})对于其他错误,它将进入预测错误。
如果我们想对特定错误使用函数,那么它应该像
new FormError((errorName,errorObject)=>{ if(errorName=='a') return '2';})对于其他错误,它将进入预测错误。
根据需要修改 predictError 函数。
FormError component
表单错误组件
form-error.html
form-error.html
<ng-container *ngIf="formError.hasError(control)">
<div class='form-error-message' *ngFor='let error of formError.getErrorMsgs(control)'>{{error}}</div>
</ng-container>
form-error.scss
form-error.scss
form-error {
.form-error-message {
color: red;
font-size: .75em;
padding-left: 16px;
}
}
form-error.ts
form-error.ts
@Component({
selector: 'form-error',
templateUrl: 'form-error.html'
})
export class FormErrorComponent {
@Input() formError: FromError;
@Input() control: AbstractControl;
}
Usage
用法
<form-error [control]='thatControl' ></form-error>
Obviously FormErroris not the best design. Modify however you like.
显然FormError不是最好的设计。随心所欲地修改。
回答by ibenjelloun
You could create a custom component ValidationMessagesComponent:
您可以创建一个自定义组件ValidationMessagesComponent:
Template :
模板 :
<p class="error_message" *ngIf="form.get(controlName).hasError('required') && (form.submitted || form.get(controlName).dirty)">Please provide {{controlName}}</p>
<p class="error_message" *ngIf="form.get(controlName).hasError('email') && (form.submitted || form.get(controlName).dirty)">Please provide valid {{controlName}}</p>
...other errors
And with the inputs :
并使用输入:
@Input() controlName;
@Input() form;
Then use it like this :
然后像这样使用它:
<validation-messages [form]="myForm" controlName="email"></validation-messages>
回答by Robin Dijkhof
For the html validation I would write a custom formcontrolwhich will basically be a wrapper around an input. I would also write custom validators which return an error message (Build-in validators return an object I believe). Within your custom formcontrol you can do something like this:
对于 html 验证,我将编写一个自定义表单控件,它基本上是输入的包装器。我还会编写返回错误消息的自定义验证器(内置验证器返回一个我相信的对象)。在您的自定义表单控件中,您可以执行以下操作:
<div *ngIf="this.formControl.errors">
<p>this.formControl.errors?.message</p>
</div>
For the backend validator you can write an async validator.
对于后端验证器,您可以编写一个异步验证器。
回答by Reza
You can use this repowhich has default validation messages and you can customize them as well
你可以使用这个具有默认验证消息的repo,你也可以自定义它们
example usage will be like this
示例用法将是这样的
<form [formGroup]="editorForm" novalidate>
<label>First Name</label>
<input formControlName="firstName" type="text">
<ng2-mdf-validation-message [control]="firstName" *ngIf="!firstName.pristine"></ng2-mdf-validation-message>
</form>
回答by Pengyy
To make template code clear and avoid duplicated code of validating messages, we should change them to be more reusable, here creating a custom directive which adds and removes validating message code block is an option(shown in below demo).
为了使模板代码清晰并避免验证消息的重复代码,我们应该将它们更改为更具可重用性,这里创建一个添加和删除验证消息代码块的自定义指令是一个选项(如下面的演示所示)。
Show/Hide validating messages
显示/隐藏验证消息
In the directive, we can access to directive' host form control and add/remove validating message based on validate status of it by subscribing to it's valueChangesevent.
在指令中,我们可以通过订阅它的valueChanges事件来访问指令的主机表单控件并根据它的验证状态添加/删除验证消息。
@Directive(...)
export class ValidatorMessageDirective implements OnInit {
constructor(
private container: ControlContainer,
private elem: ElementRef, // host dom element
private control: NgControl // host form control
) { }
ngOnInit() {
const control = this.control.control;
control.valueChanges.pipe(distinctUntilChanged()).subscribe(() => {
this.option.forEach(validate => {
if (control.hasError(validate.type)) {
const validateMessageElem = document.getElementById(validate.id);
if (!validateMessageElem) {
const divElem = document.createElement('div');
divElem.innerHTML = validate.message;
divElem.id = validate.id;
this.elem.nativeElement.parentNode.insertBefore(divElem, this.elem.nativeElement.nextSibling);
}
} else {
const validateMessageElem = document.getElementById(validate.id);
if (validateMessageElem) {
this.elem.nativeElement.parentNode.removeChild(validateMessageElem);
}
}
})
});
}
}
Validate options
验证选项
The directive adds and removes validating message based on corresponding validate errors. So the last step we should do is to tell directive which types of validate errors to watch and what messages should be shown, that's the @Inputfield by which we transport validating options to directive.
该指令根据相应的验证错误添加和删除验证消息。所以我们应该做的最后一步是告诉指令要观察哪些类型的验证错误以及应该显示哪些消息,这是@Input我们将验证选项传输到指令的字段。
Then we can simply write template code as below:
然后我们可以简单地编写模板代码如下:
<form [formGroup]="form">
<input type="text" formControlName="test" [validate-message]="testValidateOption"><br/>
<input type="number" formControlName="test2" [validate-message]="test2ValidateOption">
</form>
Refer working demo.
参考工作演示。
回答by hritika panchratan
<form [formGroup]="myForm">
<label>Name</label>
<input type="text" formControlName="name">
<p class="error_message" *ngIf="myForm.get('name').invalid && (myForm.submitted || myForm.get('name').dirty)">Please provide name</p>
<label>Lastname</label>
<input type="text" formControlName="lastname">
<p class="error_message" *ngIf="myForm.get('lastname').invalid && (myForm.submitted || myForm.get('lastname').dirty)">Please provide email</p>
<label>Email</label>
<input type="text" formControlName="email">
<p class="error_message" *ngIf="myForm.get('email').hasError('required') && (myForm.submitted || myForm.get('email').dirty)">Please provide email</p>
<p class="error_message" *ngIf="myForm.get('email').hasError('email') && (myForm.submitted || myForm.get('email').dirty)">Please provide valid email</p>
</form>

