DEV Community

Cover image for Reactive form server-side validation with AngularJs and Java
someshjagtap
someshjagtap

Posted on

Reactive form server-side validation with AngularJs and Java

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) {
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 formNameand companyIdparameters provided to the getValidation method.

  2. It calls the queryFormValidator method of the lookupServiceand 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 updateValueAndValidityon 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 mapoperator.

  12. The getValidatormethod 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 getValidationmethod fetches form validatorsbased 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
  }
]
Enter fullscreen mode Exit fullscreen mode

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;
            });
        });
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. In the ngOnInitmethod, a FormGroupnamed addPersonalInfoFormis 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 lookupServiceis used to retrieve an EmployeeDTO by its ID. The getEmployeeByIdmethod is called with a search parameter to find the employee with the specified ID.

  4. The result of the getEmployeeByIdmethod 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 addPersonalInfoFormwith 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>
Enter fullscreen mode Exit fullscreen mode
  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 addPersonalInfoFormform 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.

Top comments (0)