Angular에서 다중 제어 사용자 지정 유효성 검사기 만들기

Angular의 반응형 양식 라이브러리에 있는 사용자 지정 유효성 검사기는 개발자가 더 나은 양식의 UI/UX를 만드는 데 필요한 가장 강력한(제 생각에는 간과된) 도구 중 하나입니다. 사용자 지정 유효성 검사기는 단일 컨트롤에만 국한되지 않습니다. 전체 그룹을 평가하는 것은 쉽습니다. 이는 여러 컨트롤을 비교하는 데 유용합니다. 이 문서에서는 값이 일치하는 경우 두 필드의 유효성을 검사하는 다중 제어 사용자 지정 유효성 검사기를 만들어 가능한 예를 보여줍니다.

previous article about custom validators에서 언급했듯이 내장 유효성 검사기가 처리하지 않는 사용자 지정 논리를 처리하고 한 지점에서 유효성 검사 오류 메시지를 생성할 수 있도록 사용하는 것이 좋습니다. 이는 사용자 지정 유효성 검사기를 강력하고 재사용 가능하게 만듭니다.

다중 제어 사용자 지정 유효성 검사기 만들기





다중 컨트롤 사용자 지정 유효성 검사기를 만드는 것은 단일 컨트롤을 만드는 것과 매우 유사합니다. 유효성 검사기는 전달된 매개변수 AbstractControl 가 필요합니다. 단일 컨트롤 유효성 검사기에서 컨트롤은 일반적으로 FormControl 입니다. 그러나 다중 컨트롤 유효성 검사기의 경우 부모 FormGroup 를 컨트롤로 전달해야 합니다. 이렇게 하면 FormGroup 내부의 모든 하위 컨트롤 범위가 제공됩니다. 이 유효성 검사기를 보다 쉽게 ​​재사용할 수 있도록 비교하려는 컨트롤의 이름도 전달합니다. 또한 오류 메시지를 보다 동적으로 만들기 위해 비교하는 값 종류의 이름을 전달할 수도 있습니다.

그런 다음 양식 컨트롤의 값에 대한 변수를 만듭니다. 그것들이 있으면 몇 가지 간단한 조건을 설정합니다. 특정 FormGroup 대신 AbstractControlFormControl를 전달했기 때문에 FormControls에 오류를 설정하려면 특정 컨트롤에서 setErrors() 을 호출해야 합니다. 그렇지 않으면 ValidationErrors 을 반환하면 FormGroup 에 적용되며 여기서 원하는 것이 아닙니다.

export class MatchFieldValidator {
  static validFieldMatch(
    controlName: string,
    confirmControlName: string,
    fieldName: string = 'Password',
  ): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const controlValue: unknown | null = control.get(controlName)?.value;
      const confirmControlValue: unknown | null = control.get(
        confirmControlName,
      )?.value;

      if (!confirmControlValue) {
        control.get(confirmControlName)?.setErrors({
          confirmFieldRequired: `Confirm ${fieldName} is required.`,
        });
      }

      if (controlValue !== confirmControlValue) {
        control
          .get(confirmControlName)
          ?.setErrors({ fieldsMismatched: `${fieldName} fields do not match.` });
      }

      if (controlValue && controlValue === confirmControlValue) {
        control.get(confirmControlName)?.setErrors(null);
      }

      return null;
    };
  }
}


이제 작동하는 유효성 검사기가 있으므로 이를 구성 요소에 연결해야 합니다. 여러 개FormControls와 상호 작용하고 싶기 때문에 유효성 검사기를 부모FormGroup에 연결해야 합니다. FormBuilder 은 유효성 검사기에 전달할 수 있는 제어 구성 뒤에 옵션 인수를 사용합니다. 일치 필드 유효성 검사기를 비교하려는 컨트롤의 이름 및 비교할 필드 종류와 함께 추가합니다. 관련된 내용에만 집중하기 위해 아래 코드를 단순화했습니다.

private createForm(): FormGroup {
  const form = this.fb.group({
    password: [
      '',
      Validators.compose([PasswordValidator.validPassword(true)]),
    ],
    confirmPassword: [''],
  },
  {
    validators: Validators.compose([
      MatchFieldValidator.validFieldMatch('password', 'confirmPassword', 'Password'),
    ]),
  });

  return form;
}


이제 작업 유효성 검사가 있으므로 오류를 템플릿에 바인딩할 수 있습니다. 단순함을 위해 여전히 KeyValuePipe를 통해 오류 개체를 통해 루프를 사용하고 있습니다.

