问题
is there a way that all validators of a reactive forms can be triggered upon submit and not only by the "dirty" and "touch" events?
The reason for this is we have a very large forms which doesn't indicate if a field is required or not, and user might miss some of the required control, so upon submit, it is expected that all of the invalid fields which are missed by the end user will be shown.
I have tried marking the form as "touched" by using the
FormGroup.markAsTouched(true);
it worked, and so I also tried marking it as "dirty"
FormGroup.markAsDirty(true);
but the css of the class is still "ng-pristine",
Is there a way to trigger it manually from the component, I tried googling it to no avail, thanks you in advance!
UPDATE
I already got it working by iterating the FormGroup.controls and marking it as "dirty", but is there a "standard" way to do this.
回答1:
This can be achieved with the sample presented here, where you can make use of NgForm
directive:
<form [formGroup]="heroForm" #formDir="ngForm">
and then in your validation messages just check if the form is submitted:
<small *ngIf="heroForm.hasError('required', 'formCtrlName') && formDir.submitted">
Required!
</small>
回答2:
There are multiple ways to solve the problem. The @Splaktar's answer won't work if you have nested formgroups. So, here's the solution which will work with nested form groups.
Solution 1: Iterate through all formgroups and formcontrols and programmatically touch them to trigger validations.
Template code:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
...
<button type="submit" class="btn btn-success">Save</button>
</form>
component.ts code:
onSubmit() {
if (this.myForm.valid) {
// save data
} else {
this.validateAllFields(this.myForm);
}
}
validateAllFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFields(control);
}
});
}
Solution 2: Use a variable to check if the form has been submitted or not. FYI: The submitted field for the ngForm is currently being tested and will be included in future Angular versions. So there will not be a need to create your own variable.
component.ts code
private formSubmitAttempt: boolean;
onSubmit() {
this.formSubmitAttempt = true;
if (this.myForm.valid) {
console.log('form submitted');
}
}
Template code:
<form [formGroup]="myForm" (ngSubmit)="onSubmit()" novalidate>
<div class="form-group">
<label class="center-block">
Name:
<input class="form-control" formControlName="name">
</label>
<div class="alert alert-danger" *ngIf="myForm.get('name').hasError('required') && formSubmitAttempt">
Name is required
</div>
...
</form>
回答3:
Coming back after some months, I share here the improved version based on all the comments, just for the record:
markAsTouched(group: FormGroup | FormArray) {
group.markAsTouched({ onlySelf: true });
Object.keys(group.controls).map((field) => {
const control = group.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.markAsTouched(control);
}
});
}
Hope it will be useful!
UPDATE: Angular 8 introduced FormGroup.markAllAsTouched()
and it does this! :D
回答4:
This can be accomplished via markAsTouched(). Until PR #26812 is merged, you can use
function markAllAsTouched(group: AbstractControl) {
group.markAsTouched({onlySelf: true});
group._forEachChild((control: AbstractControl) => markAllAsTouched(control));
}
You can find out more in the source code.
回答5:
I found something that might be of interest:
On submit I set the submitAttempt = true
and put this in the div where validation should take place:
nickname.touched || nickname.dirty || (nickname.untouched && submitAttempt)
meaning: If it hasn't been touched, and we tried to submit, the error shows.
回答6:
My application has many forms and inputs, so I created various custom form components (for normal text inputs, textarea inputs, selects, checkboxes, etc.) so that I don't need to repeat verbose HTML/CSS and form validation UI logic all over the place.
My custom base form component looks up its hosting FormGroupDirective
and uses its submitted
property in addition to its FormControl
states (valid
, touched
, etc.) to decide which validation status and message (if any) needs to be shown on the UI.
This solution
- does not require traversing through the form's controls and modifying their status
- does not require adding some additional
submitted
property to each control - does not require any additional form validation handling in the
ngSubmit
-bindedonSubmit
methods - does not combine template-driven forms with reactive forms
form-base.component:
import {Host, Input, OnInit, SkipSelf} from '@angular/core';
import {FormControl, FormGroupDirective} from '@angular/forms';
export abstract class FormBaseComponent implements OnInit {
@Input() id: string;
@Input() label: string;
formControl: FormControl;
constructor(@Host() @SkipSelf()
private formControlHost: FormGroupDirective) {
}
ngOnInit() {
const form = this.formControlHost.form;
this.formControl = <FormControl>form.controls[this.id];
if (!this.formControl) {
throw new Error('FormControl \'' + this.id + '\' needs to be defined');
}
}
get errorMessage(): string {
// TODO return error message based on 'this.formControl.errors'
return null;
}
get showInputValid(): boolean {
return this.formControl.valid && (this.formControl.touched || this.formControlHost.submitted);
}
get showInputInvalid(): boolean {
return this.formControl.invalid && (this.formControl.touched || this.formControlHost.submitted);
}
}
form-text.component:
import {Component} from '@angular/core';
import {FormBaseComponent} from '../form-base.component';
@Component({
selector: 'yourappprefix-form-text',
templateUrl: './form-text.component.html'
})
export class FormTextComponent extends FormBaseComponent {
}
form-text.component.html:
<label class="x_label" for="{{id}}">{{label}}</label>
<div class="x_input-container"
[class.x_input--valid]="showInputValid"
[class.x_input--invalid]="showInputInvalid">
<input class="x_input" id="{{id}}" type="text" [formControl]="formControl">
<span class="x_input--error-message" *ngIf="errorMessage">{{errorMessage}}</span>
</div>
Usage:
<form [formGroup]="form" novalidate>
<yourappprefix-form-text id="someField" label="Some Field"></yourappprefix-form-text>
</form>
回答7:
There is now the updateOn:’submit’ option which will cause validation to trigger on submit, use like:
this.myForm = new FormGroup({},{updateOn: ‘submit’});
回答8:
If I get what you are asking for. You only want to update validation messages on each submit. The best way to do this is storing the history of the control state.
export interface IValdiationField {
submittedCount: number;
valid: boolean;
}
class Component {
// validation state management
validationState: Map<string, IValdiationField | number> = new Map();
constructor() {
this.validationState.set('submitCount', 0);
}
validationChecker(formControlName: string): boolean {
// get submitted count
const submittedCount: number = (this.validationState.get('submitCount') || 0) as number;
// form shouldn't show validation if form not submitted
if (submittedCount === 0) {
return true;
}
// get the validation state
const state: IValdiationField = this.validationState.get(formControlName) as IValdiationField;
// set state if undefined or state submitted count doesn't match submitted count
if (state === undefined || state.submittedCount !== submittedCount) {
this.validationState.set(formControlName, { submittedCount, valid: this.form.get(formControlName).valid } );
return this.form.get(formControlName).valid;
}
// get validation value from validation state managment
return state.valid;
}
submit() {
this.validationState.set('submitCount', (this.validationState.get('submitCount') as number) + 1);
}
}
Then in the html code *ngIf="!validationChecker('formControlName')" to show error message.
回答9:
"dirty", "touched", "submitted" could be combined using next method:
<form [formGroup]="form" (ngSubmit)="doSomething()" #ngForm="ngForm">
<input type="text" placeholder="Put some text" formControlName="textField" required>
<div *ngIf="textField.invalid && (textField.dirty || textField.touched || ngForm.submitted)">
<div *ngIf="textField.errors.required">Required!</div>
</div>
<input type="submit" value="Submit" />
</form>
来源:https://stackoverflow.com/questions/39893430/angular-2-reactive-forms-trigger-validation-on-submit