Using AngularJS $scope.$watch for Declarative UI Updates
Learn how $scope.$watch lets you react to model changes without manual DOM updates, with a live search example and performance tips.
10 Sept 2026, 15:44 UTC

How $watch works
AngularJS registers a listener with $scope.$watch that watches an expression and invokes a callback whenever the expression’s value changes during the digest cycle. The listener receives the new and old values, allowing you to react to model updates without touching the DOM directly.
Shallow vs. deep watches
By default $watch uses reference equality (shallow). If you pass true as the third argument, AngularJS performs a deep comparison, walking object properties or array elements. Deep watches are useful for detecting changes inside complex objects but are more expensive.
Performance considerations
Each active watch adds work to every digest loop. Avoid heavy computations or DOM reads inside the watch expression; keep it lightweight. If you notice UI lag, profile the number of watchers (e.g., via Batarang or AngularJS debug data) and consider:
- Using
$watchCollectionfor array or object shallow changes. - Switching to one‑time bindings (
::) when the value stabilizes. - Removing watches with the deregister function returned by
$watchwhen a scope is destroyed.
Worked example: live search filter
The following controller demonstrates a declarative live search. As the user types, $scope.searchTerm changes, the watcher filters the list, and the view updates automatically.
angular.module('searchApp', []) .controller('SearchCtrl', ['$scope', function($scope) { $scope.items = [ {name: 'Apple', type: 'fruit'}, {name: 'Carrot', type: 'vegetable'}, {name: 'Banana', type: 'fruit'} ]; $scope.searchTerm = ''; $scope.results = []; var stopWatch = $scope.$watch('searchTerm', function(newVal, oldVal) { if (newVal === oldVal) return; $scope.results = $scope.items.filter(function(item) { return item.name.toLowerCase().indexOf(newVal.toLowerCase()) !== -1; }); }); // Clean up when the scope is destroyed $scope.$on('$destroy', function() { stopWatch(); }); }]);HTML template:
<div ng-app='searchApp' ng-controller='SearchCtrl'> <input type='text' ng-model='searchTerm' placeholder='Type to filter…'> <ul> <li ng-repeat='item in results'> {{item.name}} ({{item.type}}) </li> </ul> </div>To verify the behavior, open the browser console and add a temporary
console.log(newVal, oldVal)inside the watcher; each keystroke should log the updated term. No manual DOM calls are required.Trade‑off and limitation
While $watch removes boilerplate, excessive or deep watchers can lengthen the digest cycle, causing noticeable lag in large apps. A practical mitigation is to limit watches to primitive values or use
$watchCollectionwhen you only need to know that an array or object changed, not the exact diff.Actionable closing
Use
$scope.$watchwhen you need a reactive side‑effect that cannot be expressed with a directive or one‑time binding. Keep the watch expression simple, deregister it when the scope disappears, and monitor watcher counts during development. For simple UI reactions like input‑driven filters, considerng-changeor controller‑as computed properties as lighter alternatives.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.