<div class="field-group">
  <mat-form-field>
    <input
      name="password"
      id="password"
      type="password"
      matInput
      placeholder="Password"
      formControlName="password"
    />
    <mat-error *ngIf="form.get('password')?.errors">
      <ng-container *ngFor="let error of form.get('password')?.errors | keyvalue">
        <div *ngIf="error.key !== 'required'">{{ error.value }}</div>
      </ng-container>
    </mat-error>
  </mat-form-field>
  <mat-form-field>
    <input
      name="confirmPassword"
      id="confirmPassword"
      type="password"
      matInput
      placeholder="Confirm Password"
      formControlName="confirmPassword"
      required
    />
    <mat-error *ngIf="form.get('confirmPassword')?.errors">
      <ng-container *ngFor="let error of form.get('confirmPassword')?.errors | keyvalue">
        <div *ngIf="error.key !== 'required'">{{ error.value }}</div>
      </ng-container>
    </mat-error>
  </mat-form-field>
</div>


유효성 검사기 테스트



다른 사용자 지정 유효성 검사기와 마찬가지로 다중 제어 사용자 지정 유효성 검사기를 쉽게 테스트할 수 있습니다. 이 유효성 검사기에 대한 단위 테스트를 작성하면 처음에는 처리하지 못했던 엣지 케이스를 찾고 처리하는 데 도움이 되었습니다. 다음은 몇 가지 예제 테스트입니다.

  describe('validFieldMatch() default field name', () => {
    const matchFieldValidator = MatchFieldValidator.validFieldMatch(
      'controlName',
      'confirmControlName',
    );
    const form = new FormGroup({
      controlName: new FormControl(''),
      confirmControlName: new FormControl(''),
    });
    const controlName = form.get('controlName');
    const confirmControlName = form.get('confirmControlName');

    it(`should set control error as { confirmFieldRequired: 'Confirm Password is required.' } when value is an empty string`, () => {
      controlName?.setValue('');
      confirmControlName?.setValue('');
      matchFieldValidator(form);
      const expectedValue = {
        confirmFieldRequired: 'Confirm Password is required.',
      };
      expect(confirmControlName?.errors).toEqual(expectedValue);
    });

    it(`should set control error as { fieldsMismatched: 'Password fields do not match.' } when values do not match`, () => {
      controlName?.setValue('password123!');
      confirmControlName?.setValue('password123');
      matchFieldValidator(form);
      const expectedValue = {
        fieldsMismatched: 'Password fields do not match.',
      };
      expect(confirmControlName?.errors).toEqual(expectedValue);
    });

    it(`should set control error as null when values match`, () => {
      controlName?.setValue('password123!');
      confirmControlName?.setValue('password123!');
      matchFieldValidator(form);
      expect(controlName?.errors).toEqual(null);
      expect(confirmControlName?.errors).toEqual(null);
    });

    it(`should set control error as null when control matches confirm after not matching`, () => {
      controlName?.setValue('password123!');
      confirmControlName?.setValue('password123!');
      matchFieldValidator(form);
      controlName?.setValue('password123');
      matchFieldValidator(form);
      controlName?.setValue('password123!');
      matchFieldValidator(form);
      expect(controlName?.errors).toEqual(null);
      expect(confirmControlName?.errors).toEqual(null);
    });

    it(`should set control error as null when confirm matches control after not matching`, () => {
      controlName?.setValue('password123!');
      confirmControlName?.setValue('password123!');
      matchFieldValidator(form);
      controlName?.setValue('password123');
      matchFieldValidator(form);
      confirmControlName?.setValue('password123');
      matchFieldValidator(form);
      expect(controlName?.errors).toEqual(null);
      expect(confirmControlName?.errors).toEqual(null);
    });
  });


사용자 지정 유효성 검사기는 만들기 쉽고 매우 강력합니다. 모든 수준의 반응형에서 만들 수 있기 때문에 여러 컨트롤과 상호 작용할 수 있는 이와 같은 다중 컨트롤 사용자 지정 유효성 검사기를 만드는 것이 가능합니다. 이는 개발자가 사용자를 위해 매우 반응적인 UI/UX를 만드는 데 도움이 됩니다.

자원



저장소에는 원하는 동작을 입력하는 데 도움이 되는 unit tests for the validator이 포함되어 있습니다. Here은 GitHub의 리포지토리이고 here은 StackBlitz에 있는 코드의 작업 데모입니다. Angular의 모든 게시물에 태그가 지정되고 수집됩니다here .

게시물 Creating a Multi-Control Custom Validator in AngularHapax Legomenon에 처음 나타났습니다.

좋은 웹페이지 즐겨찾기