[英]Issue with cursor going to the end on ngModelChange Angular/Typescript
I'm having an issue with my HTML input field and the typescript component that is using ngModelChange.我的 HTML 输入字段和使用 ngModelChange 的 typescript 组件有问题。 I want to be able to edit the input value wherever I need to.我希望能够在任何需要的地方编辑输入值。
For example:例如:
I know this is a known issue that could be fixed with re-setting the cursor using setSelectionRange, however that has not worked since 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.我知道这是一个已知问题,可以通过使用 setSelectionRange 重新设置 cursor 来解决,但是这没有用,因为即使我使用 setSelectionRange(selectionStart, selectionEnd) 和 cursor 的正确值,ngModelChange 也会把cursor 回到最后。
I also have a Regex that applies the colon after each two digits.我还有一个正则表达式,它在每两位数字后应用冒号。
Although this is my code, I also provide a stackblitz where you can play with it: https://stackblitz.com/edit/angular-ivy-adynjf?file=src/app/app.compone虽然这是我的代码,但我还提供了一个 stackblitz,您可以在其中使用它: https://stackblitz.com/edit/angular-ivy-adynjf?file=src/app/app.compone
This is my input field:这是我的输入字段:
<input
id="value"
type="text"
[ngModel]="changedValue"
(ngModelChange)="formatAndChange($event)"
/>
and part of my component:和我的一部分:
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);
}
}
Basically my question is, how is this structure supposed to be used if we want it all: the value changes and is formatted while the user is typing (we add the colon so the format is correct), and the cursor stays in place (ngModelChange does not change the cursor placement or at least I can make it return to where it was)基本上我的问题是,如果我们想要它,应该如何使用这个结构:值在用户键入时更改并格式化(我们添加冒号以便格式正确),并且 cursor 保持不变(ngModelChange不会改变 cursor 的位置,或者至少我可以让它回到原来的位置)
Appreciate it.欣赏它。 Thanks!!谢谢!!
This is not quite correct:这不太正确:
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.即使我使用 setSelectionRange(selectionStart, selectionEnd) 和 cursor 的正确值,ngModelChange 也会将 cursor 放回末尾。
The cursor is placed at the end of the input field by the browser whenever the value is updated via JavaScript. Nothing to do with Angular.每当值通过 JavaScript 更新时,浏览器会将 cursor 放在输入字段的末尾。与 Angular 无关。
Let's take a look at what happens when you type something in the input field.让我们看看当您在输入字段中键入内容时会发生什么。 This is a very well-defined sequence:这是一个非常明确的序列:
ngModelChange
fires; ngModelChange
触发;formatAndChange
runs and updates changedValue
; formatAndChange
运行并更新changedValue
;formatAndChange
method has completed by this point); Angular 的变更检测运行(此时formatAndChange
方法已经完成);ngModel
; Angular 更新模板中的值,从而更新传递给ngModel
的值;ngModel
schedules a microtask (I'll explain at the end of the answer), which updates the actual input element value. ngModel
安排了一个微任务(我会在答案的最后解释),它会更新实际的输入元素值。 Note that when ngModel
is updated, ngModelChange
is not even fired.请注意,当ngModel
更新时,甚至不会触发ngModelChange
。
If you were trying to setSelectionRange
inside formatAndChange
, it was never going to work, because this is what would happen:如果您尝试在setSelectionRange
中formatAndChange
,它永远不会起作用,因为这是会发生的事情:
changedValue
is updated; changedValue
被更新;ngModel
and subsequently the input value are updated, throwing the cursor to the end of the input. ngModel
和随后的输入值被更新,将 cursor 扔到输入的末尾。 To get this working you need to call setSelectionRange
after the input value is updated - so at least as late as a microtask after change detection is complete.为了使这个工作正常,您需要在输入值更新后调用setSelectionRange
- 所以至少在更改检测完成后调用微任务。 Here's the updated code (note that this does not work exactly right due to colons between the digits, but I am sure you can figure that out by yourself):这是更新后的代码(请注意,由于数字之间有冒号,这并不完全正确,但我相信您可以自己弄清楚):
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
);
});
}
}
}
A microtask is basically some code, that is executed after the current call stack empties.微任务基本上是一些代码,在当前调用堆栈清空后执行。 Javascript's tasks and microtasks are at the very core of the JavaScript engine, and they are actually not that simple to grasp, but very useful to understand. Javascript 的任务和微任务是 JavaScript 引擎的核心,它们实际上并不那么容易掌握,但理解起来非常有用。
I do not know why Angular developers decided to update the input value inside a microtask, must've had their reasons.我不知道为什么 Angular 开发人员决定更新微任务中的输入值,一定有他们的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.