The $scope.$watch function does not activate with each individual change

Yesterday, I realized that in my angularJS 1.4.8 application, the $scope.$watch doesn't trigger on every change causing a bug to occur.

Is there a way to make it work on every change immediately? For example, in this code snippet, I want the function in watch to be triggered upon every change in the message:

(function(){

  angular.module('myApp', [])
  .controller('myAppController', myAppController)

  function myAppController($scope){
    console.log('controller loading !');
    $scope.message = 'message1';

    $scope.$watch('message', function(newMessage){
      console.log('newMessage', newMessage)
    });

    function changeMessage(){
      $scope.message='hi';
      $scope.message='hi12';
    }

    changeMessage(); 
  }

})();

The console output will be:

controller loading !
newMessage hi22

Plunker link available: https://plnkr.co/edit/SA1AcIVwr04uIUQFixAO?p=preview

Edit: I am curious if there are any alternative methods besides wrapping the change with a timeout and using scope.apply. In my original code, there are multiple instances where I change the scope property and I would like to find a solution that does not require this for every change.

Answer №1

The reason for this occurrence is that the watch function will only trigger if the value is modified "in between" digest loops.

In your case, your function is altering the message value within the same function, which means it will all be processed in a single digest loop. Therefore, when Angular moves on to the next loop, it will only recognize the last changed value, which in this scenario is hi22.

Here's an informative article that explains this behavior in detail.

Answer №2

Revamp your changeMessage method to incorporate the $scope.$apply function. This will guarantee that any modifications made are accurately displayed and Angular is notified of the alterations made to the variable.

changeMessage() {
   setTimeout(function () {
        $scope.$apply(function () {
          $scope.message = "Timeout called!";
        });
    }, 2000);
}

Answer №3

When the value is changed within the same digest cycle, the watcher does not trigger and retains the last value. However, by using $timeout, we can change the value of $scope.message in the next digest cycle, allowing the watcher to capture the change as expected.

Here is a simple test example:

 $scope.$watch(function(){
  console.log('trigger');
  return $scope.message;
},
  function(newMessage){
  console.log('newMessage', newMessage)
});

function changeMessage(){
  $scope.message='hi';

  $timeout(function(){
    $scope.message='hi12';
  });      
}

Output:

controller loading !
 trigger
 newMessage hi
 trigger
 trigger
 newMessage hi12
 trigger

Answer №4

It is unnecessary to wrap changeMessage in both setTimeout and $apply simultaneously. If you want to delay execution, simply use:

function changeMessage(){
    $timeout(function(){
        $scope.message = 'message';
    }/* or specify time here, it doesn't matter */);
}

Alternatively, you can use:

function changeMessage(){
    $scope.message = 'message';
    $scope.$apply();
}

Both approaches eventually trigger $rootScope.$digest. For more details, visit:

Answer №5

$watch() is only triggered between every $digest().

For a detailed explanation about the $apply() and $digest()

In this scenario, the $scope.message is being updated in the current $digest() cycle.

To change this behavior, you can apply each new value to the $scope using $apply(), as suggested by @Ajinkya. However, setting a timeout of 2000ms does not always guarantee that it will be executed after the $digest(). Angular provides a built-in timeout function which can be used instead.

