Reactive form server-side validation with AngularJs and Java
Tags: javascript, typescript, form, angular
Published: 2023-07-13 18:29:11

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:

1. It creates a search object that defines search parameters for querying form validators. The `size`, `page`, `sort`, `formName.equals`, and `companyId.equals` properties are used to specify the criteria for fetching form validators from the lookupService. These criteria are based on the `formName `and `companyId `parameters provided to the getValidation method.

2. 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.

3. It uses the pipe operator to apply transformation operators to the Observable returned by queryFormValidator.

4. Within the map operator, it processes the response received from queryFormValidator. It iterates over each FormValidatorDTO object in the response using the forEach method.

5. 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.

6. 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.

7. If there is a match, it retrieves the current validators of the control using the validator property.

8. It creates a new set of validators by combining the current validators with the validator obtained from the getValidator method using `Validators.compose`.

9. It sets the new validators to the control using `setValidators`, which replaces any existing validators on the control.

10. Finally, it calls `updateValueAndValidity `on the control to trigger revalidation and update the control's validity state.

11. After processing all the rules, it returns the modified form from the `map `operator.

12. 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 as `required`, `minlength`, `maxlength`, and `pattern`. 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;
            });
        });
  }
}
```


1. In the `ngOnInit `method, a `FormGroup `named `addPersonalInfoForm `is created using the `formBuilder.group` method. This form represents the personal information fields, such as `firstName`, middleName, and lastName. Initially, these fields have empty values and no validators assigned to them.

2. 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.

3. Inside the queryData method, the `lookupService `is used to retrieve an EmployeeDTO by its ID. The `getEmployeeById `method is called with a search parameter to find the employee with the specified ID.

4. 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.

5. The `helperService.getValidation` method is then called with the arguments "`EMPLOYEE`", `1` (presumably representing the company ID), and `this.addPersonalInfoForm` (the form group representing personal information).

6. The result of `helperService.getValidation` is subscribed to using the subscribe method. When the updated form is received, the callback function is executed.

7. Inside the callback function, the received updatedForm is assigned to `this.addPersonalInfoForm`. This effectively updates the `addPersonalInfoForm `with the additional validators applied based on the form validation rules retrieved from the helperService.

8. 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>
```


1. 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 the `addPersonalInfoForm `form group.

2. 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.

3. 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 as `required`, `minlength`, and `maxlength`.