Week 11: AngularJS in Widgets: Scope, Data Binding & the Digest Cycle

This is the week to slow down if you're coming from modern Angular — the concepts rhyme, but the mechanics genuinely differ, and conflating them is where most Angular developers trip up on their first widgets. Getting the digest cycle right in your head now avoids a category of "why isn't my UI updating" bugs later.

Module 11 of 25 Week 11 of 26 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Explain what the digest cycle is and how it differs from zone.js-based change detection
  • Use ng-if, ng-repeat and ng-model correctly inside a widget template
  • Recognize the specific situations where AngularJS needs a manual nudge to notice a change

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.

what triggers a digest, conceptually
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:

directive comparison
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}"
in a widget template


{{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.

the fix — $timeout instead of setTimeout
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

Hands-on

Build a filterable incident list widget

Requirements:

  1. Extend the my-open-incidents widget from Week 10 with an ng-model-bound search input filtering the list by short description.
  2. Add an ng-click button that toggles a c.showResolved boolean, and use ng-if/ng-class to conditionally show resolved incidents with a distinct style.
  3. Deliberately reproduce the "AngularJS doesn't notice" bug: inside the controller, use a raw setTimeout to set c.data.incidents = [] after 2 seconds, and confirm the UI does not clear until you trigger some other digest (like clicking the toggle button).
  4. Fix it by injecting and using $timeout instead, 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?

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?

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?

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?

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.