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 版本 ~7.1.0 想要安装版本 4
升级到 9.0 和 angular 7 后修复 angular-redux/store