Angular2的keyup事件更新ngModel时光标位置跳到末尾

12

我遇到了一个关于 Angular2 指令的问题,它应该实现以下功能:

  • 检测用户是否输入'.'字符。
  • 如果下一个字符也是'.',则删除重复的'.'并将光标位置移动到'.'字符后面。

我已经做到了上述操作,但是当与 ngModel 结合使用时,每次更新模型时光标位置都会跳到末尾。

输入:

<input type="text" name="test" [(ngModel)]="testInput" testDirective/>

指令:

 import {Directive, ElementRef, Renderer, HostListener, Output, EventEmitter} from '@angular/core';

@Directive({
  selector: '[testDirective][ngModel]'
})
export class TestDirective {


  @Output() ngModelChange: EventEmitter<any> = new EventEmitter();

  constructor(private el: ElementRef,
    private render: Renderer) { }

  @HostListener('keyup', ['$event']) onInputChange(event) {
    // get position
    let pos = this.el.nativeElement.selectionStart;

    let val = this.el.nativeElement.value;

    // if key is '.' and next character is '.', skip position
    if (event.key === '.' &&
      val.charAt(pos) === '.') {

      // remove duplicate periods
      val = val.replace(duplicatePeriods, '.');

      this.render.setElementProperty(this.el.nativeElement, 'value', val);
      this.ngModelChange.emit(val);
      this.el.nativeElement.selectionStart = pos;
      this.el.nativeElement.selectionEnd = pos;

    }
  }
}

这个方法可以工作,但光标的位置会跳到最后。移除以下行:

this.ngModelChange.emit(val);

修复了问题,光标位置正确,但是模型没有更新。

有人能推荐解决方案吗?或者我可能对这个问题采取了错误的方法?

谢谢

3个回答

14

你需要将以下行包装在setTimeout()调用中。原因是你需要给浏览器时间来渲染新值,然后才能更改光标位置,因为光标位置会在新值渲染后重置。不幸的是,这会导致轻微的闪烁,但我找不到其他使其工作的方法。

setTimeout(() => {
  this.el.nativeElement.selectionStart = pos;
  this.el.nativeElement.selectionEnd = pos;
});

1
我可以确认这确实有效,而且闪烁不是很明显。这是一个可接受的解决方案。然而,我希望找到一种不使用setTimeout的解决方案。 - conor
然而,当我打字速度很快时,光标会跳到之前的位置,导致问题。 - Ragul Parani

7

您可以使用setSelectionRange()来更改光标位置,而不需要像接受的答案建议的那样使用setTimeout()和闪烁。

this.el.nativeElement.setSelectionRange(position, position, 'none');

例如:stackblitz


1
好的,这似乎是最好的解决方案。它可以完美地工作,而接受的解决方案则不行(也许在编写时还没有此选项)。如果您打字太快,您可能会克服setTimeout并获得意外的行为。 - António Quadrado
1
由于某种原因无法设置 selectionStartselectionEnd,所以您的建议非常好。不确定为什么其他方法不起作用(它们被正确读取并用作 setSelectionRange() 的参数,但是将值赋给它们未能使愚蠢的标记移动到正确的位置)。 - Konrad Viltersten
1
对我来说在Angular 8中不起作用,我仍然不得不将函数包装在setTimeout()中。 - FullStackOverflowDev
1
@George,我在我的回答中添加了stackblitz,您可以在其中看到这个解决方案在Angular 8中有效。 - metodribic
@metodribic,我已经不再参与该项目,因此无法进行测试,但是它是从指令内部完成的,该指令还包括ngModelChange.emit等其他操作,因此可能与此有关。 - FullStackOverflowDev
我正在使用Angular 6,在Safari浏览器中出现了这个问题。这个解决方案对我没有起作用。 - Ragul Parani

2
我的情况下,一个不使用setTimeout的可接受解决方案是:
  1. Not update the model on keyup
  2. Update the model instead on focusout

    @HostListener('focusout') focusOut() {
      this.ngModelChange.emit(this.el.nativeElement.value);
    }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接