Trouble navigating cursor position on ngModelChange in Angular/Typescript

I'm currently facing an issue with my HTML input field combined with a typescript component utilizing ngModelChange. I am aiming to have the flexibility to edit the input value wherever necessary.

Let's consider this scenario:

  • The original input is pre-filled with "00:00:00". I wish to modify it to "01:20:00".
  • Using my keyboard, I move the cursor (^) to where I want it: 0^ 0:00:00
  • I type '1', and the output shows "01:00:00^"
  • If I try adding '2', I must relocate the cursor again, which is not ideal for me.

I am aware that there is a known workaround involving resetting the cursor position using setSelectionRange. However, my attempts at using setSelectionRange(selectionStart, selectionEnd) with the correct cursor location have been unsuccessful as ngModelChange reverts the cursor back to the end.

Additionally, I have a Regex pattern in place that automatically inserts a colon after every two digits.

While I have shared my code snippet, you can also experiment with it on StackBlitz: https://stackblitz.com/edit/angular-ivy-adynjf?file=src/app/app.compone

This is the structure of my input field:

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

Here is a snippet from 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);
  }
}    

In essence, my query revolves around the optimal usage of this structure. How can we ensure that both the value changes are formatted while the user inputs data (including the insertion of colons for correct formatting), and maintain the fixed cursor position without being affected by ngModelChange?

Your insights are greatly appreciated. Thank you!

Answer №1

Unfortunately, this is not entirely accurate:

Even if I used the setSelectionRange(selectionStart, selectionEnd) with the correct cursor value, ngModelChange would always reset the cursor position to the end.

The browser, independently from Angular, automatically places the cursor at the end of the input field whenever the value is modified via JavaScript.

Let's delve into the specific sequence of events when typing in the input field:

  1. ngModelChange triggers;
  2. formatAndChange function executes and updates changedValue;
  3. Angular's change detection processes (formatAndChange has already finished running);
  4. Angular updates the template values, consequently updating the value passed to ngModel;
  5. ngModel then schedules a microtask (more on this at the end of the answer), which ultimately updates the actual input element value.

Note that when ngModel gets updated, ngModelChange does not trigger at all.

If you attempted to utilize setSelectionRange within formatAndChange, it was bound to fail because of the following cycle:

  1. changedValue gets updated;
  2. The cursor is correctly positioned in the input field;
  3. ngModel and subsequently the input value get updated, causing the cursor to jump back to the end of the input.

To resolve this issue, ensure that you call setSelectionRange after the input value update - essentially after a microtask once the change detection process has concluded. Here's the revised code snippet (note: there may be issues with colons between digits, but you can address that yourself):

(Code example provided)

Understanding Microtasks

A microtask refers to a piece of code that executes after the current call stack empties. The concepts of tasks and microtasks in JavaScript are fundamental aspects of the engine operation, although they might seem complex initially.

The decision by Angular developers to update the input value within a microtask likely stemmed from valid reasons.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Parallax effect overlay for DIV

Planning to give my website a makeover and I'm thinking of adding some parallax effects to make it more engaging. My idea is to have 3 boxes overlapping each other (with the 2nd and 3rd box appearing blurry). These boxes would be placed at the top of ...

Tips for eliminating whitespace from an input field and then updating the field with the trimmed value

Currently, I am working on email validation where users might input empty spaces in the email field. To address this issue, I have implemented a logic to trim the input value using $trim and then re-assign it to the input field. Although everything seems ...

Engage with a specific element within the Document Object Model even in the absence of a 'ref' attribute

Is there a better way to set focus on a button within a React component without using ref? The button is part of a third-party library and not immediately available upon componentDidMount. Currently, I am using setTimeout and querySelector which feels like ...

Upgrade of Angular 2 to rc 5 presents with unresolved peer dependencies

I am looking to update my angular version to rc5 in order to utilize NgModule. Following the directions provided by Angular 2. I have made changes to my package.json dependencies and then executed npm stall in the terminal: ... The results from the ter ...

Guide on scheduling a daily API GET request in a Node.js script for 11:00pm

