Angular 7 - ControlValueAccessor - 修剪绑定到表单的输入值

Posted

技术标签:

【中文标题】Angular 7 - ControlValueAccessor - 修剪绑定到表单的输入值【英文标题】:Angular 7 - ControlValueAccessor - Trim input values that are binded to a form 【发布时间】:2019-09-01 17:04:06 【问题描述】:

我们在网页上有一个输入字段,必须在用户输入该数据的同时进行修剪。由于输入绑定到 Angular 表单,因此表单中的值也必须被修剪。 我使用 Angular 7

import 
  Directive,
  ElementRef,
  forwardRef,
  HostListener,
  Input,
  Renderer2
 from "@angular/core";
import 
  ControlValueAccessor,
  NG_VALUE_ACCESSOR
 from "@angular/forms";


@Directive(
  selector: "[ebppInputTextTrimmer]",
  providers: [
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => InputTextTrimmerDirective),
    multi: true
  ]
)
export class InputTextTrimmerDirective implements ControlValueAccessor 
  @Input() prevVal: string;

  @Input() isTrimEnabled: boolean;

  onChange = (_: any) => 
  

  onTouched = () => 
  

  constructor(
      private _renderer: Renderer2,
      private _elementRef: ElementRef) 
  

  writeValue(value: any): void 
    const normalizedValue = value == null ? "" : value;
    this._renderer.setProperty(this._elementRef.nativeElement, "value", normalizedValue);
  

  registerOnChange(fn: (_: any) => void): void 
    this.onChange = fn;
  

  registerOnTouched(fn: () => void): void 
    this.onTouched = fn;
  

  setDisabledState(isDisabled: boolean): void 
    this._renderer.setProperty(this._elementRef.nativeElement, "disabled", isDisabled);
  

  @HostListener("input", ["$event.target.value"])
  handleInput(inputValue: any): void 
    let valueToProcess = inputValue;
    if (this.isTrimEnabled) 
      valueToProcess = inputValue.trim();
    

    this.onChange(valueToProcess);
    // set the value that is trimmed in the view
    this._renderer.setProperty(this._elementRef.nativeElement, "value", valueToProcess);
  


显示的代码对我来说很好用。我想知道是否有更简单的解决方案。

【问题讨论】:

【参考方案1】:

您可以像下面这样创建自定义值访问器作为指令:

const TRIM_VAL_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TrimValAccessor), multi: true);

@Directive(
  selector: 'input[trimval]',
  host:  '(keyup)': 'valOnChange($event.target)' ,
  providers: [ TRIM_VAL_ACCESSOR ]
)
export class TrimValAccessor extends DefaultValueAccessor 
  onChange = (_) => ;
  onTouched = () => ;

  constructor(private renderer:Renderer) 
  

  writeValue(value:any):void 
    if (value!=null) 
      super.writeValue(value.toString().trim());
    
  

  valOnChange(el) 
    let val = el.value.trim();
    this.renderer.setElementProperty(el, 'value', val);
    this.onChange(val);
  

在模块中提供参考:

declarations: [ TrimValAccessor ]

或在这样的组件中

@Component(
  (...)
  template: `
    <input type="text" trimval/>
  `,
  directives: [ TrimValAccessor ]
)

在输入标签中使用来修剪值

<input type="text" trimval/>

【讨论】:

【参考方案2】:

我尝试通过在父窗体中收听 control.valueChnages observable 并在那里修剪和设置值来解决这个问题。

在您的控件值访问器类中有一个方法,将其注册为 onChange 方法。

onChange () ;

registerOnChange(fn) 
  this.onChange = fn

让你的输入框像:

<input type="text" [value]="value" (input)="onChange($event.target.value)">

在您定义表单的父组件中。

