【问题标题】:Marking child forms as all touched from parent form将子表单标记为从父表单全部触及
【发布时间】:2022-01-26 23:12:51
【问题描述】:

我正在使用 Angular 的 ControlValueAccessor 和 Validator 接口处理 Angular 的出生日期组件。 我已经实现了这些接口所需的所有方法

@Component({
  selector: 'app-birthdate',
  templateUrl: './birthdate.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => BirthdateComponent)
    },
    {
      provide: NG_VALIDATORS,
      multi: true,
      useExisting: forwardRef(() => BirthdateComponent)
    }
  ]
})
export class BirthdateComponent implements ControlValueAccessor, OnDestroy, OnInit, Validator {

  form!: FormGroup;
  birthdayControl!: FormControl;
  birthmonthControl!: FormControl;
  birthyearControl!: FormControl;

  private destroy$ = new Subject<void>();

  private onChange = (_birthdatum: string) => {};
  private onTouched = () => {};

  constructor(private fb: FormBuilder,
              private rootFormGroup: FormGroupDirective) {
  }

  ngOnInit() {
    this.form = this.fb.group({
      birthday: [null, Validators.required],
      birthmonth: [null, Validators.required],
      birthyear: [null, Validators.required]
    });

    this.rootFormGroup.control.addControl('_birthdate', this.form);

    this.birthdayControl = this.form.get('birthday') as FormControl;
    this.birthmonthControl = this.form.get('birthmonth') as FormControl;
    this.birthyearControl = this.form.get('birthyear') as FormControl;

    this.form.valueChanges
      .pipe(takeUntil(this.destroy$))
      .subscribe((birthdate: Geboortedatum) => {
        this.markAsTouched();
        if (!birthdate.birthday || !birthdate.birthmonth || !birthdate.birthyear) {
          return;
        }
        this.onChange(this.parseDate(birthdate.birthyear, birthdate.birthmonth, birthdate.birthday));
      });
  }

  private markAsTouched(): void {
    if (this.birthdayControl.touched && this.birthmonthControl.touched && this.birthyearControl.touched) {
      this.onTouched();
    }
  }

  private parseDate(year: number, month: number, day: number): string {
    return `${year}-${this.parseDatePart(month)}-${this.parseDatePart(day)}`;
  }

  private parseDatePart(datePart: number | string): string {
    return ('00' + datePart).slice(-2);
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  registerOnChange(onChange: (birthday: string) => void): void {
    this.onChange = onChange;
  }

  registerOnTouched(onTouched: () => void): void {
    this.onTouched = onTouched;
  }

  writeValue(birthday: string): void {
    if (birthday == null) {
      this.birthyearControl.patchValue(null);
      this.birthmonthControl.patchValue(null);
      this.birthdayControl.patchValue(null);
    } else {
      this.birthyearControl.patchValue(birthday.slice(0, 4));
      this.birthmonthControl.patchValue(birthday.slice(5, 7));
      this.geboortedagControl.patchValue(birthday.slice(8));
    }
  }

  setDisabledState(disabled: boolean) {
    disabled ? this.form.disable() : this.form.enable();
  }

  validate(control: AbstractControl): ValidationErrors | null {
    const day = this.birthdayControl.value;
    const month = this.birthmonthControl.value;
    const year = this.birthyearControl.value;

    if (!day || !month || !year) {
      return null;
    }

    const parsedDate = this.parseDate(year, month, day);
    const birthdate = new Date(parsedDate);

    if (birthdate.getFullYear() !== year ||
      birthdate.getMonth() + 1 !== month ||
      birthdate.getDate() !== day) {
      const error = {
        validDate: {
          value: parsedDate,
          day: day,
          month: month,
          year: year
        }
      };
      this.birthdayControl.setErrors(error);
      this.birthmonthControl.setErrors(error);
      this.birthyearControl.setErrors(error);
      return error;
    } else {
      this.birthdayControl.updateValueAndValidity({ onlySelf: true, emitEvent: false });
      this.birthmonthControl.updateValueAndValidity({ onlySelf: true, emitEvent: false });
      this.birthyearControl.updateValueAndValidity({ onlySelf: true, emitEvent: false });
      return null;
    }
  }
}

我遇到的问题是调用父表单方法markAllAsTouched()时内部表单没有更新,所以我将rootFormGroup添加到构造函数并将内部表单添加到父级,但现在所有验证器都在当出生日期的值被更改时,父表单会被调用两次。

我在 ControlValueAccessor 接口上缺少writeStatus 或类似方法。我是不是错过了什么。

【问题讨论】:

    标签: angular controlvalueaccessor


    【解决方案1】:

    好的,经过一番折腾,我想出了一个解决方案。

    而不是这个。

    this.rootFormGroup.control.addControl('_birthdate', this.form);
    

    我做了这个

    this.birthDateControl = this.rootFormGroup.control.get(this.formGroupName);
    

    我还在 BirthdateComponent 中添加了以下属性:

    @Input() formGroupName!: string; // Is already set because the ControlValueAccessor also needs it.
    
    birthDateControl!: FormControl;
    

    在视图中,我更改了所有错误类

    <select [ngClass]="{ 'form-field__select--error': birthdayControl.invalid && birthdayControl.touched }" formControlName="birthday">
    

    <select [ngClass]="{ 'form-field__select--error': birthdateControl.invalid && birthdateControl.touched }" formControlName="birthday">
    

    birthmonth 和birthyear 选择输入字段相同。现在像魅力一样工作。

    【讨论】:

      猜你喜欢
      • 2017-09-18
      • 1970-01-01
      • 2022-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 1970-01-01
      相关资源
      最近更新 更多