Using Angular's `$watch` function in conjunction with the `array

I have encountered an issue with watchers in Angular and array splice that is exhibiting a strange behavior. To demonstrate the problem, I have created a small demo with logs showing this behavior. You can view the code below or see it in action on this Plunker.

html:

<div class="logs">
    <div><b>LOGS:</b></div>
    <ul ng-repeat="watchEntry in watchersLogs track by $index">
      <li>{{watchEntry}}</li>
    </ul>
    <div><b>Watcher Count:</b> {{watchers.length}}</div>
</div>
<div class="item" ng-repeat="item in list" ng-init="InitItem()">
  <div class="item-title-wrapper">
    <span class="item-title">Item {{ $index + 1 }}</span>
    <button ng-click="AddNewItem()">Add New</button>
    <button ng-click="RemoveItem()">Remove</button>
  </div>
  <div class="field">
    <div>
      CREDIT:
      <input type="number" name="credit" ng-model="item.credit" />
    </div>
    <div>
      DEBIT:
      <input type="number" name="debit" ng-model="item.debit" />
    </div>
  </div>
</div>

js:

var app = angular.module("listApp", []);
app.controller("listController", function($scope) {
  // list with all data:
  $scope.list = [{
    credit: 2000,
    debit: 0
  }, {
    credit: 100000,
    debit: 1000
  }];

  // list containing all watchers:
  $scope.watchers = [];
  // logs containing all watcher event:
  $scope.watchersLogs = [];

  function SetWatcher(itemIndex) {
    $scope.watchers.splice(itemIndex, 0, $scope.$watch("list[" + itemIndex + "]", function(newValues, oldValues, scope) {
      $scope.watchersLogs.push("Item " + itemIndex + " watcher fired!");
    }, true));
  }

  $scope.InitItem = function() {
    // set a watcher for newly create item:
    SetWatcher(this.$index);
  }

  $scope.AddNewItem = function() {
    var newItem = {
      credit: 0,
      debit: 0
    };

    // put the item into the array:
    $scope.list.splice((this.$index + 1), 0, newItem);
  };

  $scope.RemoveItem = function() {
    // destroy the watcher:
    $scope.watchers[this.$index]();
    $scope.watchers.splice(this.$index, 1);
    // remove the item from the list:
    $scope.list.splice(this.$index, 1);
  };
});

css:

.logs {
  margin-bottom: 50px;
}

.item {
  margin-bottom: 25px;
}

As demonstrated above, when initializing or adding a new item to the `$scope.list` array, each item gets assigned a new watcher. The issue arises when using `splice` to add items at specific positions, causing the watcher expression to execute multiple times unexpectedly. This is due to the change in reference of items after inserting a new one. To resolve this, I added a function called `RemoveWatcher` to clear watchers before setting up a new one. This function is called before initializing a new watcher and also within the `RemoveItem` method. By ensuring that the watchers are removed correctly, the issue with multiple executions of the watcher expression has been successfully addressed. I appreciate any further insights or solutions to improve this implementation. Thank you!

...UPDATE...

The issue has been resolved! Special thanks to @Deblaton Jean-Philippe for the assistance. I introduced a new function for removing watchers:

function RemoveWatcher(itemIndex) {
  if ($scope.watchers[itemIndex]) {
    // destroy the watcher:
    $scope.watchers[itemIndex]();
    // remove it from the array as well:
    $scope.watchers.splice(itemIndex, 1);
  }
}

This function is now called before setting up a new watcher to ensure proper clearing. Additionally, it is invoked within the `RemoveItem` method, always removing the last watcher entry from the array due to changes in field order when splicing the `$scope.list` array. The solution seems to be working flawlessly now! Refer to the updated Plunker for verification. :)

Answer №1

The code is following your instructions correctly. It is monitoring the value at the specified index.

If you try to add a new watch to an index that is already being watched, it will result in double triggering.

According to the documentation, $watch() method returns a function. Invoking this function will deactivate the watch.

To address this issue, when adding a new item, make sure to unregister the existing watch first.

You can use a function like this to help with that:

  function SetWatcher(itemIndex) {
    if($scope.watchers[itemIndex]) $scope.watchers[itemIndex]();
    $scope.watchers.splice(itemIndex, 0, $scope.$watch("list[" + itemIndex + "]", function(newValues, oldValues, scope) {
      $scope.watchersLogs.push("Item " + itemIndex + " watcher fired!");
    }, true));
  }

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

"Embracing the Dark: Exploring Quasar Framework with Vue3

Currently, I am utilizing Vue3 in combination with the Quasar Framework. I have successfully implemented an icon that switches my application to dark mode. However, I find the dark mode to be a bit too intense for my liking, and I would like to adjust the ...

Troubleshooting issue with the inability to terminate Electron JS child processes

I have developed an Electron JS script to execute a .exe file. The concept is that when the 'start' button is clicked, the .exe should start as a child process, and when the 'stop' button is clicked, the child process should be terminat ...