ngOnInit() 
  this.parentForm = new FormGroup(
    name: new FormControl(''),
    age: new FormControl(12)
  )

  this.valueChangesSub = this.parentForm.get('age').valueChanges.pipe(
    debounceTime(50),
    switchMap(newVal => of(newVal))
  ).subscribe((newVal) => 
    this.parentForm.get('age').setValue(newVal, emitEvent: false);
    this._cdr.detectChanges();
    this.parentForm.get('age').setValue(newVal.trim(), emitEvent: false);
  )

假设,age 是控件值访问器类的表单控件。您会注意到我设置了两次值,这是为了让 Angular 与更改检测一起工作,并在修剪完成时更新您的输入视图。

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

【讨论】:

谢谢。我想这也行。我忘了写我们有一个非常通用的数据结构。我们以编程方式将数据添加到 from。所以这个解决方案不适合我们。【参考方案3】:

您可以将实现简化为

@Directive(
  selector: "[ebppInputTextTrimmer]"
)

export class InputTextTrimmerDirective 
  @Input("ebppInputTextTrimmer") isTrimEnabled = false;
  @Output() ngModelChange = new EventEmitter();

  constructor(
      private _renderer: Renderer2,
      private _elementRef: ElementRef) 
  

  @HostListener("input", ["$event.target.value"])
  handleInput(inputValue: any): void 
    if (this.isTrimEnabled) 
      const valueToProcess = inputValue.trim();
      this._renderer.setProperty(this._elementRef.nativeElement, "value", valueToProcess);
      this.ngModelChange.emit(valueToProcess);
    
  


并通过使用有条件地将其添加到模板中

<input
[(ngModel)]="filter[columnConfig.key]"
...
[ebppInputTextTrimmer]="isAutoTrim(columnConfig)"
...
>

【讨论】:

【参考方案4】:

如果您使用Reactive Forms,您可以valueChanges 订阅值,然后手动修剪并再次修补值。

一个完整的例子是:


export class SomeComponent implements OnInit, OnDestroy 


  trimSubscription: Subscription;

  ngOnInit(): void 


    this.trimSubscription = this.materialForm.valueChanges
      .pipe(
        debounceTime(1000),
      )
      .subscribe((formValues) => 

        let trimmedValues = Object
          .keys(formValues)
          .reduce((previous, currentKey) => (
            ...previous,
            [currentKey]: typeof formValues[currentKey] == 'string' ? formValues[currentKey].trim() : formValues[currentKey],
          )
            , );

        this.materialForm.patchValue(trimmedValues);
      );
  

  ngOnDestroy()
    this.trimSubscription.unsubscribe();
  

  materialForm = new FormGroup(
    desc: new FormControl(''),
    descAr: new FormControl(''),
    name: new FormControl('', [Validators.required]),
    nameAr: new FormControl('', [Validators.required]),
    price: new FormControl('', [Validators.required, Validators.min(0)]),
    categoryId: new FormControl('', [Validators.required]),
  );

  // ...


【讨论】:

【参考方案5】:

这不是纯粹的 Angular 方式,但有时它看起来更有效。在提交事件上调用它。它使用 lo-dash 来修剪(IE 还在这里)。

trimInputs() 
    const cssSelector = 'input[type="text"]:not(.no-trim), textarea:not(.no-trim)';
    const inputs: (htmlInputElement | HTMLTextAreaElement)[] = this.el.nativeElement.querySelectorAll(cssSelector) || [];
    inputs.forEach((input) => 
        if (_.isString(input.value)) 
            input.value = _.trim(input.value);
            input.dispatchEvent(new Event('input'));
        
    )

【讨论】:

以上是关于Angular 7 - ControlValueAccessor - 修剪绑定到表单的输入值的主要内容,如果未能解决你的问题,请参考以下文章

Angular 6 / 7“依赖的结果是一个表达式”

已安装 Angular 版本 ~7.1.0 想要安装版本 4

升级到 9.0 和 angular 7 后修复 angular-redux/store

Angular 7 - 未注册的服务人员

Angular JS - 7 - Angular JS 常用指令2

Angular 7 - ControlValueAccessor - 修剪绑定到表单的输入值