(function(){
  
  angular.module('myApp', [])
  .controller('myAppController', myAppController)
  
  function myAppController($scope, $timeout){
    console.log('controller loading !');
    $scope.message = 'message1';
    
    $scope.$watch('message', function(newMessage){
      console.log('newMessage', newMessage)
    });
    
    function changeMessage(){
    setTimeout(function () {
        $scope.$apply(function () {
          $scope.message='hi12';
        });
    }, 2000);
      
    }
    
    changeMessage(); 
  }
  
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp" ng-controller="myAppController"></div>


Solution

The most effective approach is to utilize the built-in $timeout function without specifying the time in milliseconds.

By doing this, Angular ensures that the $timeout will always run after the latest $digest(). Additionally, there is no need to use $scope.$apply() as the $timeout already triggers a $digest(), whereas $scope.$apply() initiates a new $digest() cycle manually.

(function(){
  
  angular.module('myApp', [])
  .controller('myAppController', myAppController)
  
  function myAppController($scope, $timeout){
    console.log('controller loading !');
    $scope.message = 'message1';
    
    $scope.$watch('message', function(newMessage){
      console.log('newMessage', newMessage)
    });
    
    function changeMessage(){
        $timeout(function () {
            $scope.message='hi12';
        });
      
    }
    
    changeMessage(); 
  }
  
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

    <div ng-app="myApp" ng-controller="myAppController"></div>

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

The debate between classes and data attributes in terms of auto field initialization

A Brief Background In the realm of javascript/jQuery, I've crafted a method that traverses through various fields and configures them based on their type - be it dropdowns, autocomplete, or text fields... The motivation behind this is my personalize ...

ng-select will solely output the term 'collection'

i am having an issue with a ng-select in my contact form. everything is being received correctly except for the value of the ng-select. Instead of getting the selected option from the ng-select, the system just returns the word "array". Below is the port ...

Displaying JSON data within a div section using Ajax and jQuery is not possible

I have generated a JSON in a specific format from an external PHP file: [ { "title": "Welcome!", "description": "The world has changed dramatically..", "image": "img/strawberry-wallpaper.jpg" } ] I am trying to use this data to populate a par ...

Execute function when image finishes loading (Internet Explorer)

Is there a way to execute a method once an image is completely loaded, considering that the .load function doesn't work for images in Internet Explorer? Below is the code snippet in question: <img ref="image" :src="src" :alt="alt" @load=" ...

Increasing the size of text in CSS with the use of ":hover" and then causing it to return to its original smaller size

Allow me to explain my goal here. I have text displayed on my website that I would like to enlarge when the user hovers over it and then shrink back down when they move their cursor away. The issue I'm facing is that after enlarging the text using t ...

"Using JavaScript to toggle a radio button and display specific form fields according to the selected

Currently, I am attempting to show specific fields based on the selected radio button, and it seems like I am close to the solution. However, despite my efforts, the functionality is not working as expected and no errors are being displayed. I have define ...

The information does not display in the AngularJS-generated table

Struggling with AngularJS directives? Let's take a look at the code: <div ng-controller="Controller"> <table> <thead> ....... </thead> <tfoot> ....... </tfoot> <tbody> < ...

AngularJS - Controller not recognizing state name parameter

I'm a newcomer to angular and struggling to understand why this specific issue is occurring. Interestingly, when I use {{$state.current.name}} in my view, everything works as expected. However, the problem arises when I attempt to pass it to my contr ...

Monitor form completion status in a specified div in real time

<div class="well"> <div class="panel-title> <span class="fa fa-caret-right" ng-if="!showGeneral"></span> <span class="fa fa-caret-down" ng-if="showGeneral"></span> </div> <label>GENERAL</labe ...

Is there a bug in Safari 8.0 related to jQuery and backslashes?

I am using Mac OS 10.10 Yosemite and Safari 8.0. Attempting to read an XML (RSS) file: <content:encoded>bla bla bla</content:encoded> The Javascript Ajax method I am using is: description:$(valeur).find('content\\:encoded&apo ...

Use Javascript to create commands and variables specific to the domain or site of a webpage

Currently, I have implemented the code below to fetch the latest tweet. However, I am looking for a way to customize the TWITTERUSERNAME based on the domain where the template is being used. This template is shared across multiple domains. <script type ...

What is the best way to trigger a refresh in Next.js React component?

Current Tech Stack Versions Next.js : 14.0.3 React : 18.0.2 // TestClientComponent.tsx "use client"; import { IResident } from "@interface/resident.types"; import { getResidents } from "@models/resident.service"; import { So ...

The sendKeys() method in Protractor is failing due to the element being hidden/not

Hi there! I am currently new to automated testing with protractorJS for an angularJS homepage. While the code I've written so far has been successful, I'm facing an issue where I'm unable to input keys into the search field. After running th ...

Tips for verifying the rendered view post data retrieval from an API in Vue JS

Having trouble retrieving data from the API using Vue JS and printing the page? After fetching the data, some elements may not render completely when trying to print, resulting in blank data being displayed. While using a setTimeout function may work for s ...

Using getJSON to return key/value pair from local host URL in JSFiddle - A step-by-step guide

After following a tutorial on building an API using Python, Flask, SQLite, and SQLAlchemy, I have successfully tested the connection by hitting the localhost address in my browser. Now, I am curious if it is possible to test this connection and see the des ...

A different approach to fixing the error "Uncaught (in promise) TypeError: fs.writeFile is not a function" in TensorFlow.js when running on Chrome

I've been attempting to export a variable in the TensorFlow posenet model while it's running in the Chrome browser using the code snippet below. After going through various discussions, I discovered that exporting a variable with fswritefile in t ...

Select elements using jQuery in events while excluding others

I need to prevent form submission when the user presses Enter, except for three specific inputs. To prevent form submission on Enter key press, I can use this code: $(document).keydown(function(event){ if(event.keyCode == 13) { event.preventDefault(); re ...

Using InputAdornment with MUI AutoComplete causes the options list to disappear

I created a custom AutoComplete component with the following structure: <Autocomplete freeSolo size="small" id="filter-locks-autocomplete" options={json_list ? json_list : []} groupBy={(option) => option.lock.building} ...

Is there a way to ensure that the ng-repeat finishes before the parent directive is executed?

Is there a way to ensure that the extend-item directive runs only after the ng-repeat has finished rendering in the DOM? In the provided example, the ^ symbol is added only to the static ul elements because the dynamic ul elements generated by ng-repea ...

There seems to be an issue with updating the ng-model properly in angular-ui-tinymce

I have encountered a problem where I am attempting to add a DOM node when a custom button is clicked, but the model associated with the textarea is not being properly updated. To illustrate the issue, I have created a plunker as a demo. [https://plnkr.co ...