Combine and store downloaded stylesheets in the browser into a single file

My task involves transforming a website page along with all its external stylesheets into a single HTML file with inline CSS. This is not for usage in emails, but rather to incorporate styled sections of that page into another website's page. After so ...

What is the best way to create a loop for an array that holds objects?

Having an array with multiple objects raises a question. var profile = [ {"MODE":"Comfort","MONDAY":"09:00:00","TUESDAY":"09:00:00","WEDNESDAY":"09:00:00", "THURSDAY":"09:00:00","FRIDAY":"09:00:00","SATURDAY":null,"SUNDAY":null}, {"MODE":"Eco" ...

What is the best way to create a table by selecting a number at random from a shuffled array of 16 numbers and then incorporating it into the HTML code

I'm trying to display a table in HTML with 16 cells containing numbers from 1 to 16 in a random order. I am having trouble implementing the part where I need to pop a number from the array. Any help would be greatly appreciated :) Here's the cod ...

Utilizing JQuery arrays for optimal implementation in AJAX operations

I'm feeling a bit unsure of my abilities, but I'll give it a shot. My goal is for JQuery to locate the data attribute of all elements with a specific class and then add them to the ajax dataString array. There are 6 elements with that class, eac ...

Finding the way to locate obsolete or deprecated packages in NPM versions

Is there a way to easily identify outdated deep dependencies in the local node_modules folder, separate from the top-level packages? After running the command: npm install with this content in my package.json: "dependencies": { "bluebi ...

Learn how to effectively utilize DataBinder.Eval of repeater in ASP.NET button's OnClientClick JavaScript function

I am facing an issue with my asp.net webform repeater. Within the "ItemTemplate" of the repeater, I have added an asp:button and implemented the following code in the "OnClientClick" event of the button. // Here is a JavaScript function function Confirm ...

What is the best method to verify chrome.runtime.onInstalled in Chrome extension testing?

Is there a way to test the chrome.runtime.onInstalled handler? I am developing a Chrome extension that utilizes chrome storage to store specific data. In my upcoming release, I plan on making changes to the data model in chrome storage. To do this effectiv ...

What is the process for changing to a different page in NativeScript?

Having trouble creating a simple button in NativeScript labeled 'login'? I've created a login component, but when testing the code, it shows an error stating "no known component for element Login." <template> <Page> <TabV ...

Jquery is not working as expected

I am having trouble implementing a jQuery function to show and hide select components. It doesn't seem to be working correctly. Can someone help me identify the issue? <html> <head> <meta charset='UTF-8' /> <script ...

Stopping a setInterval countdown timer in a React component

Recently, I've been working on creating a pomodoro timer that includes a pause option. The timer features both an analogue clock and a digital timer. I have encountered a challenge with the digital timer - while I am able to pause it by clearing the i ...

Utilizing CSS background sizing with jQuery for stunning website aesthetics

In my HTML file, there is a main div container with two nested divs inside it. Each of these inner divs has been assigned separate ids for content placement. By implementing a functionality that changes the background image of the parent div upon clicking ...

I am interested in updating my website's navbar to switch from displaying "login" to "logout" once the user has logged in using

Facing an issue with my navbar logic - I need to switch it from "login" to "logout" when the user is already logged in. Utilizing Nodejs and Ejs templates for this web project, I am currently employing Level 1 Authentication. Initially focusing on the basi ...

Error 404: This page seems to have gone missing. Django and react-router

Struggling to integrate reactjs and react-router (1.x) with my Django project. I'm finding it challenging to make everything work together seamlessly. For more details, you can check out the project on GitHub: https://github.com/liondancer/django-che ...

The functionality of Material UI Slider components becomes less responsive when enclosed and rendered in JSX

Why is the Material UI's Slider not working smoothly when called in JSX as shown below? SliderAndValue.js import { Slider } from "@material-ui/core"; import { useState } from "react"; import "./styles.css"; export const ...

React-dropzone experiencing delays in loading new files for readers

Is there a way to handle conditional responses from an API and assign the desired value to errorMessageUploaded? I'm looking for a solution to receive error messages from the API, but currently, the errormessageupload variable is not being set withou ...

Creating dynamic data for Chart.js to display values

I am attempting to utilize dynamic data using ajax, but my current implementation is not functioning correctly. The ajax response returns data in JSON format via PHP. Here is the code snippet: success: function (result) { result = JSON.parse(r ...

Assessing intricate equations using ast.literal_eval()

I'm currently using Python 3.5 and attempting to transform a large string of strings into a numpy array. Following guidance from Stack Overflow, I put together the following code: import ast import numpy as np str = '["8.4","4.3E-7"]' arr ...

When querying MongoDB, the response time significantly decreases when trying to find a specific phone number amongst 20,000 phone numbers spread across 2 separate documents

At the moment, my MongoDB query is taking up to 5 minutes to search through just 2 documents, each containing 10,000 contacts. I am seeking advice on how to significantly enhance the performance of this query. The goal is to locate a phone number across n ...