Reflecting server errors in Angular form validation
Feb. 12, 2020
Here's some sample code that shows how to display server validation errors sent by a django-restframework REST server.
<form [formGroup]='form' (ngSubmit)="doChangePassword()">
<ion-item>
<ion-label>{{ 'CURRENT_PASSWORD' | translate }}</ion-label>
<ion-input formControlName="old_password" type="password"></ion-input>
</ion-item>
<ion-item class="ion-text-wrap ion-no-lines" *ngIf="form.controls.old_password.errors?.required && form.controls.old_password.dirty && form.controls.old_password.touched">
<ion-label color='danger' class="ion-no-margin" stacked>{{ 'FIELD_IS_REQUIRED' | translate }}</ion-label>
</ion-item>
<ion-item class="ion-no-lines" *ngFor="let errorMsg of form.controls.old_password.errors?.serverErrors">
<ion-label color='danger' class="ion-no-margin ion-text-wrap" stacked>
<small>{{ errorMsg }}</small>
</ion-label>
</ion-item>
<ion-item>
<ion-label>{{ 'NEW_PASSWORD' | translate }}</ion-label>
<ion-input formControlName="new_password1" type="password"></ion-input>
</ion-item>
<ion-item class="ion-text-wrap ion-no-lines" *ngIf="form.controls.new_password1.errors?.required && form.controls.new_password1.dirty && form.controls.new_password1.touched">
<ion-label color='danger' class="ion-no-margin" stacked><small>{{ 'FIELD_IS_REQUIRED' | translate }}</small></ion-label>
</ion-item>
<ion-item class="ion-no-lines" *ngFor="let errorMsg of form.controls.new_password1.errors?.serverErrors">
<ion-label color='danger' class="ion-no-margin ion-text-wrap" stacked>
<small>{{ errorMsg }}</small>
</ion-label>
</ion-item>
<ion-item>
<ion-label>{{ 'CONFIRM_NEW_PASSWORD' | translate }}</ion-label>
<ion-input formControlName="new_password2" type="password"></ion-input>
</ion-item>
<ion-item class="ion-text-wrap ion-no-lines" *ngIf="form.controls.new_password2.errors?.required && form.controls.new_password2.dirty && form.controls.new_password2.touched">
<ion-label color='danger' class="ion-no-margin" stacked><small>{{ 'FIELD_IS_REQUIRED' | translate }}</small></ion-label>
</ion-item>
<ion-item class="ion-text-wrap ion-no-lines" *ngIf="form.controls.new_password2.errors?.matchError && form.controls.new_password2.touched && form.controls.new_password2.dirty">
<ion-label color='danger' class="ion-no-margin" stacked>
<small>{{ 'PASSWORDS_MISMATCH' | translate }}</small>
</ion-label>
</ion-item>
<ion-item class="ion-no-lines" *ngFor="let errorMsg of form.controls.new_password2.errors?.serverErrors">
<ion-label color='danger' class="ion-no-margin ion-text-wrap" stacked>
<small>{{ errorMsg }}</small>
</ion-label>
</ion-item>
<ion-button type="submit" expand="block" [disabled]="!form.valid">
{{ 'CHANGE_PASSWORD' | translate }}
</ion-button>
</form>
Specifically, note how ngIf
& ngFor
tags are used to conditionally detect and display error messages set on individual controls. The attribute serverErrors on a control is set by the page handler TypeScript code via a snippet like this:
// Form definition
this.form = formBuilder.group({
old_password: ['', Validators.required],
new_password1: ['', Validators.compose([
Validators.required,
])],
new_password2: ['', Validators.required],
}, {
// form submit handler
async doFormSubmit() {
try {
await this.service.post(this.form.value);
let toast = await this.toastCtrl.create({
message: 'Server updated',
duration: 3000,
position: 'top'
});
toast.present();
this.router.navigateByUrl('/home');
} catch (err) {
// 'err' contains server errors
this.handleServerErrors(err.error);
}
}
// For fields with specific error messages, set the relevant field
// control's error.serverError attribute to the server returned error
// message. This then can be directly displayed in the template.
handleServerErrors(err) {
// Each key of the err object will correspond to the the field
// name which has an error. Since the field names in the form match
// the REST serializer fields, we can use this to attach the server
// supplied error messages to corresponding UI input field.
for (const controlName in err) {
if (this.form.contains(controlName)) {
this.form.controls[controlName].setErrors({serverErrors: err[controlName]});
} else {
// Need to handle generic form errors, that do not apply to a specific
// field, but to the whole form.
}
}
}