如何在设置或获取之前截取FormControl的值?

Posted

技术标签:

【中文标题】如何在设置或获取之前截取FormControl的值?【英文标题】:How to intercept the value of FormControl before setting or getting it? 【发布时间】:2018-01-16 16:21:24 【问题描述】:

这个问题不言自明。我想拦截 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/… - 还包括一个关于验证的注释,它应该适用于所有控件。 【参考方案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" 呢?同样,问题在于可扩展性。此解决方案适用于小型实例。但是,当您有一个包含许多需要转换的字段的表单时,您最终会得到大量的样板代码。

以上是关于如何在设置或获取之前截取FormControl的值?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 FormArray 中设置 FormControl 的值

如何在组件销毁时销毁反应式 FormControl?

如何在角度7中将动态值设置为formControl

如何从 Angular2 FormControl 对象中获取输入字段的名称?

如何将新的 FormGroup 或 FormControl 添加到表单

用JS获取当前页面的URL以及截取其中的字段