ngModelChange Angular/Typescript 上的游标结束问题
Issue with cursor going to the end on ngModelChange Angular/Typescript
我的 HTML 输入字段和使用 ngModelChange 的打字稿组件有问题。我希望能够在任何需要的地方编辑输入值。
例如:
- 原始输入自动填充为“00:00:00”。我想将其编辑为“01:20:00”。
- 用我的键盘将光标 (^) 定位到我需要的位置 0^0:00:00
- 我输入1,结果是“01:00:00^”
- 如果我想加上2,我需要再次移动光标,这对我来说是不可取的。
我知道这是一个已知问题,可以通过使用 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.
无关
让我们看看当您在输入字段中键入内容时会发生什么。这是一个非常明确的序列:
ngModelChange
触发;
formatAndChange
运行并更新 changedValue
;
- Angular 的变化检测运行(
formatAndChange
方法此时已完成);
- Angular 更新模板中的值,从而更新传递给
ngModel
; 的值
ngModel
安排一个微任务(我会在答案的最后解释),它会更新实际的输入元素值。
请注意,当 ngModel
更新时,ngModelChange
甚至 都不会被触发。
如果你试图在 formatAndChange
中 setSelectionRange
,它永远不会起作用,因为这就是会发生的事情:
changedValue
已更新;
- 光标位于输入字段中的预期位置;
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 开发人员决定更新微任务中的输入值,一定有他们的原因。
我的 HTML 输入字段和使用 ngModelChange 的打字稿组件有问题。我希望能够在任何需要的地方编辑输入值。
例如:
- 原始输入自动填充为“00:00:00”。我想将其编辑为“01:20:00”。
- 用我的键盘将光标 (^) 定位到我需要的位置 0^0:00:00
- 我输入1,结果是“01:00:00^”
- 如果我想加上2,我需要再次移动光标,这对我来说是不可取的。
我知道这是一个已知问题,可以通过使用 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.
无关让我们看看当您在输入字段中键入内容时会发生什么。这是一个非常明确的序列:
ngModelChange
触发;formatAndChange
运行并更新changedValue
;- Angular 的变化检测运行(
formatAndChange
方法此时已完成); - Angular 更新模板中的值,从而更新传递给
ngModel
; 的值
ngModel
安排一个微任务(我会在答案的最后解释),它会更新实际的输入元素值。
请注意,当 ngModel
更新时,ngModelChange
甚至 都不会被触发。
如果你试图在 formatAndChange
中 setSelectionRange
,它永远不会起作用,因为这就是会发生的事情:
changedValue
已更新;- 光标位于输入字段中的预期位置;
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 开发人员决定更新微任务中的输入值,一定有他们的原因。