用AngularJS和Java进行反应性形式的服务器端验证
#javascript #angular #typescript #form

您将在本教程中使用服务器端Java Spring Boot互动的角度反应形式验证。

简介

这篇文章将教会我们有关Angular的反应性形式验证。在上面,我们将进行一些服务器端验证。除了内置检查外,我们还将为反应性形式添加一些唯一的验证。

步骤1:添加用于获取服务器端验证的服务。
我创建了一个名为helperService的服务。

import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root",
})
export class HelperService {
  constructor(private lookupService: LookupService) {
}

步骤2:添加一种使用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;
    }
  }
}

getValidation方法是一个接受三个参数的函数:来自Angular的反应性形式,FormName(字符串)和CompanyID(数字)的FormGroup。然后,它返回具有更改形式的可观察到的,该表格将额外的验证器发射到控件中。

这是该方法的逐步说明:

  1. 它创建了一个搜索对象,该对象定义用于查询表单验证器的搜索参数。 sizepagesortformName.equalscompanyId.equals属性用于指定从LookupService获取形式验证器的标准。这些标准基于提供给GetValidation方法的formNamecompanyId参数。

  2. 它调用了lookupService的QueryFormValidator方法,并将搜索对象作为参数传递。假定此方法返回一个可观察的可发出的可发出的httpresponse,该响应包含一个formValidatordto对象的数组。

  3. 它使用管道操作员将转换操作员应用于QueryFormValidator返回的可观察到的。

  4. 在地图运算符中,它处理从QueryFormValidator接收到的响应。它使用foreach方法在响应中的每个formvalidatordto对象上迭代。

  5. 对于响应中的每个规则,它调用getValidator方法,将规则作为参数传递。此方法根据规则对象的类型属性确定适当的验证函数。

  6. 它使用for ... In循环以for形式在每个控件上迭代。对于每个控件,它检查控件的键(字段名称)是否匹配当前规则的字段名称属性。

  7. 如果有匹配,它使用验证器属性检索控件的当前验证器。

  8. 它通过将当前验证器与从getValidator方法获得的验证器相结合,创建了一组新的验证器。

  9. 它使用setValidators将新验证器设置为控件,该验证器替换了控件上的任何现有验证器。

  10. 最后,它将updateValueAndValidityon称为“控件”以触发重新验证并更新控件的有效性状态。

  11. 处理所有规则后,它将从mapoperator返回修改的表单。

  12. getValidatormethod是一个辅助函数,它将规则对象作为参数并根据规则的类型属性返回适当的验证器函数。它使用开关语句根据类型确定验证器。目前,它支持验证器,例如requiredminlengthmaxlengthpattern。您可以根据需要添加更多情况来支持其他验证器类型。

总体而言,基于某些标准的getValidationmethod fetches表格validators键,将验证器应用于提供的形式的相应控件,并将修改的表单作为可观察的形式返回。这允许基于获取的表单验证器制定动态验证规则。

这是QueryFormValidator()方法的JSON响应。

[
  {
    "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
  }
]

步骤3:员工 - 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. ngOnInitmethod中,使用formBuilder.group方法创建的FormGroup名为addPersonalInfoFormis。此表单代表个人信息字段,例如firstName,Middlename和LastName。最初,这些字段具有空值,没有分配给它们的验证器。

  2. querydata方法被称为,大概是在ngoninit方法或其他地方的。此方法负责查询与员工有关的数据,更新form并应用验证规则。

  3. 在querydata方法中,lookupServiceis用于通过其ID检索就业。 getEmployeeByIdmethod带有搜索参数,以找到具有指定ID的员工。

  4. getEmployeeByIdmethod的结果订阅使用订阅方法。收到响应后,执行回调函数。响应主体(如果存在)将分配给员工变量。如果响应主体为空,则将临时空的对象分配给临时。

  5. 然后,
  6. 随后使用参数“ EMPLOYEE”,1(大概代表公司ID)和this.addPersonalInfoForm(代表个人信息的表单组)来调用helperService.getValidation方法。

  7. helperService.getValidation的结果订阅使用订阅方法。收到更新的表单后,执行回调函数。

  8. 在回调函数中,将接收到的更新被分配给this.addPersonalInfoForm。这有效地更新了addPersonalInfoForm,并根据从HelperService检索的表单验证规则应用的其他验证器。

  9. 通过调用helperService.getValidation并订阅更新的表单,该代码根据检索到的员工的信息将验证规则动态应用于个人信息表格。这允许基于获取的数据进行动态验证,从而增强表单的功能。

步骤4:员工 - 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. 每个表单字段由<input><select>元素表示。 formControlname属性用于将表单字段绑定到addPersonalInfoFormform组中的相应控件。

  2. [class.invalid]指令用于将无效类应用于表单字段时,并已被用户触摸。这可能有助于显示验证错误。

  3. 在每个表单字段下,都有一个带有*ngIf指令的<div>元素。它检查相应的形式控件是否无效并已触摸。如果是这样,它将显示验证错误消息。错误消息根据应用于每个表单控件的验证规则(例如requiredminlengthmaxlength)而变化。