You will learn about Angular reactive form validation with server-side Java spring boot interaction in this tutorial.
Introduction
This post will teach us about Angular’s reactive forms validations. On it, we’ll put in place some server-side validations. We will add some unique validations to the reactive form in addition to the built-in checks.
Step 1: Add Service for fetching server-side validation.
I have created one service named as HelperService.
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class HelperService {
constructor(private lookupService: LookupService) {
}
Step 2: Add a method for fetching server-side validation using API.
import { HttpResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { FormGroup, Validators } from "@angular/forms";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { FormValidatorDTO } from "../../sharing/model/FormValidatorDTO";
import { LookupService } from "./lookup.service";
@Injectable({
providedIn: "root",
})
export class HelperService {
constructor(private lookupService: LookupService) {}
getValidation(
formName: string,
companyId: number,
form: FormGroup
): Observable<FormGroup> {
const search = {
"size": 100,
"page": 0,
"sort": "id,desc",
"formName.equals": formName,
"companyId.equals": companyId,
};
return this.lookupService.queryFormValidator(search).pipe(
map((res: HttpResponse<FormValidatorDTO[]>) => {
res.body.forEach((rule) => {
const validator = this.getValidator(rule);
for (const key in form?.controls) {
if (form.controls.hasOwnProperty(key) && key === rule.fieldName) {
const currentValidators = form.controls[key].validator;
const newValidators = Validators.compose([
currentValidators,
validator,
]);
form.controls[key].setValidators(newValidators);
form.controls[key].updateValueAndValidity();
}
}
});
return form;
})
);
}
getValidator(rule: any): any {
switch (rule.type) {
case "required":
return Validators.required;
case "minlength":
return Validators.minLength(rule.value);
case "maxlength":
return Validators.maxLength(rule.value);
case "pattern":
return Validators.pattern(rule.value);
// add more cases as needed
default:
return null;
}
}
}
The getValidation method is a function that accepts three arguments: formGroup from Angular’s Reactive Forms, formName (a string), and companyId (a number). It then returns an Observable with an altered form that emits extra validators to the controls.
Here’s a step-by-step explanation of what the method does:
It creates a search object that defines search parameters for querying form validators. The
size
,page
,sort
,formName.equals
, andcompanyId.equals
properties are used to specify the criteria for fetching form validators from the lookupService. These criteria are based on theformName
andcompanyId
parameters provided to the getValidation method.It calls the queryFormValidator method of the
lookupService
and passes the search object as an argument. This method is assumed to return an Observable that emits an HttpResponse containing an array of FormValidatorDTO objects.It uses the pipe operator to apply transformation operators to the Observable returned by queryFormValidator.
Within the map operator, it processes the response received from queryFormValidator. It iterates over each FormValidatorDTO object in the response using the forEach method.
For each rule in the response, it calls the getValidator method, passing the rule as an argument. This method determines the appropriate validator function based on the type property of the rule object.
It iterates over each control in the form using a for...in loop. For each control, it checks if the control's key (field name) matches the fieldName property of the current rule.
If there is a match, it retrieves the current validators of the control using the validator property.
It creates a new set of validators by combining the current validators with the validator obtained from the getValidator method using
Validators.compose
.It sets the new validators to the control using
setValidators
, which replaces any existing validators on the control.Finally, it calls
updateValueAndValidity
on the control to trigger revalidation and update the control's validity state.After processing all the rules, it returns the modified form from the
map
operator.The
getValidator
method is a helper function that takes a rule object as an argument and returns the appropriate validator function based on the type property of the rule. It uses a switch statement to determine the validator based on the type. Currently, it supports validators such asrequired
,minlength
,maxlength
, andpattern
. You can add more cases as needed to support additional validator types.
Overall, the getValidation
method fetches form validators
based on certain criteria, applies the validators to the corresponding controls of the provided form, and returns the modified form as an observable. This allows for dynamic validation rules based on the fetched form validators.
Here is the JSON response of queryFormValidator() method.
[
{
"id": 606,
"type": "maxlength",
"value": "50",
"formName": "EMPLOYEE",
"fieldName": "firstName",
"companyId": 1,
"status": "A",
"lastModified": "2023-02-08T13:30:56Z",
"lastModifiedBy": "Granite"
},
{
"id": 605,
"type": "minlength",
"value": "2",
"formName": "EMPLOYEE",
"fieldName": "firstName",
"companyId": 1,
"status": "A",
"lastModified": "2023-03-06T11:19:36Z",
"lastModifiedBy": null
},
{
"id": 604,
"type": "required",
"value": "true",
"formName": "EMPLOYEE",
"fieldName": "firstName",
"companyId": 1,
"status": "A",
"lastModified": "2023-03-06T10:57:32Z",
"lastModifiedBy": null
}
]
Step 3: employee-profile.component.ts
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
@Component({
selector: "app-employee-profile",
templateUrl: "./employee-profile.component.html",
styleUrls: ["./employee-profile.component.css"],
})
export class EmployeeProfileComponent implements OnInit {
addPersonalInfoForm: FormGroup;
constructor(
private formBuilder: FormBuilder,
private lookupService: LookupService
) {}
ngOnInit() {
this.addPersonalInfoForm = this.formBuilder.group({
id: undefined,
firstName: ["", []],
middleName: ["", []],
lastName: ["", []],
});
this.queryData();
}
queryData() {
this.lookupService
.getEmployeeById({'id.equals':this.employeeId})
.subscribe((req: HttpResponse<EmployeeDTO>) => {
var temp: EmployeeDTO = {};
this.employee = req.body ? req.body : temp;
this.helperService
.getValidation("EMPLOYEE", 1, this.addPersonalInfoForm)
.subscribe((updatedForm) => {
this.addPersonalInfoForm = updatedForm;
});
});
}
}
In the
ngOnInit
method, aFormGroup
namedaddPersonalInfoForm
is created using theformBuilder.group
method. This form represents the personal information fields, such asfirstName
, middleName, and lastName. Initially, these fields have empty values and no validators assigned to them.The queryData method is called, presumably from within the ngOnInit method or elsewhere. This method is responsible for querying data related to an employee, updating the
form
, and applying validation rules.Inside the queryData method, the
lookupService
is used to retrieve an EmployeeDTO by its ID. ThegetEmployeeById
method is called with a search parameter to find the employee with the specified ID.The result of the
getEmployeeById
method is subscribed to using the subscribe method. When the response is received, the callback function is executed. The response body, if present, is assigned to the employee variable. If the response body is empty, a temporary empty object is assigned to temp.The
helperService.getValidation
method is then called with the arguments "EMPLOYEE
",1
(presumably representing the company ID), andthis.addPersonalInfoForm
(the form group representing personal information).The result of
helperService.getValidation
is subscribed to using the subscribe method. When the updated form is received, the callback function is executed.Inside the callback function, the received updatedForm is assigned to
this.addPersonalInfoForm
. This effectively updates theaddPersonalInfoForm
with the additional validators applied based on the form validation rules retrieved from the helperService.By calling
helperService.getValidation
and subscribing to the updated form, the code dynamically applies validation rules to the personal information form based on the retrieved employee's information. This allows for dynamic validation based on the fetched data, enhancing the form's functionality.
Step 4: employee-profile.component.html
<form (ngSubmit)="editProfileInfo()" [formGroup]="addPersonalInfoForm">
<div class="row">
<div class="col-md-12">
<div class="profile-img-wrap edit-img">
<img [src]="picture" class="inline-block" *ngIf="picture != undefined; else noImageFound">
<ng-template #noImageFound>
<img class="inline-block" src="assets/img/profiles/userIcon.png" alt="Fallbackimage">
</ng-template>
<div class="fileupload btn">
<span class="btn-text">edit</span>
<input class="upload" accept=".jpg , .jpeg , .png" (change)="this.onFilechange($event)" type="file">
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="col-form-label">First Name <span class="text-danger">*</span></label>
<input class="form-control" type="text"
[class.invalid]="addPersonalInfoForm?.get('firstName').invalid && addPersonalInfoForm?.get('firstName').touched"
formControlName="firstName">
<div
*ngIf="addPersonalInfoForm?.get('firstName').invalid && addPersonalInfoForm?.get('firstName').touched">
<small *ngIf="addPersonalInfoForm?.get('firstName').errors.required" class="text-danger">*First
name is required</small>
<small *ngIf="addPersonalInfoForm?.get('firstName').errors.minlength" class="text-danger">*First
name must be at least {{ addPersonalInfoForm?.get('firstName').errors.minlength.requiredLength
}}
characters long</small>
<small *ngIf="addPersonalInfoForm?.get('firstName').errors.maxlength" class="text-danger">*First
name must be at least {{ addPersonalInfoForm?.get('firstName').errors.maxlength.requiredLength
}}
characters long</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="col-form-label">Middle Name <span class="text-danger">*</span></label>
<input class="form-control" type="text"
[class.invalid]="addPersonalInfoForm?.get('middleName').invalid && addPersonalInfoForm?.get('middleName').touched"
formControlName="middleName">
<div
*ngIf="addPersonalInfoForm?.get('middleName').invalid && addPersonalInfoForm?.get('middleName').touched">
<small *ngIf="addPersonalInfoForm?.get('middleName').errors.required" class="text-danger"> *Middle
name is required</small>
<small *ngIf="addPersonalInfoForm?.get('middleName').errors.minlength" class="text-danger">*Middle
name must be at least {{ addPersonalInfoForm?.get('middleName').errors.minlength.requiredLength
}}
characters long</small>
<small *ngIf="addPersonalInfoForm?.get('middleName').errors.maxlength" class="text-danger">*Middle
name must be at least {{ addPersonalInfoForm?.get('middleName').errors.maxlength.requiredLength
}}
characters long</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="col-form-label">Last Name<span class="text-danger">*</span></label>
<input class="form-control" type="text"
[class.invalid]="addPersonalInfoForm?.get('lastName').invalid && addPersonalInfoForm?.get('lastName').touched"
formControlName="lastName">
<div
*ngIf="addPersonalInfoForm?.get('lastName').invalid && addPersonalInfoForm?.get('lastName').touched">
<small *ngIf="addPersonalInfoForm?.get('lastName').errors.required" class="text-danger"> *Last
name is required</small>
<small *ngIf="addPersonalInfoForm?.get('lastName').errors.minlength" class="text-danger">*Last
name must be at least {{ addPersonalInfoForm?.get('lastName').errors.minlength.requiredLength }}
characters long</small>
<small *ngIf="addPersonalInfoForm?.get('lastName').errors.maxlength" class="text-danger">*Last
name must be at least {{ addPersonalInfoForm?.get('lastName').errors.maxlength.requiredLength }}
characters long</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="col-form-label">Department<span class="text-danger">*</span></label>
<select class="form-select form-control" formControlName="department"
[class.invalid]="addPersonalInfoForm?.get('department').invalid && addPersonalInfoForm?.get('department').touched">
<option> -- Select -- </option>
<option *ngFor="let department of departmentList" [value]="department.id">{{department.name}}
</option>
</select>
<div
*ngIf="addPersonalInfoForm?.get('department').invalid && addPersonalInfoForm?.get('department').touched">
<small *ngIf="addPersonalInfoForm?.get('department').errors.required" class="text-danger">
*Department is required</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="col-form-label">Designation<span class="text-danger">*</span></label>
<select class="form-select form-control"
[class.invalid]="addPersonalInfoForm?.get('designation').invalid && addPersonalInfoForm?.get('designation').touched"
formControlName="designation">
<option> -- Select -- </option>
<option *ngFor="let designation of designationList" [value]="designation.id">
{{designation.name}}</option>
</select>
<div
*ngIf="addPersonalInfoForm?.get('designation').invalid && addPersonalInfoForm?.get('designation').touched">
<small *ngIf="addPersonalInfoForm?.get('designation').errors.required" class="text-danger">
*Designation is required</small>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label class="col-form-label">Joining Date<span class="text-danger">*</span></label>
<div class="cal-icon">
<input class="form-control datetimepicker" type="text" bsDatepicker type="text"
[class.invalid]="addPersonalInfoForm?.get('joindate').invalid && addPersonalInfoForm?.get('joindate').touched"
[bsConfig]="{ dateInputFormat: 'YYYY-MM-DD', returnFocusToInput: true }"
formControlName="joindate">
<div
*ngIf="addPersonalInfoForm?.get('joindate').invalid && addPersonalInfoForm?.get('joindate').touched">
<small *ngIf="addPersonalInfoForm?.get('joindate').errors.required" class="text-danger">
*JoinDate
name is required</small>
<small *ngIf="addPersonalInfoForm?.get('joindate').errors.minlength"
class="text-danger">*JoinDate
name must be at least {{ addPersonalInfoForm?.get('joindate').errors.minlength.requiredLength
}}
characters long</small>
<small *ngIf="addPersonalInfoForm?.get('joindate').errors.maxlength"
class="text-danger">*JoinDate
name must be at least {{ addPersonalInfoForm?.get('joindate').errors.maxlength.requiredLength
}}
characters long</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="submit-section">
<button class="btn btn-primary submit-btn">Submit</button>
</div>
</form>
Each form field is represented by an
<input>
or<select>
element. The formControlName attribute is used to bind the form field to the corresponding control in theaddPersonalInfoForm
form group.The
[class.invalid]
directive is used to apply the invalid class to the form field when it is invalid and has been touched by the user. This can be helpful for displaying validation errors.Under each form field, there is a
<div>
element with an*ngIf
directive. It checks if the corresponding form control is invalid and has been touched. If so, it displays a validation error message. The error messages vary based on the validation rules applied to each form control, such asrequired
,minlength
, andmaxlength
.
Top comments (0)