AngularJS Watchers
A watcher observes an expression during digest. Choose reference, collection or deep comparison deliberately and unregister a watcher when its independent ownership ends.
How it works
The desk observes a shallow collection with $watchCollection. Pushing one item changes the collection contents even though the array reference remains the same. The listener stores a short count summary and does not keep changing the watched collection. A listener that changes its own observed value on every pass can create an unstable digest loop.
Run the code
AngularJS 1.8.3 is a legacy framework whose official support ended in January 2022. These examples teach maintenance, not a recommendation for a new application. They need a separate local browser page; Bhylu has no AngularJS playground.
In an empty folder save the HTML below as watchers.html and the server as lab-server.mjs. Make a vendor folder and save the four pinned libraries there, retaining their MIT notices. With Node 22 or later run node lab-server.mjs watchers.html, then open http://127.0.0.1:8136/. Stop that lab before running a different lesson. The server supplies fixed read-only HTTP fixtures; no data is saved and it does not connect to SQL. No PHP, Apache or MySQL configuration changes are needed.
Example
<!doctype html>
<html lang="en" ng-app="repairLab" ng-strict-di>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AngularJS Watchers</title>
<style>
:root {
font:
18px/1.55 system-ui,
sans-serif;
color: #0f3464;
background: #fff;
color-scheme: light;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
main {
max-width: 50rem;
margin: auto;
padding: 1.2rem;
}
h1 {
font-size: 1.65rem;
}
label {
display: block;
margin: 0.6rem 0;
}
input,
select,
textarea,
button {
font: inherit;
max-width: 100%;
padding: 0.35rem;
margin: 0.2rem;
}
button {
border: 1px solid #034fa0;
border-radius: 0.25rem;
background: #034fa0;
color: white;
}
button:disabled {
opacity: 0.55;
}
output,
[role="status"] {
display: block;
min-height: 1.5rem;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
padding: 0.4rem;
border: 1px solid #a5bbd4;
text-align: left;
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
:focus-visible {
outline: 3px solid #0078d4;
outline-offset: 3px;
}
[ng-cloak] {
display: none !important;
}
.marked {
font-weight: 700;
}
.error {
color: #a02424;
}
:root[data-theme="dark"] {
color: #d6e7fb;
background: #061d30;
color-scheme: dark;
}
:root[data-theme="dark"] .error {
color: #ffb4b4;
}
</style>
</head>
<body>
<main ng-controller="DeskController as vm" ng-cloak>
<h1>Community repair desk</h1>
<p>AngularJS 1.8.3 · Legacy maintenance lab · Local demonstration</p>
<button type="button" ng-click="vm.add()">Add request</button>
<p data-result>{{ vm.summary }}</p>
</main>
<script src="/vendor/angular.min.js"></script>
<script>
(function () {
"use strict";
var app = angular.module("repairLab", []);
app.controller("DeskController", [
"$scope",
function ($scope) {
var vm = this;
vm.items = ["Hinge"];
vm.summary = "";
vm.add = function () {
vm.items.push("Latch");
};
var stop = $scope.$watchCollection(
function () {
return vm.items;
},
function (items) {
vm.summary =
items.length + (items.length === 1 ? " record" : " records");
},
);
$scope.$on("$destroy", stop);
},
]);
})();
</script>
</body>
</html>
Focus on the bindings and controller
This excerpt leaves out the document shell, styles and library files. Use the complete download to run it.
<button type="button" ng-click="vm.add()">Add request</button>
<p data-result>{{ vm.summary }}</p>
<script>
(function () {
"use strict";
var app = angular.module("repairLab", []);
app.controller("DeskController", [
"$scope",
function ($scope) {
var vm = this;
vm.items = ["Hinge"];
vm.summary = "";
vm.add = function () {
vm.items.push("Latch");
};
var stop = $scope.$watchCollection(
function () {
return vm.items;
},
function (items) {
vm.summary =
items.length + (items.length === 1 ? " record" : " records");
},
);
$scope.$on("$destroy", stop);
},
]);
})();
</script>
Expected result
1 record
Adding a request changes the summary to two records. The test exercises the actual watcher instead of calling its listener directly. Deep watches can retain copies and compare more data on every digest; use them only when that cost and ownership contract are justified.