I am working on a node js application that involves making an AWS API GET call. http://localhost:3000/amazon/api Within this call, I have specified the necessary functionalities. My goal is to automate this call to run everyday at 11:00PM using node js ...

I have created an Express.js application. Whenever I visit a page, I consistently need to refresh in order for the variables to appear correctly

Hello, I'm seeking some assistance. Despite my efforts in searching for a solution, I have not been successful in finding one. I've developed an application using Express.js that includes a basic form in jade. The intention is to display "Yes" i ...

The response from the node is not accurate

In the index.js file, I have the following code snippet: function Answer(value) { this._val = value; } Answer.prototype.get = function get() { return this._value; } var lifeAnswer = new Answer(42); console.log(lifeAnswer.get()); var piAnswer = new ...

Encountering a node-sass problem during npm installation in my Angular project on Mac OS

While attempting to install node_modules for my Angular project on Mac OS, I encountered an issue with node-sass. My Node.js version is v16.13.2 and the node-sass version in the project is ^4.14.1. The package.json files can be viewed in image1 and image2. ...

Error in Charts API - [draw function failed to render for one or more participants]

Currently encountering an error on the client side where one or more participants failed to draw. I am able to successfully output data in the drawVisualization call, indicating that the issue lies within the JavaScript code. Despite following examples, I ...

Color scheme for navigation bar carousel item background color

I have a navigation bar and carousel within the same section. I want to change the background color of both the navigation bar and carousel item when the carousel indicator becomes active. Any suggestions on how to achieve this using a jQuery function? H ...

Error: The update-config.json file could not be located in Protractor

I recently converted my Cucumber tests to TypeScript and started running them with Protractor. When I run the tests from the command-line using the following commands: rimraf cucumber/build && tsc -p cucumber && protractor cucumber/build/p ...

"Encountering a 404 error when submitting a contact form in Angular JS

Looking to set up a contact form for sending emails with messages. Currently diving into Angular Express Node Check out the controller code below: 'use strict'; /** * @ngdoc function * @name exampleApp.controller:ContactUsCtrl * @descripti ...

Update the grand total based on the values of the checkboxes

I have a code snippet that successfully calculates the total value based on the checkboxes that are selected. Each checkbox has a data attribute value, and I want the total to update dynamically as checkboxes are ticked or unticked. Currently, the code ju ...

Concealing specific DIV elements (unfortunately not nested)

Currently, I am dealing with pre-existing code that is automatically generated and needs to adhere to a specific format: <div id="TITLE1"></div> <div id="div-1"></div> <div id="div-2"></div> <div id="div-3"></d ...

Learn how to dynamically load columns and rows from an HTTP call in ag-Grid, allowing for both the column structure and data records to be flexible

My task involves allowing the user to click on a table name which will then render the corresponding data in ag-grid. I am currently using AngularJS 1.x and have tried various methods to achieve this. $scope.gridOptions = {}; $scope.loadTableInGrid = fun ...

Update a JQuery function for an input field following an innerHTML command

Using JQuery to check the file size and name from an html file input field can be a useful tool. The input field in question looks like this: <div id="uploadFile_div"> <input name="source" id="imageFile" type="file" style ="border: 1px solid ...

three.js LambertMeshMaterial

Hello everyone: I've noticed that my models using MeshLambertMaterial are consuming approximately 1.6GB of memory, exceeding the capabilities of a 32-bit Chrome browser. Switching to Basic Material drops the memory consumption to around 456mb, but the ...

Loading data in a Bootstrap datatable can sometimes be a slow process

Hi there, I'm currently using a codeigniter bootstrap theme datatable to retrieve data from my database. However, the loading time is quite slow as it loads all the data at once before converting it into pagination. Is there any way to only load 10 re ...

Passing a function as a parameter to custom hooks does not update the values of variables

Utilizing this custom hook allows me to listen for the window unload event. import { useRef, useEffect } from 'react'; const useUnload = fn => { const cb = useRef(fn); useEffect(() => { cb.current = fn; }, [fn]); ...

Unable to relocate the cursor to an empty paragraph tag

Wow, I can't believe how challenging this issue is. My current project involves implementing the functionality for an enter key in a content editable div. Whenever the user hits enter, I either create a new p tag and add it to the document or split t ...