$watch function not triggering when model changes

Loading

If your $watch function is not triggering when the model changes in AngularJS, it can be due to several reasons, such as scope hierarchy issues, asynchronous updates, or improper data binding. Below, I’ll explain the possible causes and solutions in detail.


Step 1: Understanding How $watch Works

The $watch() function in AngularJS monitors a scope variable and triggers a callback when the value changes.

Basic Example of $watch

$scope.name = 'Narendra';

$scope.$watch('name', function(newVal, oldVal) {
console.log('Name changed from', oldVal, 'to', newVal);
});

If $scope.name changes, the callback should execute. If it’s not triggering, there’s an issue.


Step 2: Common Issues and Fixes

1. Scope Hierarchy Issue

If $watch() is defined on $scope, but the model is in a different scope, AngularJS may not detect changes.

Example of a Scope Issue

<div ng-controller="ParentCtrl">
<input type="text" ng-model="name">
<div ng-controller="ChildCtrl">
<p>Child: {{ name }}</p>
</div>
</div>
app.controller('ParentCtrl', function($scope) {
$scope.name = "Narendra";
});

app.controller('ChildCtrl', function($scope) {
$scope.$watch('name', function(newVal) {
console.log('Watch triggered:', newVal);
});
});

Here, ChildCtrl gets a new child scope, so $watch('name') in ChildCtrl doesn’t track changes in ParentCtrl.

Solution: Use $scope.$parent

Change $watch in ChildCtrl to:

$scope.$parent.$watch('name', function(newVal) {
console.log('Watch triggered:', newVal);
});

or use $rootScope if the variable is global:

app.run(function($rootScope) {
$rootScope.globalName = "Narendra";
});

app.controller('ChildCtrl', function($scope, $rootScope) {
$rootScope.$watch('globalName', function(newVal) {
console.log('Watch triggered:', newVal);
});
});

2. Object or Array Changes Not Detected

If $watch() is monitoring an object/array, it won’t trigger unless the reference changes.

Example

$scope.user = { name: "Narendra" };

$scope.$watch('user', function(newVal) {
console.log('User changed:', newVal);
});

If you update the property like this:

$scope.user.name = "Krishna";

The $watch won’t trigger because the reference (user) hasn’t changed.

Solution: Use Deep Watching (true as the third parameter)

$scope.$watch('user', function(newVal) {
console.log('User changed:', newVal);
}, true);

or use $watchCollection() for arrays:

$scope.$watchCollection('user', function(newVal) {
console.log('User changed:', newVal);
});

3. Model Updated Asynchronously (Not in Angular Context)

If the model is updated inside a setTimeout, an event listener, or an external API call, AngularJS won’t automatically detect the change.

Example

setTimeout(function() {
$scope.name = "Krishna";
}, 2000);

Here, $watch() won’t trigger because the change happens outside Angular’s digest cycle.

Solution: Use $apply() or $timeout

setTimeout(function() {
$scope.$apply(function() {
$scope.name = "Krishna";
});
}, 2000);

or use Angular’s $timeout (which internally triggers $apply()):

$timeout(function() {
$scope.name = "Krishna";
}, 2000);

4. Using One-Time Binding (::) in the View

If a variable is one-time bound using ::, it won’t update in the UI.

Example

<h1>{{::name}}</h1>

Even if $scope.name changes, the view won’t update, and $watch() won’t trigger.

Solution: Remove ::

<h1>{{ name }}</h1>

5. $watch() Defined After the Model Update

If $watch() is added after the model is modified, it won’t track the previous changes.

Example

$scope.name = "Krishna"; // Change before watch
$scope.$watch('name', function(newVal) {
console.log('Watch triggered:', newVal);
});

The $watch() starts tracking after name is already changed, so it won’t detect this initial update.

Solution: Move $watch() Earlier

Define $watch() before modifying the variable:

$scope.$watch('name', function(newVal) {
console.log('Watch triggered:', newVal);
});
$scope.name = "Krishna"; // This change is now detected

6. Watching a Computed Property

If $watch() is tracking a computed value (from a function), it won’t trigger automatically.

Example

$scope.getName = function() {
return $scope.firstName + " " + $scope.lastName;
};

$scope.$watch('getName()', function(newVal) {
console.log('Computed name changed:', newVal);
});

This won’t work because $watch() expects a scope variable, not a function.

Solution: Use a Function Callback

$scope.$watch(function() {
return $scope.firstName + " " + $scope.lastName;
}, function(newVal) {
console.log('Computed name changed:', newVal);
});

7. Digest Cycle Not Running

If changes are made but AngularJS doesn’t know about them, $watch() won’t trigger.

Example

$scope.name = "Krishna"; // No digest cycle started

Solution: Manually Trigger Digest

If necessary, call $scope.$apply():

$scope.$apply(function() {
$scope.name = "Krishna";
});

Step 3: Debugging Steps

  1. Check Scope Hierarchy – Is $watch() in the correct scope?
  2. Use Deep Watch (true) – If tracking an object or array.
  3. Wrap in $apply() or $timeout() – If changes happen outside Angular’s context.
  4. Remove :: One-Time Binding – If used in the view.
  5. Define $watch() Before Model Updates – To catch changes from the beginning.
  6. Watch Functions Properly – Use a function callback instead of getName().
  7. Manually Trigger $apply() – If digest cycle isn’t running.

Leave a Reply

Your email address will not be published. Required fields are marked *