I'm having an issue with my HTML input field and the typescript component that is using ngModelChange. I want to be able to edit the input value wherever I need to.
For example:
- original input is auto-filled with is "00:00:00". I want to edit it to "01:20:00".
- with my keyboard I position the cursor (^) where I need it to 0^0:00:00
- I type in 1, the result is "01:00:00^"
- If I want to add the 2, I need to move the cursor again, and that for me is not desirable.
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.
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
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)
Appreciate it. Thanks!!
This is not quite correct:
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.
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;formatAndChange
runs and updateschangedValue
;formatAndChange
method has completed by this point);ngModel
;ngModel
schedules a microtask (I'll explain at the end of the answer), which updates the actual input element value.Note that when
ngModel
is updated,ngModelChange
is not even fired.If you were trying to
setSelectionRange
insideformatAndChange
, it was never going to work, because this is what would happen:changedValue
is updated;ngModel
and subsequently the input value are updated, throwing the cursor to the end of the input.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. 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):Microtasks
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.
I do not know why Angular developers decided to update the input value inside a microtask, must've had their reasons.