【问题标题】:How to intercept the value of FormControl before setting or getting it?如何在设置或获取之前截取FormControl的值?
【发布时间】:2025-11-21 20:05:02
【问题描述】:

这个问题不言自明。我想拦截 FormControl 的 value 属性的传入值,并能够拦截传出值到它所连接的 HTML 控件。

假设我有一个名为“firstName”的 FormControl,我将它连接到一个文本框:

<input type="text" formControlName="firstName" />

默认情况下,当用户在文本框中输入值并提交时,FormControl 的值被设置为文本框中的值。有什么方法可以拦截设置的值并在设置之前对其进行修改?

同样,有没有什么办法可以截取FormControl发送给HTML控件的值呢?例如,如果我将 FormControl 的值设置为某个值,但我想修改显示在文本框中的值。

我知道我可以使用 ngModel 作为表单和控件之间的中介,但是当使用多个控件时会变得很麻烦。我也知道您可以创建自己的控件并实现 ControlValueAccessor,但这也很麻烦,因为我必须为要使用的每个控件创建相应的控件。

有关我为什么要问这个问题的更多信息,请参阅https://github.com/ionic-team/ionic/issues/7121

【问题讨论】:

  • 你能创建 plunker 吗?你想输入3 得到777吗?
  • 基本上,是的。我想要的是用户输入1并将表单控件值设置为0.01。如果表单控件值为 0.01,则文本框应显示 1。
  • 这是一篇关于这个主题的好文章——专门针对 Angular Material。 medium.com/angular-in-depth/… - 还包括一个关于验证的注释,它应该适用于所有控件。

标签: angular ionic-framework ionic2 angular2-forms angular-reactive-forms


【解决方案1】:

似乎验证器在处理 angular 形式 api 的事件方面具有最高优先级(如果您实际调试到 angular 源代码中,您可以看到);因此,根据您的需要,您可以在表单控件值更改之前使用自定义验证器实际获取用户值。

@Directive({
    selector:'[InterceptorDirective]',
    providers: [
    {provide: NG_VALIDATORS, useExisting: forwardRef(() => InterceptorDirective), multi: true}
    ]
}) export class InterceptorDirective implements Validator {
    validate(control: AbstractControl): ValidationErrors | null {
        let value = control.value
        // intercepting
        control.setValue(value, {emitModelToViewChange: false}) // to prevent revalidation we avoid model to view changes
    }
}

甚至你可以使用指令并在里面注入 Ng Control。因为这是纯粹的指令,你可以在它上面使用@hostlistener(而不是angular调用验证器的validate方法),一切都发生在angular form.js进入处理之前。

@Directive({
    selector:'[InterceptorDirective]'
}) export class InterceptorDirective implements AfterContentInit {
  constructor(private control:NgControl) {
  }
  ngAfterContentInit(){
      this.control.valueChanges.subscribe(()=>console.log('valueChanges')) // will be logged after hostlistener handler
  }
  @HostListener('input')
  onInput(event:any){
      console.log('input') // will be logged first
  }

第二种方法可以确保验证者也会收到拦截的值

【讨论】:

    【解决方案2】:

    您可以创建一个指令inject the formControl in the directive,然后使用setValueemitModelToViewChangeemitViewToModelChange 选项。

    像这样:

    @Directive({
      selector: '[valueModifier]',
    })
    export class ValueModifierDirective {
      constructor(private control: NgControl) {}
    
      private setValueOfModel() {
        this.control.control!.setValue('Custom Value', { emitModelToViewChange: false });
      }
    }
    
    

    此解决方案适用于任何类型的表单输入(输入、选择、复选框等)。

    【讨论】:

      【解决方案3】:

      你可以编写一个可重用的指令来拦截来自和进入视图的值:

      const MODIFIER_CONTROL_VALUE_ACCESSOR: Provider = {
        provide: NG_VALUE_ACCESSOR,
        useExisting: forwardRef(() => ValueModifierDirective),
        multi: true,
      };
      
      @Directive({
        selector: '[valueModifier]',
        host: { '(keyup)': 'doSomething($event)' },
        providers: [MODIFIER_CONTROL_VALUE_ACCESSOR],
      })
      export class ValueModifierDirective implements ControlValueAccessor {
      
        @Input() valueModifier: [Function, Function];
      
        private writeToForm;
      
        constructor(public _el: ElementRef) { }
      
        doSomething(event: Event) {
          const viewToForm = this.valueModifier[0];
          this.writeToForm(viewToForm(event.target.value));
        }
      
        registerOnChange(fn: (value: any) => void) {
          this.writeToForm = fn;
        }
      
        registerOnTouched(fn: any) {
          // nothing to do
        }
      
        writeValue(value: any) {
          const formToView = this.valueModifier[1];
          this._el.nativeElement.value = formToView(value);
        }
      }
      

      要使用它,只需将指令添加到您应用 formControlName 的同一元素并传递转换函数:

      @Component({
        selector: 'my-app',
        template: `
        <form [formGroup]="form">
        <input [valueModifier]="[viewToForm, formToView]" name="value" type="text" formControlName="input"  />
        <button (click)="random()">Set Random Value</button>
        </form>
        `,
        styleUrls: ['./app.component.css']
      })
      export class AppComponent {
        form = new FormGroup({
          input: new FormControl(1)
        });
        viewToForm = (text: string) => "toForm" + text;
        formToView = (text: string) => "toView" + text;
      
        constructor() {
          this.form.valueChanges.subscribe(value => console.log(value));
        }
      
        random () {
          this.form.patchValue({
            input: Math.random()
          })
        }
      }
      

      现场示例(Stackblitz):

      https://stackblitz.com/edit/angular-afmkxl?file=src%2Fapp%2Fapp.component.ts

      以上适用于文本输入。我认为您可以为其他类型的输入编写类似的指令。

      【讨论】:

      • 这是一个很好的解决方案,但它不适用于自定义验证器。如果我错了,请纠正我,但是当我尝试过时,验证器函数没有收到指令的“转换”值。
      • 你能举个例子吗?也许订单已经关闭,所以转换发生在验证之后?
      【解决方案4】:

      您可以使用 onBlur 调用函数(即modifyValue()),然后利用 patchValue 修改值:

      <input type="text" onblur="modifyValue()" formControlName="firstName" />
      
      modifyValue() {
          this.form.patchValue({
            firstName: this.form.firstName //modify firstName here
          })
      }
      

      如果可行,您可以创建一个通用函数并将键/值传递给以修补它,而无需创建一堆特定函数

      <input type="text" onblur="modifyValue('firstName')" formControlName="firstName" />
      
        modifyValue(key) {
            this.form.controls[key].patchValue(this.form.controls[key] // modify value here)
        }
      

      【讨论】:

      • 如果我使用 onBlur 手动设置表单值,为什么我需要 formControlName="firstName" 呢?同样,问题在于可扩展性。此解决方案适用于小型实例。但是,当您有一个包含许多需要转换的字段的表单时,您最终会得到大量的样板代码。
      最近更新 更多