ngModelChange Angular/Typescript 上的游标结束问题

Issue with cursor going to the end on ngModelChange Angular/Typescript

我的 HTML 输入字段和使用 ngModelChange 的打字稿组件有问题。我希望能够在任何需要的地方编辑输入值。

例如:

我知道这是一个已知问题,可以通过使用 setSelectionRange 重新设置光标来解决,但是这没有用,因为即使我使用 setSelectionRange(selectionStart, selectionEnd) 和正确的光标值, ngModelChange,会将光标放回末尾。

我还有一个正则表达式,它在每​​两位数字后应用冒号。

尽管这是我的代码,但我还提供了一个 stackblitz 供您使用:https://stackblitz.com/edit/angular-ivy-adynjf?file=src/app/app.compone

这是我的输入字段:

<input
  id="value"
  type="text"
  [ngModel]="changedValue"
  (ngModelChange)="formatAndChange($event)"
/>

我的部分组件:

export class AppComponent {
  public changedValue: String = "00:00:00";

  public formatAndChange(inputValue: string) {
    this.changedValue = inputValue;

    if (inputValue.length > 8) {
      inputValue = inputValue.substr(0, 8);
    }
    let unformat = inputValue.replace(/\D/g, "");
    if (unformat.length > 0) {
      inputValue = unformat.match(new RegExp(".{1,2}", "g")).join(":");
    }

    this.changedValue = new String(inputValue);
  }
}    

基本上我的问题是,如果我们想要它,应该如何使用这个结构:值会在用户键入时更改并格式化(我们添加冒号以便格式正确),以及光标保持原位(ngModelChange 不会更改光标位置,或者至少我可以 return 到原来的位置)

欣赏。 谢谢!!

这不太正确:

even if I used the setSelectionRange(selectionStart, selectionEnd) with the correct value of the cursor, the ngModelChange, would put the cursor back to the end.

只要通过 JavaScript 更新值,浏览器 就会将光标放在输入字段的末尾 。与 Angular.

无关

让我们看看当您在输入字段中键入内容时会发生什么。这是一个非常明确的序列:

  1. ngModelChange 触发;
  2. formatAndChange 运行并更新 changedValue;
  3. Angular 的变化检测运行(formatAndChange 方法此时已完成);
  4. Angular 更新模板中的值,从而更新传递给 ngModel;
  5. 的值
  6. ngModel安排一个微任务(我会在答案的最后解释),它会更新实际的输入元素值。

请注意,当 ngModel 更新时,ngModelChange 甚至 都不会被触发。

如果你试图在 formatAndChangesetSelectionRange,它永远不会起作用,因为这就是会发生的事情:

  1. changedValue已更新;
  2. 光标位于输入字段中的预期位置;
  3. ngModel 并随后更新输入值,将光标置于输入的末尾。

要使此工作正常,您需要在 输入值更新后调用 setSelectionRange - 所以至少要在更改检测完成后调用微任务。这是更新后的代码(请注意,由于数字之间有冒号,这不能完全正确地工作,但我相信您可以自己弄清楚):

import {
  AfterViewChecked,
  Component,
  ElementRef,
  ViewChild
} from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewChecked {
  public changedValue: String = '00:00:00';

  private valueUpdated: boolean;

  private selectionStart: number;

  private selectionEnd: number;

  private selectionDirection: 'forward' | 'backward' | 'none';

  @ViewChild('input')
  private inputRef: ElementRef<HTMLInputElement>;

  public formatAndChange(inputValue: string) {
    console.log(inputValue);
    const oldChangedValue = this.changedValue;
    this.changedValue = inputValue;

    if (inputValue.length > 8) {
      inputValue = inputValue.substr(0, 8);
    }
    let unformat = inputValue.replace(/\D/g, '');
    if (unformat.length > 0) {
      inputValue = unformat.match(new RegExp('.{1,2}', 'g')).join(':');
    }
    console.log(inputValue);

    this.changedValue = new String(inputValue);

    this.valueUpdated = oldChangedValue !== this.changedValue;

    if (this.valueUpdated && this.inputRef.nativeElement) {
      const element = this.inputRef.nativeElement;
      this.selectionStart = element.selectionStart;
      this.selectionEnd = element.selectionEnd;
      this.selectionDirection = element.selectionDirection;
    }
  }

  // This lifecycle hook is called after change detection is complete for this component
  ngAfterViewChecked() {
    // This method is called VERY often, so we need to make sure that we only execute this logic when truly necessary (i.e. the value has actually changed)
    if (this.valueUpdated && this.inputRef.nativeElement) {
      this.valueUpdated = false;

      // This is how you schedule a microtask
      Promise.resolve().then(() => {
        // Make sure you update this to deal with colons
        this.inputRef.nativeElement.setSelectionRange(
          this.selectionStart,
          this.selectionEnd,
          this.selectionDirection
        );
      });
    }
  }
}

微任务

微任务基本上是一些代码,在当前调用堆栈清空后执行。 Javascript 的任务和微任务是 JavaScript 引擎的核心,它们实际上并不那么容易掌握,但理解起来非常有用。

我不知道为什么 Angular 开发人员决定更新微任务中的输入值,一定有他们的原因。