1. The Controller's c as AngularJS's $scope
Every property you attach to c in the client controller becomes
available in the HTML template, and — this is the part worth sitting with —
the template re-renders automatically whenever a property on
c changes, with no explicit re-render call, no
markForCheck(), nothing you have to invoke by hand. That
"automatically" is doing a lot of work, and understanding what actually triggers
it is this week's real subject.
2. The Digest Cycle
AngularJS doesn't use a virtual DOM or fine-grained reactive signals. Instead,
it runs a digest cycle: whenever something AngularJS is
already watching happens — a click handled by ng-click, an
ng-model-bound input changing, a promise resolving inside an
AngularJS-aware call — AngularJS re-evaluates every "watched" expression on the
page, compares each one's new value to its last known value, and re-renders
anywhere it finds a difference.
ng-click="c.refresh()" → triggers a digest after c.refresh() runs
ng-model="c.searchText" → triggers a digest on every keystroke
c.server.update().then(...) → triggers a digest when the promise resolves
setTimeout(() => { c.x = 1 }) → does NOT trigger a digest on its own
If you know modern Angular's zone.js (or signals): zone.js patches async
browser APIs so change detection runs automatically almost everywhere; AngularJS
1.x has no such patching. It only knows to run a digest from inside its own
directives and services (ng-click, $http,
$timeout) — a raw setTimeout or a third-party
library's callback runs completely outside AngularJS's awareness by default.
3. Directives You Already Half-Know
If you've used Angular's structural directives, these will look immediately familiar, with different names and slightly different syntax:
Angular (2+) AngularJS 1.x (Service Portal widgets)
*ngIf="cond" → ng-if="cond"
*ngFor="let x of xs" → ng-repeat="x in xs"
[(ngModel)]="val" → ng-model="val"
(click)="fn()" → ng-click="fn()"
[class.active]="c" → ng-class="{active: c}"
{{item.name}}
No items found.
4. When AngularJS Doesn't Notice a Change
The practical failure mode: you set c.someValue inside a callback
AngularJS doesn't know about (a raw fetch(), a third-party JS
library's event handler, a plain setTimeout), and the template
silently doesn't update — the value changed in memory, but no digest ran to
notice and re-render.
function(spUtil, $timeout) {
var c = this;
someThirdPartyLibrary.onEvent(function(result) {
// Wrap the state update so AngularJS knows to run a digest afterward:
$timeout(function() {
c.data.result = result;
});
});
}
$timeout is AngularJS's own wrapper around setTimeout
that triggers a digest when its callback finishes — injecting it as a controller
dependency (as shown) is the standard fix whenever you need to update
c from inside code AngularJS didn't initiate itself.
5. Hands-on Exercise
Build a filterable incident list widget
Requirements:
- Extend the my-open-incidents widget from Week 10 with an
ng-model-bound search input filtering the list by short description. - Add an
ng-clickbutton that toggles ac.showResolvedboolean, and useng-if/ng-classto conditionally show resolved incidents with a distinct style. - Deliberately reproduce the "AngularJS doesn't notice" bug: inside the controller, use a raw
setTimeoutto setc.data.incidents = []after 2 seconds, and confirm the UI does not clear until you trigger some other digest (like clicking the toggle button). - Fix it by injecting and using
$timeoutinstead, and confirm the list now clears automatically after 2 seconds with no other interaction.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What actually causes a widget's template to re-render when a property on c changes?
What actually causes a widget's template to re-render when a property on c changes?
AngularJS's digest cycle: when something AngularJS is already watching happens (a directive like ng-click firing, an ng-model input changing, an AngularJS-aware promise resolving), it re-evaluates every watched expression, compares each to its last known value, and re-renders wherever it finds a difference.
Q2
How does this differ fundamentally from modern Angular's zone.js-based change detection?
How does this differ fundamentally from modern Angular's zone.js-based change detection?
zone.js patches most async browser APIs so change detection runs automatically almost everywhere in modern Angular. AngularJS 1.x has no equivalent patching — it only knows to trigger a digest from inside its own directives and services (ng-click, $http, $timeout), so plain setTimeout calls or third-party library callbacks run entirely outside its awareness by default.
Q3
You set c.data.value inside a raw setTimeout callback and the template doesn't update. What's the fix?
You set c.data.value inside a raw setTimeout callback and the template doesn't update. What's the fix?
Wrap the state update in AngularJS's own $timeout service instead of the browser's native setTimeout. $timeout is a thin wrapper that runs a digest after its callback completes, which is what actually triggers AngularJS to notice the change and re-render the template.
Q4
What's the AngularJS 1.x equivalent of Angular's *ngFor and *ngIf?
What's the AngularJS 1.x equivalent of Angular's *ngFor and *ngIf?
ng-repeat (equivalent to *ngFor, e.g. item in items) and ng-if (equivalent to *ngIf, e.g. ng-if="condition"). The concepts map closely — repeating over a list and conditionally rendering a block — even though the directive names and exact syntax differ.