【问题标题】:Angular 2 input with pipe not updating view (change detection not working)带有管道的 Angular 2 输入未更新视图(更改检测不起作用)
【发布时间】:2017-09-19 02:25:11
【问题描述】:

我创建了一个简单的管道,我将其应用于文本输入。正则表达式有效 - 但有些奇怪。控制台日志正确显示新值(已删除非字母数字),但在浏览器中,输入字段在我输入好字符之前不会更新。所以输入'123!!!!A'会显示感叹号,直到输入A然后它们消失。为什么?

我是这样使用管道的:

<input type="text" class="form-control" [ngModel]="name | inputFormat" (ngModelChange)="name=$event">

管道是

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'inputFormat'
})
export class InputFormatPipe implements PipeTransform {

    transform(value: any): any {
    value = value.replace(/[^a-z0-9]/gi, ''); 
    console.log('new value: '+value);
    return value;
  }

}

我怀疑更改检测不起作用 - 但不确定如何解决此问题。

【问题讨论】:

  • 你想用这段代码解决什么任务?我会说它不应该工作。如果您希望在输入中以某种特定方式格式化您的值,那么您可能需要实现 ControlValueAccessor... 但这只是我的猜测。请提供您想要达到的目标的详细信息。
  • 我正在编写一个通用管道,它将删除受限制的字符、更改大小写、用其他字符替换一些字符等。为了简单起见,我已将我的问题简化为最简单的情况来演示错误
  • 无论如何,这不是要走的路。如果您需要执行此类操作,那么 ControlValueAccessor 是您最好的朋友。它将允许您直接订阅输入上的不同事件并在您的值到达模型之前处理它们。例如,您可以通过在 keydown 事件中拒绝不需要的字符来避免更正您的值,甚至在它们被输入获取之前。只需查看角度源中的 DefaultValueAccessor 并添加一些创造性思维。这不是一门火箭科学,虽然它需要你提供一些代码,但它肯定是更好的方法。
  • 有没有可以参考的例子?早些时候有人告诉我,PIPE 是实现这一目标的正确方法(我使用的是 onchange 指令)。除此之外,我很好奇为什么这不起作用以及如何解决它!
  • 管道仅用于格式化值以便将它们显示为静态文本,即在 span/div 或类似的东西中。不用于输入。我无法告诉你为什么它不起作用,老实说,我不想深入研究这个问题,因为我看不到知识中有任何价值,为什么某些东西在使用不当时不起作用。 :) 例如,我会尝试为您制作一些东西,请给我几分钟。

标签: angular


【解决方案1】:

好的,下面是一个示例,说明如何在设计应处理的地方处理用户输入。

test-input.directive.ts

import { Directive, ElementRef, Renderer2, forwardRef, HostListener } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const TEST_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TestInputDirective),
  multi: true
};

@Directive({
  selector: 'input[appTestInput]',
  providers: [TEST_VALUE_ACCESSOR]
})
export class TestInputDirective implements ControlValueAccessor {

  onChange = (_: any) => { };
  onTouched = () => { };

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

  // this gets called when the value gets changed from the code outside
  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);
  }

  // just as an example - let's make this field accept only numbers
  @HostListener('keydown', ['$event'])
  _handleKeyDown($event: KeyboardEvent): void {
    if (($event.keyCode < 48 || $event.keyCode > 57) && $event.keyCode !== 8) {
      // not number or backspace, killing event
      $event.preventDefault();
      $event.stopPropagation();
    }
  }

  @HostListener('input', ['$event'])
  _handleInput($event: KeyboardEvent): void {
    // this is what we should call to inform others that our value has changed
    this.onChange((<any>$event.target).value);
  }
}

只需将此指令声明添加到您的模块:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { TestInputDirective } from './test-input.directive';

@NgModule({
  declarations: [
    AppComponent,
    TestInputDirective // <-- you'll need this
  ],
  imports: [
    BrowserModule,
    FormsModule        // <-- and this
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

然后你可以像这样在模板中使用它:

app.component.html

<input type="text" appTestInput [(ngModel)]="name">
<div>{{name}}</div>

这确实比使用管道的代码要多一些,但这是处理用户输入的正确方法,尤其是当它应该以某种方式进行预处理时。

【讨论】:

  • 这适用于简单地杀死击键,但是如果我想将键 'a' 更改为 'A' 怎么办?
  • 然后预处理传递给 onChange() 的值。您可以在输入上处理您想要的任何事件。例如,我省略了 'blur' 事件,我有时用它来重新格式化用户输入的值,特别是在时间上加零(比如将 '9' 变成 '09:00')或转换字符串为大写。你可以在这里做很多事情。我什至更改了此输入绑定到的数据类型。我的一个项目中的日期输入直接绑定到日期值,我不必从输入中的字符串值进行单独的转换。很方便。
  • 但是我需要在用户键入时更改字符(例如,用 . 替换 / 和 \ )所以这就是我使用管道的原因。我不想更改 blur 的值,因为这可能会让用户感到沮丧。必须有一种方法可以在用户键入时更改和/或删除键...
  • 好吧,用管道实现它实际上会在每次击键后将光标位置重置到字符串的末尾,而不管它是在哪里输入的......所以如果用户尝试在中间输入一些东西他们会变得愤怒。 ;) 除此之外,没有简单的方法来“改变”事件。您需要做的是取消原始事件, setTimeout() 等待其处理完全完成,然后生成您自己的事件,在输入上触发它......并祈祷这一切正常,因为浏览器可以很容易地考虑这样的技巧可疑的东西并阻止它。
  • 所以,你真正想做的是确保你在模型和屏幕上得到正确的值。 ControlValueAccessor 是专门为这类东西设计的。使用管道,您无法控制输入元素及其原始事件,并且在击键发生和管道启动之间的模型中会出现间歇性错误值。ControlValueAccessor 使您能够保证 比模型值总是是正确的。
猜你喜欢
  • 1970-01-01
  • 2017-01-23
  • 1970-01-01
  • 2017-09-07
  • 1970-01-01
  • 1970-01-01
  • 2019-11-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多