【问题标题】:Angular 7 - ControlValueAccessor - Trim input values that are binded to a formAngular 7 - ControlValueAccessor - 修剪绑定到表单的输入值
【发布时间】: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);
  }

}

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

【问题讨论】:

    标签: angular angular7 controlvalueaccessor


    【解决方案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'));
                  }
              })
          }
          

          【讨论】:

            猜你喜欢
            • 2019-11-02
            • 2012-09-07
            • 2019-12-02
            • 2021-11-25
            • 1970-01-01
            • 2022-01-27
            • 2019-04-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多