Angular 4 监听 setValue 或 patchValue 而不是使用 valueChanges

Angular 4 Listen for setValue or patchValue instead of using valueChanges

我在反应形式的字段中添加了一个浮点指令,每 1000 个添加一个逗号,并将 .00 附加到字段值,专门用于 UI 中的可读性,效果很好。

当表单加载现有值时,我希望对这些值进行格式化,所以我将其添加到我的浮点指令中,以便在使用 setValue 或 [=15= 填充表单字段时对值进行一次格式化],效果很好。

浮点指令的片段

public ngOnInit() {
  this.formatFloat();
}

private formatFloat() {
  const handle = this.ngControl.valueChanges
    .subscribe((value: string) => {
      const float = this.getFloat();
      if (float) {
        this.element.value = this.format(value);
      }
      handle.unsubscribe();
    });
}

** Added the full directive below, but this is only part that really matters.

但是,如果您在填写空表单时动态地将表单字段添加到 FormArray,这将不会触发一次性格式设置,因此您在字段中键入的第一个数字会添加格式设置。例如,打开一个空表单,单击一个按钮以添加一个动态字段,在该字段中键入 1 会触发一次 valueChange 并且输入现在有 1.00 并且用户将继续输入 1.001244 而不是 11244.

我知道 patchValuesetValue 通过 emitEvent docs 直接链接到 valueChanges,但是有没有办法监听 setValuepatchValue 改变而不是监听 valueChanges?或者有另一种方法可以使它工作,但仍然具有现有功能,因为即使只听 setValuepatchValue 也意味着一次性格式订阅仍然有效。

浮点指令

import { Directive, HostListener, ElementRef, OnInit } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { FormGroup, NgControl } from '@angular/forms';

@Directive({
  selector: '[cfFloat]',
  providers: [DecimalPipe] // TODO: why do I need this?
})
export class FloatDirective implements OnInit {
  public element: HTMLInputElement;

  constructor(
    private elementRef: ElementRef,
    private decimalPipe: DecimalPipe,
    private ngControl: NgControl
  ) {
    this.element = this.elementRef.nativeElement;
  }

  @HostListener('blur', ['$event'])
  onBlur(event: KeyboardEvent) {
    const float = this.getFloat();
    if (float) {
      this.element.value = this.format(float);
    }
  }

  @HostListener('focus', ['$event'])
  onFocus(event: KeyboardEvent) {
    const float = this.getFloat();
    if (float) {
      this.element.value = this.replace(float);
    }
  }

  public ngOnInit() {
    this.formatFloat();
  }

  private formatFloat() {
    const handle = this.ngControl.valueChanges
      .subscribe((value: string) => {
        const float = this.getFloat();
        if (float) {
          this.element.value = this.format(value);
        }
        handle.unsubscribe();
      });
  }

  private getFloat(): string {
    const value = this.element.value;
    const float = this.replace(value);
    // Only perform an action when a floating point value exists and there are
    // no errors, otherwise leave the erroneous value to be fixed manually by
    // ignoring an action
    if (value && float && this.checkIsValid()) {
      return float;
    }
  }

  private checkIsValid(): boolean {
    return !this.ngControl.control.errors;
  }

  private replace(value: string): string {
    return value.replace(/[^\d\.]+/g, '');
  }

  private format(value: string) {
    return this.decimalPipe.transform(value, '1.2-2');
  }

}

好的,明白了。不是一个可怕的修复,但似乎它可能更优雅......总是征求建议。

private onInitFormatHandler: Subscription; // <-- ADDED HANDLER AS MEMBER VARIABLE INSTEAD

@HostListener('focus', ['$event'])
onFocus(event: KeyboardEvent) {

  // Remove initial formatting subscription since no patch of the value has
  // occurred, and is no longer likely to occur if the user is actively
  // applying focus
  // ---
  // NOTE: Not unsubscribing causes formatting to occur on dynamically added
  // fields on the first change of the input value prior to blur
  if (!this.onInitFormatHandler.closed) { // <-- ADDED CHECK AND EXTRA UNSUBSCRIPTION
    this.onInitFormatHandler.unsubscribe();
  }

  const float = this.getFloat();
  if (float) {
    this.element.value = this.replace(float);
  }
}

public ngOnInit() {
  this.formatFloat();
}

/**
 * Format the input value only once after the initial form response has
 * patched the model.
 * ---
 * NOTE: Format handler is stored and unsubscribed either on valueChange, or
 * if focus is applied to the field, whichever occurs first.
 */
private formatFloat() {
  this.onInitFormatHandler = this.ngControl.valueChanges // <-- UPDATED HANDLER TO BE MEMBER VARIABLE
    .subscribe((value: string) => {
      const float = this.getFloat();
      if (float) {
        this.element.value = this.format(value);
      }
      this.onInitFormatHandler.unsubscribe(); // <-- UPDATED HANDLER TO BE MEMBER VARIABLE
    });
}