AngularTS is a browser framework for building interactive web pages and
applications with HTML, JavaScript, and TypeScript. These docs assume no prior
framework knowledge.
Start here
Introduction explains the
framework’s HTML-first model and what it adds to AngularJS.
Install AngularTS adds the
framework with one script tag or an npm package.
AngularTS is buildless, type-safe and reactive JS framework for building
structured web applications at any scale. It continues the legacy of
AngularJS by providing the best developer experience
via immediate productivity without the burden of JS ecosystem tooling. Getting
started with AngularTS does not even require JavaScript. All you need is a
little bit of HTML. Below is a canonical example of a counter:
Example
<sectionng-appng-cloak><buttonclass="btn btn-dark"ng-init="count = 0"ng-click="count++"> Count is: {{ count }}
</button></section>
Result
This code demonstrates the following key AngularTS features:
HTML-first: AngularTS is designed for HTML-first approach, meaning your
application logic can be expressed declaratively in your markup using custom
attributes–called directives. Here, we are using ng-init directive to
initialize our application state and ng-click directive to add an event
handler that changes our state.
Template-driven: AngularTS’s built-in template engine automatically keeps
the UI in sync with your application state, eliminating the need for manual
DOM tracking and updates. The {{count}} expression above is an example of
Angular’s interpolation syntax. As the value of count variable increases,
our UI is updated with the new state.
Island-first and zero-cost: AngularTS creates an application on any HTML
tag with ng-app attribute, allowing multiple independent applications
(modules) to live on a single page. This allows AngularTS to work alongside
your existing tech stack where the majority of your page is rendered on the
server, while AngularTS is isolated to small “islands” (regions of your page)
where custom interactivity or personalization is required.
Micro-framework appearance: With its minimal setup, AngularTS is
well-suited for quick experiments, LLM-generated code, and learning web
development in general. But beneath its lightweight surface, it supports
structured enterprise design patterns, MVC architecture, and component-driven
design. With its rich directive library, state-based routing and support for
animations, AngularTS is a complete package for building large SPAs, server,
mobile, and desktop applications.
1 - Get started
Install AngularTS and build a complete first application without prior framework experience.
Follow these pages in order. They use the same task-list application so each
page adds one idea instead of starting over.
You only need a browser and a text editor. Node.js, a bundler, and TypeScript
are optional.
Already comfortable with browser frameworks? Use the
Quickstart as a shorter
alternative to the complete beginner sequence.
1.1 - AngularTS: modern evolution of AngularJS
Understand where AngularTS fits, what remains compatible with AngularJS, and what changed in the new runtime.
AngularTS is a modernized continuation of AngularJS —
carrying forward its three core pillars (string interpolation, dependency
injection, and two-way data binding) while rebuilding the internals around a
reactive change-detection model, full TypeScript support, and direct access to
native browser APIs. It requires no build step and no bundler to get started.
What AngularTS preserves from AngularJS
AngularJS was the result of a decade of engineering by the Angular team at
Google and accumulated one of the largest test suites in open-source JavaScript.
AngularTS inherits that foundation directly:
Declarative HTML templates using familiar ng-* directives
Dependency injection container for organizing and composing application
services
Two-way data binding between the DOM and application state
Controllers, filters, and the module system — all structurally compatible
with AngularJS patterns
If you have existing AngularJS knowledge, the mental model transfers directly.
What AngularTS adds
AngularTS extends the AngularJS foundation with modern primitives and new
capabilities:
Reactive change detection
Proxy-based reactivity replaces the digest cycle. The DOM updates only when data
actually changes — no polling, no virtual DOM diffing.
Native DOM and Promises
Directives and controllers work directly with native DOM APIs. The $q and
$timeout abstractions are replaced by native Promise and setTimeout.
Built-in enterprise router
ng-router is a port of ui-router, supporting nested views, state
transitions, resolves, and URL matching out of the box.
Declarative HTTP directives
ng-get, ng-post, ng-put, and ng-delete let you make HTTP requests
declaratively from HTML attributes. The server-first approach may feel familiar
to HTMX users, but AngularTS has its own API and behavior.
Real-time injectables
$websocket, $sse (Server-Sent Events), $rest, Web Workers, and WebAssembly
modules are first-class injectable services.
Built-in animations
CSS and JavaScript animation support ships in the core package with no
additional dependencies.
When to use AngularTS
AngularTS is a strong fit for these scenarios:
Server-rendered applications with interactive islands. Any HTML element can
host an independent ng-app. You can drop AngularTS into a server-rendered page
and add interactivity to specific regions without restructuring the whole
application.
Applications where a build step is a liability. AngularTS loads from a CDN
script tag and runs directly in the browser. There are no compilation steps, no
bundler configuration files, and no Node.js required to develop or deploy.
Large-scale SPAs that need structure. The module system, dependency
injection, state-based router, and MVC architecture scale to enterprise
applications. AngularTS is not just a micro-library — it is a complete framework
for applications of any size.
Migrating from AngularJS. If you maintain an AngularJS codebase and want to
modernize incrementally, AngularTS preserves the API surface you already know.
How it compares to other frameworks
AngularTS
React
Vue
Angular (v2+)
Build step required
No
Yes
Optional
Yes
CDN drop-in
Yes
Partial
Yes
No
Two-way binding
Yes
No
Yes
Yes
Dependency injection
Yes
No
No
Yes
HTML-first templates
Yes
No (JSX)
Yes
Yes
Bundle size
Small (UMD)
Medium
Small
Large
Note: Angular 2+ (also called just “Angular”) is a complete rewrite of
AngularJS and shares no API surface with it. AngularTS is a continuation of
AngularJS — not a migration target from Angular 2+.
Philosophy
AngularTS stays as close to web standards as possible. It avoids inventing
abstractions where the platform already provides them: native Promise instead
of $q, native fetch under the hood for $http, direct DOM element
references in directives instead of jQuery wrappers. This keeps the mental model
small and the debugging experience familiar.
The result is described by the project as “a high-performance, buildless,
multi-paradigm and battle-tested JS framework.”
Add AngularTS via a CDN script tag or npm. The package ships TypeScript declarations and supports auto-bootstrap with ng-app or manual bootstrap.
AngularTS can be added to any project in two ways: a single <script> tag for
zero-configuration browser use, or an npm package for projects that use a
bundler or want TypeScript declarations. Both approaches support the full
framework — there is no difference in available features between the two.
CDN
The fastest way to get started is to load AngularTS directly from
jsDelivr. No installation, no configuration — just
add the script to your HTML file:
<html><head><scriptsrc="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script></head><body><divng-appng-init="x = 'world'">Hello {{ x }}</div></body></html>
The UMD build exposes the angular global on window and auto-bootstraps any
element with an ng-app attribute once the DOM is ready.
Tip: The CDN URL always points to the latest published version on npm. To
pin a specific version, include it in the URL:
https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts@0.26.0/dist/angular-ts.umd.min.js
npm
Install the package from the npm registry:
npm install @angular-wave/angular.ts
yarn add @angular-wave/angular.ts
pnpm add @angular-wave/angular.ts
The package ships two distribution formats:
ESM (dist/angular-ts.esm.js) — the default entry point for bundlers
UMD (dist/angular-ts.umd.js) — for direct browser use
Import the library in your entry file:
import{angular}from'@angular-wave/angular.ts';
The default ESM import and UMD script both scan for ng-app once the DOM is
ready. The angular singleton is also assigned to window.angular
automatically.
TypeScript setup
The published package includes generated TypeScript declarations under
@types/. No separate @types/ package is required.
After installing, TypeScript will resolve types automatically. You can reference
the type namespace in your project:
Info: The ng namespace provides types for scopes, injectors, services,
directives, and all other AngularTS primitives. It is declared globally by the
package and available anywhere TypeScript resolves the package types.
Auto-bootstrap with ng-app
The simplest way to start an AngularTS application is the ng-app attribute.
Place it on any HTML element and AngularTS will bootstrap that element as the
application root when the DOM is ready. Importing the default package entry is
enough; no startup call is needed for a static ng-app root.
<divng-app>{{ 1 + 1 }}</div>
To connect a named module, set ng-app to the module name:
<divng-app="myApp"><png-controller="GreetController">{{ greeting }}</p></div><scriptsrc="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script><script>angular.createModule('myApp',[]).controller('GreetController',['$scope',function($scope){$scope.greeting='Hello from AngularTS';},]);</script>
Multiple independent ng-app elements can exist on the same page. Each becomes
its own isolated application instance.
Explicit dependency injection
AngularTS requires explicit annotations for every dependency-bearing function.
Use array annotation or a static $inject property; parameter names are never
interpreted as injection tokens.
Manual bootstrap with angular.bootstrap()
For full control over when and how your application starts, call
angular.bootstrap() directly instead of using ng-app:
It returns the $injector instance for the bootstrapped application.
Warning: Do not call angular.bootstrap() on an element that already has
an ng-app attribute, or on an element that contains ng-view, ng-if, or
other transclusion directives. This causes the root element and injector to be
misplaced.
Learn how the AngularTS module system organizes your application.
1.3 - Build your first application
Build a task list from one HTML file while learning modules, controllers, state, events, forms, and reactive rendering.
What you will build
You will build a task list that accepts text, adds tasks, marks them complete,
and shows the remaining count. The whole application fits in one HTML file.
Before you start
Complete installation or use
the CDN script shown below. Create index.html.
Create the application
<html><head><scriptsrc="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script></head><bodyng-app="todoApp"ng-controller="TodoController"><formng-submit="addTodo()"><inputng-model="draft"placeholder="What needs doing?"required/><button>Add</button></form><p>{{ remaining() }} remaining</p><ul><ling-repeat="todo in todos"><inputtype="checkbox"ng-model="todo.done"/> {{ todo.title }}
</li></ul><!-- Add the script below here. --></body></html>
Place this script before the closing body tag, where the comment appears:
Open the file in a browser. Enter a task and press Add. The list and count
change without a page reload.
Understand each part
ng-app="todoApp" starts the named module. A module is the application
container. ng-controller connects behavior and state to this part of the DOM.
The injected $scope is the reactive state visible to the HTML template.
ng-model keeps an input and state property synchronized. ng-submit runs a
function when the form is submitted. ng-repeat creates one list item per task.
{{ remaining() }} displays an expression and refreshes when its reactive
inputs change.
Make a change
Change the initial task title, reload the page, and add two tasks. Then open the
browser console and resolve any reported error before continuing.
The AngularTS script finds the element marked with ng-app.
AngularTS loads that element’s named module and creates an injector.
The compiler walks the element’s DOM and finds directives.
The controller creates state on its scope.
Directives connect that state to text, properties, events, and child DOM.
An injector creates and shares dependencies. A directive adds behavior to HTML.
A scope owns reactive state and lifecycle cleanup. The compiler is the runtime
process that connects directives to DOM nodes; it is not a build step.
A reactive update
When addTodo() pushes an item into todos, the reactive scope records that
change. AngularTS schedules the affected bindings, updates the repeated list and
remaining count, and leaves unrelated DOM alone.
You do not manually redraw the page, compare a virtual DOM, or start a digest
loop. Browser events and native asynchronous callbacks can update reactive state
directly.
Application structure
Small applications may stay in one file. Larger applications normally use a
module, components for UI regions, services for shared behavior or data, and a
router for multiple screens. Dependency injection connects those parts without
global variables.
Use the migration guide. It separates
AngularJS-compatible concepts from AngularTS replacements.
Use another language
Use the integration guide after learning
the runtime concepts in this tutorial.
Optimize and standardize an application
Use the cookbook after the first feature is
working. Start by measuring, then apply the narrowest relevant recipe. Adopt the
application style guide before multiple contributors add components.
Next step
Open the first page in the path that matches your project.
1.6 - Build your first AngularTS app
A step-by-step guide to creating modules, controllers, and directives in AngularTS — with a counter example and a complete todo list application.
AngularTS apps are built from HTML and JavaScript — no compilation required.
This guide walks through creating a module, wiring up a controller, and using
directives to bind data to the DOM. By the end you will have a working counter
and a functional todo list.
Add AngularTS to your page
Create an HTML file and include the AngularTS script from the CDN. This single
tag is all you need to get the full framework:
<!doctype html><html><head><metacharset="UTF-8"/><title>My AngularTS App</title><scriptsrc="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script></head><body><!-- application markup goes here --></body></html>
If you are using npm, install the package and import the singleton instead:
npm install @angular-wave/angular.ts
import{angular}from'@angular-wave/angular.ts';
Create a module
A module is the top-level container for your application. It holds controllers,
services, directives, and filters. Create one by calling angular.createModule() with
a name and an empty dependency array:
constapp=angular.createModule('myApp',[]);
The first argument is the module name. The second argument lists other modules
your module depends on — an empty array means no dependencies. You reference
this name in the ng-app attribute to tell AngularTS which module to bootstrap.
Connect the module to your HTML by adding ng-app to a container element:
<bodyng-app="myApp"><!-- AngularTS controls everything inside this element --></body>
Add a controller
Controllers attach behavior to a region of the DOM. They receive a $scope
object — a plain JavaScript object that acts as the data model for their
template. Anything you put on $scope becomes available in the HTML template.
The {{ count }} expression is AngularTS’s interpolation syntax — it renders
the current value of $scope.count and updates automatically whenever the value
changes. ng-click binds a click event to the controller method.
Use directives in your template
Directives are the building blocks of AngularTS templates. They extend HTML with
behavior declared as attributes or element names. The core library ships over 50
directives covering data binding, conditional rendering, list rendering, forms,
and HTTP requests.
Here is a complete counter example using only HTML attributes and no separate
JavaScript file:
<!doctype html><html><head><scriptsrc="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script></head><body><sectionng-appng-cloak><buttonng-init="count = 0"ng-click="count++"> Count is: {{ count }}
</button></section></body></html>
Key directives used here:
ng-app — designates the root element of the application and triggers
auto-bootstrap
ng-cloak — hides the element until AngularTS has compiled the template,
preventing a flash of unrendered {{ }} expressions
ng-init — initializes a scope variable inline; useful for simple cases
without a controller
ng-click — evaluates an expression when the element is clicked
Complete example: todo list
This excerpt combines a named module, two-way binding, and list rendering. Put
the normal AngularTS script tag in the document head as shown above.
<sectionng-app="todoApp"ng-controller="TodoController"><formng-submit="addTodo()"><inputng-model="newTodo"required/><button>Add</button></form><ling-repeat="todo in todos"><inputtype="checkbox"ng-model="todo.done"/> {{ todo.text }}
</li></section><script>angular.createModule('todoApp',[]).controller('TodoController',['$scope',($scope)=>{$scope.todos=[];$scope.addTodo=()=>$scope.todos.push({text:$scope.newTodo,done:false});},]);</script>
This example uses:
Directive
Purpose
ng-model
Two-way binding between the input value and $scope.newTodo
ng-submit
Calls addTodo() when the form is submitted
ng-repeat
Renders a <li> for each item in $scope.todos
ng-class
Adds the done CSS class when todo.done is true
ng-show
Shows the element only when the expression is truthy
ng-click
Calls a scope function when the element is clicked
Using TypeScript
If you are working with npm and TypeScript, annotate the controller function
using the ng.Scope type:
Use built-in services for HTTP, WebSockets, REST, and more.
2 - Tutorial
Build and ship one complete AngularTS application from an empty directory.
This tutorial assumes basic HTML and JavaScript but no AngularTS knowledge. You
will build a browser-owned task board with reactive state, form validation,
routing, server data, tests, and a production checklist.
This application deliberately uses AngularTS’s optional typed programmatic
views because its model and rendering live in TypeScript. If your server renders
the HTML, follow the first application guide instead and keep the HTML you already
have.
What you will build
The finished application can list tasks, add a task, open a task route, load
initial data, and expose loading and error states.
Register a component whose model and DOM bindings are checked by TypeScript.
A typed view constructs real DOM and keeps controller reads reactive. Its main
advantage over string templates is type safety between the component model and
its bindings.
import{h1,main,p}from'@angular-wave/angular.ts';classTaskBoard{title='Task board';tasks=[{id: 1,title:'Read the guide',done: false}];}app.component<TaskBoard>('taskBoard',{controller: TaskBoard,view:({controller})=>main(h1(()=>controller.title),p(()=>`${controller.tasks.length} task(s)`),),});
The view is registered on the normal application module. It does not need a
separate mount operation. Functions used as children or property values are
reactive readers; plain values render once.
Checkpoint
The page should display “Task board” and one task count.
Separate model tests from browser-level rendering checks.
Start with a fast controller test:
it('adds a trimmed task',()=>{constboard=newTaskBoard();board.draft=' Write tests ';board.submit();expect(board.tasks.at(-1)?.title).toBe('Write tests');expect(board.draft).toBe('');});
Then verify the user-visible contract in a browser:
test('adds a task',async({page})=>{awaitpage.goto('/tasks');awaitpage.getByLabel('Task').fill('Write tests');awaitpage.getByRole('button',{name:'Add'}).click();awaitexpect(page.getByText('Write tests')).toBeVisible();});
Test behavior, not internal scope fields or generated DOM comments. Keep network
outcomes deterministic.
Checkpoint
Both the model test and browser test pass, including the loading failure case.
The example keeps data local so it runs without a backend. The routing and
server-data step shows where application-owned transport behavior belongs.
Compare behavior
Verify that both versions:
Render the initial keyed collection.
Disable submission for blank text.
Trim accepted task titles.
Clear the input after submission.
Preserve existing rows when a task is appended.
CI type-checks the TypeScript application and runs the browser interaction
contract. Documentation checks also validate its AngularTS API usage.
3 - Core concepts
Understand the small set of ideas that AngularTS applications use: modules, components, scopes, dependency injection, templates, views, and reactivity.
Read this section after the first-application tutorial. A concept explains why
the framework behaves as it does; a task guide
shows how to complete a particular job.
Use the Angular runtime to create modules, bootstrap applications, access injectors, and bridge external code.
Angular is the runtime entry point
for AngularTS. It owns module registration, application bootstrap, injector
creation, cached DOM helpers, and the event-based invocation helpers exposed
through window.angular.
The built-in ng module is prepended automatically. Every dependency-bearing
function must use array annotation or declare a static $inject property.
Each element can host only one application. Bootstrapping an element that
already has an injector throws ng:btstrpd.
Auto-Bootstrap
The default package entry and UMD build scan for ng-app when the DOM is ready.
The first root uses the global
Angular instance; additional roots
become sub-applications in angular.subapps. No startup call is required.
Custom runtimes do not scan the document. Bootstrap their root explicitly:
Use defineAngularElement() from
@angular-wave/angular.ts/runtime/web-component when an AngularTS feature
should ship as a native web component. The helper creates a custom runtime,
registers only the directives and services you list, defines the custom element,
and builds the injector without requiring a host page bootstrap.
Custom runtime core providers include only the compile, scope, controller,
parse, interpolation, and exception-handling services needed to render
templates. Orchestration services such as $machine and $workflow are not
part of that core set. Include machineModule for reactive state machines,
workflowModule for workflows, or the compatible orchestrationModule
aggregate when the runtime needs both.
Include wasmModule when a custom runtime loads WebAssembly or exposes
AngularTS scope state through the language-neutral WASM ABI. The bridge remains
owned by that runtime and is disposed with it.
Include sseModule, websocketModule, or webTransportModule when a custom
runtime uses one transport. Include the compatible realtimeModule aggregate
when it uses all three. Connection defaults still use the normal typed module
configuration, and all active connections close when the custom runtime is
destroyed.
Include routerModule when a custom runtime owns navigation. The slice includes
URL state, navigation security, route links, routed views, ARIA state, anchor
scrolling, and route-template fetching, while leaving unrelated browser and
orchestration services out of the build.
Include serviceWorkerModule for managed service-worker registration, updates,
and messaging without including the full browser integration group. The native
listeners owned by $serviceWorker are released when the custom runtime is
destroyed.
Use these helpers for small integrations such as browser callbacks, legacy
scripts, or embedded widgets. Normal application code should prefer dependency
injection.
Locate Named Scopes
getScopeByName() searches from $rootScope for a scope with a matching
scopeName.
Construct component and directive views with real DOM nodes, reactive readers, keyed collections, and normal AngularTS module registration.
Programmatic views provide a JSX-free alternative to HTML templates. A view is
an ordinary function that returns real DOM nodes created with typed tag
factories. It uses
the normal AngularTS compiler, scope lifecycle, dependency injection, component
registration, directive registration, and transclusion pipeline.
There is no separate mount API. Register the component or directive on an
AngularTS module and let the compiler instantiate it from normal markup.
Create a component view
Import the helpers you use from the package entry point:
Named imports are preferred in ESM applications because bundlers can retain
only the factories that are used. No-build UMD applications can access tag
factories through angular.tags and helpers through angular.view. Keep
tags for namespace proxies; use tag() when the element name is dynamic.
Define the view directly on the component registration:
The conditional expression in this example is evaluated only when the view is
constructed. Make the condition itself a reader when it must remain reactive:
A function in a child or ordinary non-event property position is a reactive
reader. AngularTS executes it, tracks the proxied scope and controller state it
reads, and updates only the corresponding DOM binding when that state changes.
Use reader functions whenever a child or property must be reevaluated
reactively.
Unlike string templates, programmatic views keep bindings between the model and
the rendered DOM inside TypeScript’s type system. Renaming a controller or model
property updates through normal refactoring tools, while misspelled members and
incompatible typed DOM property values fail at compile time rather than becoming
runtime template errors.
Properties and attributes
The first plain object passed to a tag is its property map. Reactive readers use
DOM property semantics, including properties exposed only by custom elements.
For static values, AngularTS assigns a DOM property when it exists on the
element and otherwise uses an attribute.
Normal property maps are checked against the element’s DOM type. Use attrs()
for arbitrary attribute names and props() for custom-element properties;
misspelled native properties are rejected by TypeScript.
Boolean HTML attributes use presence semantics. Other attributes, including ARIA
attributes, are serialized normally. For example, an ARIA value of false
becomes the string "false" rather than removing the attribute.
Use props() to assign exact, literal DOM property values. Values inside
props() are not reactive readers. This distinction permits function-valued
properties on custom elements:
// Reactive property
input({value:()=>controller.name});// Literal function assigned as the value property
input(props({value:()=>controller.name}));
Potentially dangerous properties such as innerHTML and srcdoc still pass
through AngularTS contextual escaping. Use trusted SCE values only when the
content has been reviewed and is intentionally trusted.
Handle events
Functions assigned to native on* properties are event handlers:
Calling todo() reads the current value associated with that key. When a new
object replaces an old object with the same key, AngularTS preserves its DOM
nodes and updates bindings that read todo(). Reordering moves existing nodes,
new keys create nodes, and removed keys dispose their nodes and bindings.
Duplicate keys are rejected through $exceptionHandler. Keep keys stable and
unique for the lifetime of each logical item.
Create dynamic and namespaced elements
Use tag() when the HTML tag name is dynamic or cannot use property access:
The scope that owns reactive bindings and generated DOM.
host
The component host element.
transclude
onDestroy
Registers view-owned cleanup and returns a cancellation function.
Directive views receive a
ProgrammaticViewContext.
It adds required, containing controllers resolved from the directive’s
require declaration. Its controller is undefined when none is declared.
Create a directive view
Directives can provide view instead of a template. Generated nodes continue
through the normal compile and link pipeline, so registered directives on those
nodes remain available.
Reactive bindings, event listeners, keyed children, and compiled fragments are
disposed automatically. Register resources created by application code with
onDestroy():
Cleanup runs when either the owning scope is destroyed or the host element is
deallocated. The returned cancellation function transfers ownership elsewhere. A
cleanup registered after destruction runs immediately. Cleanup errors are
reported through $exceptionHandler.
Language integrations
The Kotlin, Scala, and Dart integrations expose typed wrappers for the same
runtime model:
ProgrammaticTags wraps angular.tags.
ProgrammaticViewApi wraps angular.view helpers.
ProgrammaticViewContext
exposes controller, required, scope, host, transclude, and
onDestroy.
Integration each() renderers receive an item reader, matching TypeScript.
Plain callback functions are reactive in normal child and property positions.
Use each integration’s props() helper when assigning a literal callback as a
DOM property.
Choose templates or programmatic views
Use a programmatic view when direct DOM construction, dynamic tag selection,
custom-element property assignment, or fine-grained imperative integration is
clearer than markup. Use an HTML template when static document structure and
designer-readable markup are more important.
Both forms use the same AngularTS module, scope, compiler, directive, and
lifecycle infrastructure and can coexist in one application.
3.3 - Components
Understand components as registered UI units with a controller, inputs, lifecycle, and either an HTML template or typed view.
A component is a reusable UI unit registered on a module. It owns a DOM region,
an optional controller, explicit inputs, and one rendering definition.
The module registration makes the component available to compiled DOM in that
application. No annotation syntax, decorator, separate mount call, or global
custom-element registration is required.
Controller and bindings
The controller stores component behavior and local state. In a template, $ctrl
refers to that controller. Bindings define the component’s public inputs and
outputs instead of exposing its internal scope.
Template or typed view
A component can render an HTML template, load a templateUrl, or provide a
typed view function. These options share component registration, dependency
injection, directives, child compilation, and cleanup.
3.4 - Reactive change detection with ES6 Proxy scopes
See how AngularTS replaces the AngularJS digest loop with ES6 Proxy interception, scheduling microtask DOM updates that only re-evaluate affected bindings.
Change detection is how a framework decides when to update the DOM to reflect
new data. AngularJS used a polling mechanism called the digest cycle — every
time something might have changed, AngularJS ran all registered watchers,
compared old and new values, and repeated until nothing changed. AngularTS
replaces this entirely with ES6 Proxy-based reactive observation: the
framework knows exactly which property changed, which bindings depend on it, and
schedules only those bindings to re-evaluate.
The digest cycle problem
In AngularJS, every watch registered anywhere in the application was checked
on every digest. A large application could have thousands of watchers running on
every user interaction, mouse move, or HTTP response. This was O(n) in the
number of watchers per cycle, and cycles could chain into one another.
AngularTS eliminates this entirely. There are no cycles. There is no polling.
How Proxy-based reactivity works
When AngularTS creates a scope via createScope(), it wraps the plain object in
a Proxy whose handler is a Scope
instance. The Scope class implements
the set trap:
O(1) lookup: Listeners are stored in a Map<string, Listener[]> keyed by
property name. When count changes, only listeners registered under
'count' are scheduled — not every watcher in the application.
Microtask scheduling: _scheduleListener calls queueMicrotask(), which
defers the flush until after the current synchronous call stack completes.
Multiple changes to the same property in the same tick are coalesced.
Recursive proxying: When you assign an object —
$scope.user = { name: 'Alice' } — the new value is itself wrapped in a
Proxy. Nested property changes ($scope.user.name = 'Bob') are tracked
just as well as top-level ones.
A concrete example
$scope.count=0;$scope.increment=function(){$scope.count+=1;// At this point the Proxy set trap fires:
// 1. Stores the new value
// 2. Looks up listeners for 'count' in the watchers Map
// 3. Schedules them via queueMicrotask
// 4. Returns — no $apply() needed
};}]);
<div><buttonng-click="increment()">+</button><span>{{ count }}</span><!-- binding re-evaluates only when 'count' changes --></div>
When increment() fires, the Proxy intercepts the assignment to count, finds
the binding registered for 'count', and schedules it. The DOM update happens
on the next microtask — after increment() returns but before the browser
renders the next frame.
Comparison with AngularJS
AngularJS (1.x)
AngularTS
Detection mechanism
Dirty-checking digest loop
ES6 Proxy set trap
Watcher lookup cost
O(n) — all watchers checked
O(1) — direct Map lookup by property name
Trigger
Manual digest entry required for async code
Automatic — Proxy intercepts every assignment
Update granularity
All watchers, all the time
Only bindings for the changed property
Nested objects
Shallow by default; $watchCollection needed for deep
Deep — nested objects are automatically proxied
Async code
Must manually enter change detection
No wrapping needed for standard async (Promise, fetch, etc.)
When no $apply is needed
Because the Proxy fires synchronously on every assignment, any code that assigns
to a scope property — whether inside a controller, a service callback, or a
Promise handler — automatically triggers the right DOM update:
$scope.users=[];$scope.loading=true;// No $apply() needed — the Proxy intercepts the assignment
$http.get('/api/users').then(({data})=>{$scope.users=data;// triggers DOM update for {{ users }}
$scope.loading=false;// triggers DOM update for ng-if="loading"
});}]);
Tip: If you are integrating a third-party library that updates data
outside of AngularTS (such as a raw WebSocket callback or a non-Promise-based
timer), and the library stores results in scope properties, those assignments
still flow through the Proxy and trigger updates automatically. No $apply
wrapper is needed.
Microtask batching
Multiple property changes within the same synchronous block are batched. The
listener scheduler enqueues a queueMicrotask flush only once per tick:
_enqueueScheduledTask(task: ScheduledTask):void{constscheduler=this._listenerScheduler;scheduler._queue.push(task);if(!scheduler._queued&&!scheduler._flushing){scheduler._queued=true;queueMicrotask(()=>{this._flushScheduledTasks();// runs all queued listener notifications at once
});}}
If you set ten properties in one synchronous function, all ten listener
notifications are flushed together in a single microtask. The DOM is updated
once, not ten times.
Post-render layout work
Use afterRender when a controller needs to read layout after AngularTS has
applied the DOM work from the current flush:
AngularTS coalesces afterRender to one callback per controller instance per
render flush. The hook runs after directive and binding DOM mutations have
completed, after linked children from structural directives such as ng-repeat
exist, after class/style/attribute bindings for that flush are applied, and
after one browser animation frame gives layout a chance to settle. It does not
wait for external resources such as fonts or images by default.
For explicit scheduling outside the controller lifecycle, import afterRender
or queueAfterRender:
Objects that carry $nonscope: true on the instance or constructor.
This prevents the framework from wrapping DOM nodes or native collections in
Proxies, which would be incorrect and slow.
// Prevents this class from being wrapped in a Proxy
static$nonscope=true;readonlyapiBase='https://api.example.com';}// Or per-instance:
$scope.rawData=Object.assign(newSomeClass(),{$nonscope: true});
When to use watch
In the reactive proxy model, watch is rarely necessary for keeping the DOM in
sync — that happens automatically. Use watch when you need to run
side-effect code in response to a scope property changing:
$scope.watch('selectedTab',function(newTab){externalTabWidget.activate(newTab);});// Trigger a service call when a search term changes
$scope.watch('searchQuery',function(query){if(query&&query.length>2){searchService.find(query).then((results)=>{$scope.results=results;});}});// React to a computed condition
$scope.watch('items.length > 100',function(tooMany){$scope.showPagination=tooMany;});
Note:watch on a constant expression — a bare string literal, number, or
boolean — is evaluated exactly once and the returned deregistration function
is a no-op. The runtime detects the _constant flag on the compiled
expression and avoids registering a watcher at all.
Watcher count and $$watchersCount
You can inspect how many active watchers are registered in a scope subtree:
console.log($scope.$$watchersCount);
This is computed by walking the _watchers Map and counting entries whose
_scopeId matches any scope in the subtree. It is useful for identifying scopes
with unexpectedly high watcher counts during performance profiling.
Performance characteristics
Only affected bindings update
A change to user.name schedules only the bindings that depend on name.
Thousands of unrelated bindings are never touched.
No re-entrancy problems
The scheduler tracks a _flushing flag. If a listener notification causes
another property change, that change is enqueued and flushed in a follow-up
microtask rather than re-entrantly.
Deep tracking at zero cost
Nested objects are proxied at assignment time, not at watch time. There is no ``
or explicit deep-watch flag — all depths are tracked uniformly.
Scope destruction cleans up
When destroy() is called, all watcher entries for that scope’s id are
removed from the shared _watchers Map in a single O(n) pass, with O(1)
swap-pop removal for each matched entry.
3.5 - Dependency injection and the AngularTS injector
Understand how the AngularTS injector resolves named tokens, how to annotate dependencies, and how the provider pattern controls the config and run phases.
Dependency injection (DI) is the mechanism AngularTS uses to supply components
with the services they need. Rather than constructing dependencies directly, you
declare what you need by name and the injector finds, instantiates, and
delivers each dependency. This keeps components decoupled, composable, and
straightforward to test in isolation.
How the injector works
When you call angular.bootstrap() (or use the ng-app directive), AngularTS
calls createInjector() with your list of modules. The injector builds two
internal caches:
Provider cache — holds provider objects, accessible during the config
phase.
Instance cache — holds fully constructed service singletons, built on
first request.
When a service is requested via $injector.get('myService'), the injector looks
up myServiceProvider, calls its get method (injecting that method’s own
dependencies), caches the result, and returns it. Every later get returns the
same instance.
// Retrieve a service
const$http=injector.get('$http');// Built-in token literals infer their registered service type.
const$machine=injector.get(angular.tokens.$machine);// Custom services can provide an explicit result type.
constflags=injector.get<{enabled: boolean}>('featureFlags');// Check existence without throwing
if(injector.has('myOptionalService')){constsvc=injector.get('myOptionalService');}// Invoke a function with injection
injector.invoke(['$rootScope','$http',function($rootScope,$http){// both dependencies are injected automatically
},]);
Applications that use the injector directly can declare their complete custom
service registry once. Built-in token inference remains available alongside the
application services, and invoke() and instantiate() infer their return
types.
interfaceAppServices{auth: AuthService;featureFlags:{enabled: boolean};}constappInjector: ng.InjectorService<AppServices>=injector;constauth=appInjector.get('auth');constanswer=appInjector.invoke(()=>42);// number
constsession=appInjector.instantiate(Session);// Session
Declaring dependencies
AngularTS reads dependency names from the function’s arguments — but argument
names are erased by minifiers. The safe, minification-proof approach is array
annotation: an array where every element except the last is a dependency name
string, and the last element is the function.
Note: The $inject static property on a class is the TypeScript-idiomatic
form of array annotation. AngularTS does not infer dependencies from function
parameter names.
Explicit dependency annotation
Every dependency-bearing function must use array annotation or a static
$inject property. An unannotated function with parameters throws immediately.
This invariant makes dependency injection deterministic under minification.
Injectable types
value
A fixed JavaScript value registered with module.value(). It is not available
during the config phase.
constant
Like value, but available during the config phase and cannot be overridden by a
decorator.
factory
A function whose return value becomes the singleton. Must not return
undefined.
service
A constructor function. Instantiated with new once; the instance is the
singleton.
provider
The most flexible type. An object with a get method. Can expose configuration
methods accessible during the config phase.
decorator
Wraps an existing service. Receives $delegate (the original) and returns a
replacement or augmented version.
The provider pattern
Providers give you a way to configure a service before any instance is created.
During the config phase the provider itself is injected (not the instance),
allowing configuration. During the run phase and everywhere else, the
instance produced by get is injected.
privateprefix='Hello';// Configuration method — callable during config phase
setPrefix(value: string){this.prefix=value;}// The injector calls get to produce the service instance
get=['$log',($log: ng.LogService)=>{constprefix=this.prefix;return{greet(name: string){$log.info(`${prefix}, ${name}!`);},};}];}angular.createModule('myApp',[]).provider('greeter',GreeterProvider).config(['greeterProvider',function(greeterProvider){// During config, the *provider* is injected — note the "Provider" suffix
greeterProvider.setPrefix('Greetings');}]).run(['greeter',function(greeter){// During run, the *instance* is injected
greeter.greet('world');}]);
Info: During the config phase, inject a provider by appending Provider
to its name: greeterProvider for the greeter service. This naming
convention is enforced by AngularTS internally.
Framework-owned double-dollar tokens are internal implementation details and are
deliberately absent from PublicInjectionTokens,
InjectionTokenMap, and
the ng namespace.
Every
InjectionTokenMap[ value is a named ](../../../typedoc/interfaces/InjectionTokenMap.html)ng.\*contract exported through the generated TypeDoc entry point. Adding a public token without both its namespace contract and documentation export failsmake check.
Decorating existing services
A decorator intercepts an existing service and can replace, wrap, or augment it.
The original instance is available as $delegate.
Framework configuration is a typed declarative object. Run blocks have access to
runtime services.
app.config({$http:{defaults:{headers:{common:{'X-App':'myApp'},},},},});// Run — services are available
app.run(['$rootScope','$state',function($rootScope,$state){$rootScope.on('$stateChangeError',()=>$state.go('error'));},]);
$injector API reference
Method
Description
$injector.get(token)
Returns the service instance for token. Throws if not found.
$injector.has(token)
Returns true if a provider for token is registered.
$injector.invoke(fn, self?, locals?)
Calls fn with injected arguments. Optionally overrides specific tokens with locals.
$injector.instantiate(Type, locals?)
Instantiates a constructor with new, injecting its dependencies.
$injector.annotate(fn)
Returns the array of dependency names for fn.
$injector.loadNewModules(mods)
Loads additional modules into a running injector.
3.6 - Modules: organizing your AngularTS application
Learn how AngularTS modules group controllers, services, directives, and config into composable units that the injector loads at bootstrap.
Every AngularTS application is assembled from one or more modules. A module
is a named container that registers services, directives, controllers, filters,
and configuration blocks. Modules do not execute any code themselves — they
record recipes that the injector later uses to construct and wire together your
application. This separation between declaration and instantiation is what makes
AngularTS applications easy to test and compose.
Create and retrieve modules
Use angular.createModule() to create or replace a module. Its dependency list
defaults to an empty array. Use angular.getModule() to retrieve a module that
is already registered.
TypeScript
// Create a new module with no dependencies
constapp=angular.createModule('myApp',[]);// Retrieve an already-registered module
constsame=angular.getModule('myApp');
JavaScript
// Create a new module
varapp=angular.createModule('myApp',[]);// Retrieve it elsewhere (e.g., in another file)
varsame=angular.getModule('myApp');
Warning: Calling angular.createModule('myApp') a second time replaces the
existing module. Use angular.getModule('myApp') when you only need to add
registrations to the existing module.
Module registration methods
The NgModule instance returned by
angular.createModule() or angular.getModule() exposes a fluent API for registering every kind of
application artifact. All methods return the same module so calls can be
chained.
.controller(name, fn)
Registers a controller constructor. The controller is instantiated by
$controller and receives a child scope.
.service(name, ctor)
Registers a singleton service constructed with new. The constructor is
instantiated once and reused.
.factory(name, fn)
Registers a factory function whose return value becomes the singleton. The
function must not return undefined.
.provider(name, type)
Registers a full provider. The provider’s get method is called to produce the
service instance. Providers can expose configuration methods accessible during
the config phase.
.value(name, val)
Registers an arbitrary value as a service. Values cannot be injected during the
config phase.
.constant(name, val)
Registers a constant that is available during both the config and run phases and
cannot be overridden by a decorator.
.directive(name, fn)
Registers a directive factory for the $compile provider.
.filter(name, fn)
Registers a filter function for use in templates and $filter.
.config(settings)
Declares typed configuration for built-in services before they are instantiated.
Configuration keys use the same names as their runtime injectables. Application
code does not inject or configure provider objects directly.
.run(fn)
Runs after the injector has been created. Services are available. Use this for
one-time initialization logic.
.router(definition)
Registers one
RouterModuleDeclaration
or a readonly forest. Literal route names, parameters, children, and resolves
are inferred into the returned typed router module. Custom runtimes include
router implementation code only when the router runtime is composed into the
build.
A complete module example
The following example creates a module, registers a constant, a service, a
controller, and a config block, then composes that module with a second utility
module.
The requires array lists the names of other modules that must be loaded before
the current one. The injector resolves the entire dependency graph recursively
before running any config or run blocks.
'ng',// AngularTS core — always auto-loaded but explicit is fine
'util',// local utility module
'ngRoute',// third-party module
]);
Modules are loaded in dependency order, depth-first. If two modules declare the
same service, the last one registered wins.
The built-in ng module
The ng module is registered automatically when the
Angular class is instantiated. You
never need to declare it as a dependency — it is always prepended to the
bootstrap module list. It provides every core service and directive, including:
Token
Description
$rootScope
The root of the scope hierarchy
$compile
Template compiler
$http
HTTP client
$interpolate
Template interpolation
$parse
Expression parser
$filter
Filter registry
$animate
Animation support
$location
URL management
$machine
Reactive mode machines
$state
Router state service
$eventBus
Pub/sub messaging
Tip: Splitting your application into small, focused modules makes unit
testing dramatically easier. Each module can be loaded in isolation with
angular.injector(['ng', 'myModule']), giving you a fully functional injector
without bootstrapping the DOM.
Special module methods
Beyond the standard registration methods,
NgModule supports several
higher-level conveniences:
These methods create typed provider-registration commands. The injector applies
them through its private registry during bootstrap; the registry is not
injectable application state.
3.7 - Scopes, data binding, and the scope hierarchy
Explore how AngularTS scopes work as reactive ES6 Proxies, how the hierarchy flows from $rootScope, and how to use events and watchers effectively.
A scope in AngularTS is the glue between the model and the view. It is a
plain JavaScript object wrapped in an ES6 Proxy so that property assignments
automatically trigger DOM updates without any manual notification. Every
controller, directive, and component operates against a scope, and those scopes
are organized into a tree rooted at $rootScope.
What a scope actually is
When you write $scope.count = 0 in a controller, AngularTS is not storing
count on a special object — it is storing it on an ordinary JavaScript object.
The Proxy layer intercepts the set trap and, whenever a watched property
changes, schedules the affected binding listeners to re-evaluate on the next
microtask.
set(target,property,value,proxy):boolean{// ... after storing the value ...
constlisteners=this._watchers.get(property);if(listeners){this._scheduleListener(listeners);// queues a microtask flush
}returntrue;}
The result is that you write natural JavaScript assignments and the DOM stays in
sync — no $apply() call required for synchronous code paths.
Creating scopes
Scopes are created by the framework, not by application code. The injector
creates $rootScope when the ng module loads. Each ng-controller directive,
isolate directive, and ng-repeat iteration creates a child scope derived from
its parent.
You can create child scopes manually when building custom directives:
constchild=$scope.new();// Isolate child — does not inherit watchable properties
constisolate=$scope.newIsolate();// Transcluded child — linked to an outer parent scope
consttranscluded=$scope.transcluded(outerScope);
Info:$scope.new() sets the new scope’s prototype to the parent’s
underlying target object. Reading a property that does not exist locally will
walk the prototype chain, just like plain JavaScript objects.
The scope hierarchy
Every scope holds a reference to root (the root scope) and parent (the scope
that created it). Child scopes are tracked in the _children array.
ng-scope requires a non-empty name. It names the current scope; it does not
create a new one. Find that scope from JavaScript:
// Find it from $rootScope
constpanel=$rootScope.searchByName('userPanel');// Or via the angular instance
constpanel2=angular.getScopeByName('userPanel');
Scope events
Scopes communicate across the hierarchy through a lightweight event system.
Events travel either upward or downward — never both directions at once.
emit — upward
// Fire an event from a child scope upward toward $rootScope
$scope.emit('user:selected',{id: 42});// A parent scope listens
$scope.on('user:selected',function(event,user){console.log('Selected user ID:',user.id);});
broadcast — downward
// Broadcast from $rootScope down to all descendants
$rootScope.broadcast('config:updated',newConfig);// Any child scope can listen
$scope.on('config:updated',function(event,config){$scope.theme=config.theme;});
The on method returns a deregistration function. Call it when the listener is
no longer needed:
// Later, when the listener should stop
deregister();
Stopping propagation
emit propagation can be stopped before it reaches $rootScope:
event.stopPropagation();// stops upward travel
event.preventDefault();// signals that the default was prevented
});
Watching scope properties
In the reactive proxy model, you rarely need watch — template bindings update
automatically. Use watch when you need to run side-effect code in response to
a data change, such as syncing to an external library or triggering a service
call.
constderegister=$scope.watch('searchQuery',function(newValue,target){if(newValue){searchService.query(newValue).then((results)=>{$scope.results=results;});}});// Lazy watch — skips the initial call, only fires on changes
$scope.watch('count',function(newValue){console.log('count changed to',newValue);},/* lazy */true,);// Stop watching when done
deregister();
Note:watch on a constant expression (a literal string or number) is
evaluated once and the deregistration function is a no-op. The runtime detects
this via the _constant flag on the compiled expression.
Watchable expression types
watch accepts any expression that $parse can compile — identifiers, member
expressions, binary comparisons, function calls, array and object literals, and
conditional expressions.
$scope.watch('count > 10',handler);// binary expression
$scope.watch('items.length',handler);// member on array
$scope.watch('getTotal()',handler);// function call
$scope.watch('a || b',handler);// logical expression
$scope.watch('[firstName, lastName]',handler);// array expression
Merging scope state
// merge copies properties from a plain object into the scope
$scope.merge({name:'Alice',age: 30});
Destroying a scope
When a controller’s element is removed from the DOM, AngularTS calls destroy()
on the associated scope. You can also call it manually. Destroying a scope:
Broadcasts $destroy downward to all children.
Removes all watcher registrations for this scope’s ID from the shared
_watchers map.
Clears all on listeners.
Removes itself from the parent’s _children array.
Sets _destroyed = true and nulls internal references on the next microtask.
consttempScope=$rootScope.new();tempScope.data=loadSomething();// When done:
tempScope.destroy();
Warning: Do not read from or write to a scope after calling destroy().
The scope’s property map is reduced to a minimal tombstone on the next
microtask.
Relationship to controllers and directives
Each ng-controller directive asks $scope.new() to create a child scope and
passes it to the controller constructor as $scope. The controller writes model
properties directly onto that scope:
$scope.count=0;$scope.increment=function(){$scope.count+=1;// Proxy intercepts; DOM updates automatically
};}]);
Directives with scope: true get an inherited child scope; directives with
scope: {} get an isolate scope. The isolate scope does not walk the parent
prototype chain for watchable properties, but it can still receive values
through explicit bindings.
Scope API quick reference
Method
Description
$scope.new(child?)
Creates an inherited child scope.
$scope.newIsolate(instance?)
Creates an isolate child scope.
$scope.watch(expr, fn, lazy?)
Registers a watcher; returns a deregistration function.
$scope.on(name, fn)
Registers an event listener; returns a deregistration function.
$scope.emit(name, ...args)
Fires an event upward through the hierarchy.
$scope.broadcast(name, ...args)
Fires an event downward to all descendants.
$scope.merge(obj)
Copies enumerable properties from obj into the scope.
$scope.destroy()
Tears down the scope and all its watchers.
$rootScope.searchByName(name)
Finds a scope by its scopeName anywhere in the tree.
$scope.getById(id)
Finds a scope by numeric ID within the subtree.
3.8 - Templates, interpolation, and expression parsing
Master the {{ }} interpolation syntax, one-time bindings, filter pipes, and how $interpolate and $parse power programmatic expression evaluation.
Templates in AngularTS are ordinary HTML files augmented with a handful of
special syntax forms. The most fundamental is interpolation: the
{{ expression }} delimiters that tell the template compiler to evaluate an
expression against the current scope and render its result as text. The
$interpolate service handles this translation at compile time, and the
reactive proxy system ensures the DOM re-renders whenever the underlying data
changes.
Interpolation syntax
Place any valid AngularTS expression between {{ and }} to render its current
value:
The expression is parsed by $parse, evaluated against the current scope, and
the result is stringified and inserted into the text node. If the expression
throws, $interpolate catches and routes the error through $exceptionHandler.
What expressions can contain
AngularTS expressions are a safe subset of JavaScript. They support:
Property access
user.profile.name, items[0].label
Method calls
format(date), list.filter(fn)
Arithmetic and comparison
count * price, score >= 100
Logical operators
isAdmin || isMod, name && name.length
Ternary
flag ? 'yes' : 'no'
Array and object literals
[a, b, c], { key: value }
Expressions cannot contain new, delete, typeof, void, assignment
operators, or any access to the global scope. This sandbox keeps templates safe
against inadvertent or malicious code execution.
Filters in templates
Apply a filter to an expression using the pipe character |. Filters transform
the value before it is rendered. You can chain multiple filters:
<p>Price: {{ product.price | currency:'USD' }}</p><!-- Sort a list by a property --><ling-repeat="item in items | orderBy:'name'">{{ item.name }}</li><!-- Limit a list and filter it by a search term --><ling-repeat="item in items | filter:search | limitTo:10">{{ item.title }}</li><!-- Date formatting --><span>{{ event.date | date:'longDate' }}</span><!-- Chained filters --><p>{{ message | uppercase | limitTo:50 }}</p>
Filters receive the value as their first argument and any parameters after the
colon as later arguments. They are pure functions — given the same input, they
always produce the same output, which lets the reactive system cache their
results.
One-time bindings
Prefix an expression with :: to evaluate it once and then release the binding.
This is a performance optimization for data that is set during initialization
and never changes:
<h1>{{ ::pageTitle }}</h1><p>User ID: {{ ::user.id }}</p><!-- Inside ng-repeat — binds each item once --><ling-repeat="item in ::staticList">{{ ::item.name }}</li>
Tip: Use one-time bindings for any data that is effectively constant after
initialization: configuration values, user identity, translated strings, or
items loaded once at startup. They reduce the number of active watchers and
improve scroll performance in long lists.
The $interpolate service
The $interpolate service is what $compile uses internally to process
interpolation expressions in templates. You can use it directly when you need to
evaluate an interpolation string programmatically.
'$scope','$interpolate',function($scope,$interpolate){// Compile a template string into a reusable function
constgreetFn=$interpolate('Hello, {{ name }}! You have {{ count }} messages.');$scope.name='Alice';$scope.count=5;// Evaluate the template against a context object
constresult=greetFn($scope);// → "Hello, Alice! You have 5 messages."
// The interpolation function exposes the raw expressions it found
console.log(greetFn.expressions);// ['name', 'count']
console.log(greetFn.exp);// 'Hello, {{ name }}! You have {{ count }} messages.'
},]);
$interpolate parameters
text: string,mustHaveExpression?: boolean,// return undefined if no {{ }} found
trustedContext?: SceContext,// SCE context for security checking
allOrNothing?: boolean,// return undefined if any expression is undefined
):InterpolationFunction|undefined
The returned
InterpolationFunction
is callable with (context, cb?). When cb is provided, $interpolate sets up
watch calls on the scope so cb is invoked whenever any expression in the
template changes.
// Watch mode: cb fires on every change
fn($scope,(newValue)=>{titleElement.textContent=newValue;});
Customizing the delimiters
The default delimiters are {{ and }}. Configure them with
module.config({ $interpolate: ... }):
After this change, templates use {[ expression ]} instead. This is useful when
you are embedding AngularTS in a server-side template engine that already uses
{{ (such as Jinja2 or Handlebars).
The $parse service
$parse is the lower-level primitive beneath $interpolate. It compiles a
single expression string into a
CompiledExpression function
that can be called with a context:
'$scope','$parse',function($scope,$parse){$scope.user={name:'Bob',score: 42};// Compile once, call many times
constgetScore=$parse('user.score');console.log(getScore($scope));// 42
// Expressions with assign support two-way binding
constnameParse=$parse('user.name');nameParse.assign($scope,'Carol');console.log($scope.user.name);// 'Carol'
// Parsed expressions are cached — repeated calls with the same
// string return the same compiled function object
constgetScore2=$parse('user.score');console.log(getScore===getScore2);// true
},]);
Compiled expression properties
Property
Type
Description
_constant
boolean
true if the expression is a literal constant
_literal
boolean
true if the expression is a simple literal
_assign
function?
Present for l-value expressions; assigns a value to the context
_inputs
any[]?
Sub-expressions tracked for change detection
_decoratedNode
BodyNode
The annotated AST for this expression
Security and binding policies
Plain interpolation produces text. HTML bindings such as ng-bind-html use
$compile.htmlPolicy to sanitize strings or reject them before insertion.
Configure this callback with your application’s HTML sanitizer:
Without an HTML policy, nonempty HTML strings are rejected. The framework
also accepts native TrustedHTML. See binding policies
for URL and script contexts.
Escaping interpolation delimiters
If you need to display literal {{ in a template without triggering
interpolation, escape each character with a backslash:
<p>The syntax is \{\{ expression \}\}</p>
$interpolate unescapes these sequences before returning the final string.
3.9 - Glossary
Plain-language definitions of browser and AngularTS terms used throughout the documentation.
Application: The DOM region started by ng-app or angular.bootstrap().
Attribute: A setting written inside an HTML opening tag.
Binding: A connection that transfers a value between state and the DOM.
Bootstrap: Start an application on a specific DOM element.
Compiler: The AngularTS runtime that discovers directives and connects them
to DOM nodes. It does not produce a build artifact.
Component: A registered, reusable UI unit with inputs, behavior, and a view.
Controller: An object or function that supplies state and behavior to a DOM
region or component.
Dependency injection: Requesting a registered value by token instead of
constructing or finding it directly.
Directive: An HTML attribute or element that adds AngularTS behavior.
DOM: The browser’s live object tree for an HTML document.
Expression: A small JavaScript-like statement evaluated against a scope in
an HTML template.
Module: A named registry for an application’s components, directives,
services, configuration, and dependencies.
Reactive: Able to notify dependent bindings when a value changes.
Scope: The reactive state and lifecycle context available to a compiled DOM
region.
Service: Shared behavior or data created and supplied by the injector.
Template: HTML containing AngularTS directives and expressions.
Token: The name or value used to request a dependency.
Typed view: A TypeScript function that constructs component DOM with typed
tags, properties, controller access, and reactive readers.
View: The DOM output rendered by a component, directive, or router state.
4 - Task guides
Implement complete AngularTS workflows with explicit state, failure, lifecycle, accessibility, and verification behavior.
These guides extend one task-board application. They explain implementation
boundaries rather than presenting isolated snippets.
Start with server-rendered HTML
Do not start with an SPA. Keep the model, rules, URLs, and rendering together on
the server. Send useful HTML first, then add AngularTS to the smallest region
that needs browser behavior.
Even when using the router, prefer server-rendered route fragments. Move the
model into the browser only for the most complex client-owned features. When the
browser takes over model consistency and compilation, use typed programmatic
views as the primary view style. REST services, explicit caching, and a
browser-facing security policy also become primary at that same threshold. Until
then, prefer normal HTTP pages, forms, and server-rendered fragments.
Apply this decision per module. A server-rendered login and public site can live
beside a dashboard SPA in the same application. The dashboard’s complexity does
not require every other module to move into the browser.
Start with the complete tutorial if you have not built an
AngularTS component.
4.1 - Keep the application on the server
Render HTML and keep domain logic on the server until the browser must own a genuinely complex application model.
Start with HTML from the server
Do not start by building an SPA. Render the useful page on the server and use
normal links and forms. Keep domain models, validation, authorization,
transactions, URLs, and HTML generation together for as long as possible.
This is the simplest way to keep the model consistent. The server reads current
data, applies the rules, and renders the result in one place. The browser
receives HTML it can display immediately. There is no second model to
synchronize, no client startup request for content the server already had, and
no duplicated loading, routing, validation, and error behavior.
HTML and server rendering are the default application architecture. AngularTS is
there to add behavior where the browser can improve an already useful page.
Add behavior without taking over the page
Put ng-app around the smallest region that needs local interaction. Use scope,
events, effects, HTTP directives, and DOM references for immediate browser
behavior while the server continues to own the page and its durable model.
Keep these on the server:
Initial and durable application state
Validation and authorization
Data loading and mutations
Primary URLs and navigation
HTML for complete pages and reusable fragments
Error, empty, permission, and maintenance responses
Do not fetch JSON after startup merely to rebuild HTML that the server could
have sent in the first response.
Keep router content server-rendered
Using the AngularTS router does not require moving rendering or the model into
the browser. Let the router coordinate transitions and component lifetimes, but
keep route content as server-rendered HTML fragments while that remains
practical.
This preserves one server model while still allowing faster transitions,
long-lived browser resources, and local interaction state. A router is not a
reason to duplicate server templates as client templates.
Move the model only as a last resort
Move model ownership into the browser only when the feature genuinely needs it.
Examples include substantial offline work, high-frequency local editing, complex
cross-screen state, or an application-like workspace whose model must remain
alive independently of server navigation.
Before making that move, confirm that server-rendered fragments, focused
interactive regions, or a small client model cannot solve the problem. Most
pages should never cross this line.
Move compilation with the model
Once the browser owns the model, it also owns model consistency and view
compilation. Do not keep a complex client model on one side and loosely typed
HTML template bindings on the other.
For a client-owned screen, optional typed programmatic views can keep model access, DOM properties, events, and
generated structure in the same checked TypeScript code. HTML templates remain
available for client-owned screens and are the default for server-owned pages,
server-rendered fragments, and small enhancements.
Move the data boundary with the model
Before the browser owns the model, prefer simple HTTP: normal navigation, form
submissions, and server-rendered HTML fragments. The server already has the
authoritative data and can return the final representation directly.
Once the browser owns a complex application model, REST services become the
primary server boundary. At that point the frontend also needs an explicit cache
strategy and security policy:
Define stable request, response, validation, pagination, and error contracts.
Decide which model owns each cached record and how writes invalidate it.
Handle stale data, concurrent writes, retries, and offline state deliberately.
Define credential handling, authorization failures, CSRF or token policy,
cross-origin access, content security, and how client code avoids secrets.
Do not introduce this REST, cache, and security machinery for a page that can
submit a form or request a server-rendered fragment. It becomes primary only
when the browser has genuinely taken ownership of the model.
Let modules use different architectures
The ownership decision is not application-wide. AngularTS applications are
multimodular, and each module or ng-app root can stop at a different point in
the progression.
For example:
Area
Appropriate architecture
Sign-in and account recovery
Server-rendered HTML forms and normal HTTP
Public content and checkout
Server pages with focused AngularTS enhancements
Dashboard workspace
Client router, browser-owned model, REST, cache policy, and a chosen view style
Reports and downloads
Server-generated files and normal browser navigation
The login form does not need to become part of the dashboard SPA. The dashboard
can start as its own ng-app after authentication and own only the screens
whose complexity justifies it. Both modules can share the same server session
and authorization rules without sharing a frontend architecture.
This keeps complexity contained. A client-owned dashboard does not force public,
authentication, settings, or support pages to adopt its router, model cache, or
rendering strategy.
Use this order
Start here
Escalate only when needed
Server-rendered page
Small AngularTS-enhanced region
Normal links and forms
Server-rendered router fragment
Server-owned model
Small browser interaction model
Simple HTTP pages and fragments
REST, cache, and browser security policy
Server HTML templates
Optional typed view when stronger checks are useful
One server-rendered module
One client-owned module where justified
At every step, stop when the current approach solves the user problem. The last
row is not a target architecture. It is the option for the minority of features
that truly need the browser to own the application model.
Verify the decision
For each client-owned model, write down why a server-rendered page, server
fragment, or focused enhancement is insufficient. Test direct navigation,
JavaScript failure, authorization, validation, refresh, Back and Forward, stale
data, deployment rollback, and recovery after a failed request.
4.2 - Structure an application
Separate AngularTS registration, UI state, domain operations, and external resources without adding speculative layers.
Start with server rendering
Follow the server-first guideline.
Keep domain models, validation, authorization, navigation, and HTML rendering
together on the server. Render the useful document and initial state before
AngularTS starts.
Place an AngularTS root around the smallest region that needs browser behavior.
Do not create a browser copy of the domain model merely to make the page
interactive. Local selection, disclosure, pending state, and DOM behavior can
remain small browser concerns while durable state stays on the server.
Do not choose an SPA as a default project shape. If client routing is justified,
continue loading server-rendered route fragments while the server can own the
model. Move the model into the browser only when complex offline, editing,
cross-screen, or long-lived workspace behavior requires independent client
ownership.
When that move happens, the browser becomes responsible for model consistency
and view compilation. If compile-time checks between that model and its DOM are
valuable, the module can use optional typed programmatic views. HTML templates
remain supported.
The same threshold changes server communication. Server-owned pages should use
normal HTTP navigation, forms, and rendered fragments. A browser-owned model
needs REST contracts, explicit cache ownership and invalidation, and a complete
browser security policy. Introduce those systems together with frontend model
ownership, not before it.
Mix architectures by module
Do not force one rendering architecture across the product. Use separate modules
or ng-app roots for areas with different ownership needs.
A sign-in module can remain a server-rendered HTML form. After authentication, a
dashboard module can bootstrap a client router, keep a complex model in the
browser, and use REST and caching. It can independently choose HTML or typed
programmatic views. Public pages, reports, and account recovery can continue
using server rendering and normal navigation.
Keep shared concerns on the server: session identity, authorization, domain
rules, and durable state. Share frontend services only when two client-owned
modules genuinely need the same browser resource or model.
Start from ownership, not file types
A component owns DOM-facing state and its view lifetime. A model owns shared
reactive domain state. A service owns an operation or external resource. A
module records how those pieces are assembled.
Use one feature directory until a capability is independently reusable:
Keep registration free of request execution and DOM access. Registration
describes recipes; runtime methods perform work.
Keep view state local
A task-list controller may own loading, error, selection, and filter text.
Move task data to a named model only when multiple component roots need reactive
access to the same records. Move loading into a repository only when the
repository intentionally deduplicates or caches requests.
Do not introduce “manager” or “facade” layers without a lifetime or policy they
own. A forwarding class adds indirection but no boundary.
Verify the structure
A feature passes the ownership test when its component can be destroyed without
leaking resources, its domain logic can be tested without a DOM, and its
repository can be replaced in a test without changing view code.
4.3 - Build and validate a form
Model AngularTS form submission, validation visibility, duplicate prevention, and accessible recovery as one workflow.
Separate validity from validation visibility
A field can be invalid before the user has interacted with it. Show field
feedback after blur or a failed submit, but always prevent invalid submission.
<formname="taskForm"ng-submit="submit(taskForm)"novalidate><labelfor="task-title">Task title</label><inputid="task-title"name="title"ng-model="draft.title"requiredaria-describedby="task-title-error"/><pid="task-title-error"role="alert"ng-show="submitted && taskForm.title.$invalid"> Enter a task title.
</p><buttonng-disabled="saving">Add task</button></form>
The controller owns submitted, saving, and the editable draft. The
repository owns the write.
Do not clear the draft before the server confirms success. Disable or reject
repeated submission while a non-idempotent write is pending. Return focus to the
first invalid field after a failed submit and announce server failures
separately from field validation.
Use browser constraints for semantics, AngularTS form state for view
coordination, and server validation as the authority. A disabled button does not
enforce a business rule.
4.4 - Load server data
Own AngularTS HTTP requests, cancellation, stale-result protection, and explicit screen states.
Represent request state explicitly
Do not use tasks.length === 0 to mean both “not loaded” and “loaded empty.”
The timeout promise should resolve when the component is destroyed or a newer
query supersedes the request. Cancellation saves work; a request sequence or
identity check still protects against transports that complete while
cancellation is racing.
Set loading before starting. On success, replace the collection property so
typed readers observe the commit. In finally, clear loading only if the
completing request is still current. Render loading, stale data, empty success,
recoverable error, and permission failure deliberately.
Use the $http guide for interceptors, defaults,
caching, and response contracts.
4.5 - Add routing
Register AngularTS route trees, choose resolve boundaries, and make navigation cleanup and failure behavior explicit.
Do not default to client-side routing
Use server routes and ordinary links first. They provide addressability,
history, streaming HTML, metadata, authorization boundaries, and resilient
navigation without a client router.
Introduce the AngularTS router when multiple screens are intentionally owned by
one bootstrapped application and preserving client state or resources between
those screens materially improves the product. A client router is not required
for AngularTS components embedded in server-rendered pages.
Keep route snippets server-rendered for as long as possible. Let the router own
the transition, URL state, and component lifetime while the server still renders
the route HTML from its authoritative model. Do not move the model and rendering
into the browser merely because navigation no longer reloads the document.
If a route eventually needs a complex browser-owned model, move compilation with
that model. A typed programmatic view is available when the screen benefits from
compile-time checks between the model and DOM; it is not required by the router.
That transition also makes REST services, frontend cache consistency, and
browser-facing security policy primary concerns. A router using server-rendered
fragments can continue using simple HTTP; client navigation alone does not
justify building a separate API and cache layer.
Scope the router to the module that needs it. A dashboard can be a full SPA
under its own ng-app while sign-in, recovery, public pages, and downloads
remain server-rendered routes. Adding a dashboard router does not make the
entire application a client-owned router tree.
Register components as screens
The router is part of the built-in runtime; do not add a speculative ng.router
module dependency.
Use stable state names as application identifiers. URLs may evolve; state names
are referenced by links, transitions, tests, and diagnostics.
Resolve identity or authorization data that must exist before the screen can be
valid. Load optional panels and large collections inside the component so the
route can render a shell. Parent states own shared layout and resolves; children
should not refetch parent-owned data.
Test direct URL entry, back/forward, invalid parameters, same-state parameter
changes, rejected resolves, and leaving during pending work. Continue with the
routing overview.
4.6 - Handle errors
Classify AngularTS domain, transport, transition, and programming failures and give each one the correct owner.
Classify before rendering
Validation and domain rejection are expected outcomes near the action.
HTTP timeout, abort, offline, and non-success status belong to the request
owner.
Route resolve failure belongs to transition policy.
Unexpected exceptions belong to $exceptionHandler and diagnostics.
Do not convert every rejection to [], null, or false. That destroys
information and commonly renders an empty success state after a real failure.
Wrap low-level errors with the operation name while retaining the original
cause. The UI should receive a stable category and recovery action, not a stack
trace or server payload. Logs may include release, route, operation, and
correlation identifiers but must exclude credentials and private data.
A repository may retry a safe idempotent read. A component should own the retry
button. Global interceptors should refresh credentials or normalize protocol
behavior, not retry every request indiscriminately.
Unexpected exceptions should reach the
$exceptionHandler reference. Test that
expected failures remain recoverable and unexpected exceptions are reported.
4.7 - Test application behavior
Allocate AngularTS tests across plain logic, typed contracts, browser integration, lifecycle, and failure boundaries.
Use the narrowest layer that can fail
Test transformations and domain transitions as plain TypeScript. Compile typed
component views to catch invalid model properties, event types, and DOM property
names. Use a browser for compilation, directives, focus, routing, and reactive
rendering.
test('task form prevents duplicate submission',async({page})=>{awaitpage.goto('/tasks');awaitpage.getByLabel('Task title').fill('Write tests');awaitpage.getByRole('button',{name:'Add task'}).dblclick();awaitexpect(page.getByRole('listitem')).toHaveCount(1);});
Select by role, label, visible text, or stable application identifiers. Scope
IDs, generated comments, internal CSS classes, and scheduler timing are
implementation details.
For each asynchronous screen, cover loading, stale data, empty success,
recoverable failure, retry, cancellation on navigation, and late completion.
Control server responses rather than adding sleeps. For resource owners, enter
and leave repeatedly and assert listener and connection counts return to
baseline.
Run critical browser contracts against the production bundle.
4.8 - Troubleshoot an application
Locate AngularTS bootstrap, injection, compilation, reactive, routing, and request failures by testing boundaries in order.
Find the first failed boundary
Confirm the JavaScript module loaded.
Confirm registration completed before automatic or manual bootstrap.
Confirm ng-app names the created module.
Read the first AngularTS error, not the cascade.
Confirm injected token order matches function parameters.
Confirm the owning scope/controller contains the value read by the view.
Inspect transport status before changing rendering code.
Raw interpolation means bootstrap or compilation did not complete. “Module not
available” usually means boot order or spelling. “Unknown provider” means the
token is absent from the loaded module graph.
For typed views, verify changing positions receive a function reader rather than
a snapshot. Call keyed item readers inside nested bindings. Replace collection
properties in controller methods. ESM applications import tag factories such as
div and button directly from @angular-wave/angular.ts; no-build UMD
applications use angular.tags. Controller typing is supplied to
component<T>(); use DOM property names such as htmlFor.
Remove sibling features while preserving the failing owner, data, and timing. Do
not hide races with timeouts. Use the error catalog
after identifying the failed boundary.
4.9 - Production and deployment
Produce, verify, deploy, observe, and roll back an AngularTS artifact without relying on development behavior.
Produce one immutable artifact
Install from the lockfile, typecheck source and tests, validate generated
bindings, run browser tests, and build once. Promote the same artifact through
environments. Rebuilding after approval changes the release.
Fingerprint immutable assets. Revalidate HTML and route manifests so they
reference current fingerprints. Version service-worker caches and remove
obsolete entries only after the new worker can serve a complete application.
Verify production behavior
Smoke-test generated output with development fallbacks disabled. Cover direct
route entry, refresh on nested URLs, CSP, API credentials, loading and failure
states, offline behavior, and service-worker upgrade from the previous release.
Design rollback before deployment
Retain the previous artifact. Determine whether storage migrations, server
contracts, or service-worker caches make rollback incompatible. Prefer
additive data changes.
Attach a release identifier to errors and operations. After deployment, verify
asset integrity, critical routes, request latency, error rate, and one synthetic
workflow. Stop promotion or roll back from explicit thresholds.
Define an AngularTS browser contract from required Web APIs, compiled output, fallbacks, and production tests.
List browser families and versions, mobile requirements, assistive-technology
coverage, and optional capabilities. Framework support does not automatically
provide WebTransport, service workers, storage, workers, or WebAssembly
everywhere.
Detect the API used, not a browser brand. Keep fallback selection in one service
so components consume domain events rather than transport differences.
Test production output: cold load, direct route entry, back/forward, storage
denial, offline transition, reduced motion, keyboard navigation, and
service-worker upgrade. Disable capabilities to test fallbacks even when CI
supports them.
When no fallback exists, render an actionable unsupported state before
constructing the service. A runtime exception is not a support policy.
4.11 - Accessibility
Preserve semantics, focus, validation, announcements, and motion preferences across AngularTS structural and route updates.
ng-if removes content and destroys focused descendants. Before closing a
dialog, menu, or editor, choose the element that regains focus. ng-show
retains the subtree; verify hidden descendants cannot remain in keyboard order.
Use native buttons, links, labels, headings, and lists before ARIA. Attach
AngularTS events to those controls instead of rebuilding their keyboard behavior
on generic elements.
Associate errors with controls using aria-describedby. Show invalid state
after interaction or submission, retain the invalid value, focus the first
invalid control after failed submission, and provide a summary for long forms.
Server failures are separate from field constraints.
Use a polite live region for completion or background updates and an alert for
failures requiring immediate attention. Do not place an entire keyed list in a
live region.
Test direct route entry, route transitions, keyed reordering, loading
replacement, reduced motion, zoom, keyboard-only operation, and a screen-reader
workflow. Automated rules cannot determine whether focus movement makes sense.
Use stable message identifiers and named placeholders. Do not concatenate
translated fragments or use English text as an identifier. Choose plural forms
from locale rules rather than count === 1.
AngularTS formatting filters delegate to Intl. Pass the application locale and
explicit currency or time-zone policy; browser defaults may differ from user
settings.
<span>{{ total | currency:user.locale }}</span><time>{{ dueAt | date:user.locale }}</time>
A locale service should deduplicate catalog requests, cache by locale and
catalog version, and expose loading and fallback state. Do not render raw
message identifiers while loading.
Set document or feature lang and dir. Prefer logical CSS properties. Test
long labels, narrow screens, mixed-direction names, non-Latin input, plural edge
cases, currency minor units, and dates around daylight-saving transitions.
Locale switching should invalidate message and formatter readers without
rebuilding unrelated domain state.
4.13 - Application security
Apply AngularTS trust boundaries to templates, typed properties, URLs, requests, storage, workers, and service workers.
External sources include URL parameters, forms, storage, server responses,
realtime messages, worker messages, and cross-window events. Sensitive sinks
include HTML, executable URLs, navigation, credentials, logs, and persisted
state.
Prefer interpolation, text children, and typed DOM properties. Use
ng-bind-html only for markup that passed application sanitization. A trust
override must be local and reviewable; never trust a value merely because it
came from your server.
Hidden routes, disabled buttons, and client guards improve UX but do not
authorize operations. The server must validate identity, permission, input
shape, size, and state transition.
Configure credentials and XSRF behavior for known origins. Reject unexpected
redirect and content types at the repository boundary. Store the minimum browser
data, validate versioned records on read, and never persist bearer credentials
for convenience.
Deploy HTTPS and a specific Content Security Policy. Review worker and
service-worker scope, allowed connection origins, update behavior, and rollback.
Security failures should remain visible rather than trigger broad policy
relaxation.
4.14 - Observability
Correlate AngularTS navigation, requests, workflows, rendering outcomes, and unexpected failures without recording sensitive state.
Assign a stable operation name and correlation ID at the user-action or route
boundary. Record start, terminal outcome, duration, release, and route. Do not
log every reactive read or scope write; volume hides causality and changes
timing.
Expected domain and transport failures should produce classified outcomes.
Unexpected exceptions should reach $exceptionHandler with their cause. Redact
credentials, request bodies, user-entered text, and model snapshots.
Carry one correlation ID through route transition, request, retry, and workflow
command. Record cancellation separately from failure. For realtime connections,
record state transitions and reconnect exhaustion, not every message.
After deployment, verify release tagging, source-map policy, critical routes,
error rate, request latency, and one synthetic path. Every alert needs an owner
and actionable threshold.
5 - View guides
Choose and learn AngularTS HTML templates or type-safe component views, which share the same module and component lifecycle.
A view is the DOM output of a component or directive. AngularTS offers two
authoring styles:
HTML templates keep static structure
in markup and use directives for behavior.
Programmatic views construct DOM in
TypeScript and type-check controller-to-view bindings.
Read how to choose. Both styles register
on normal modules. A server-rendered module can use HTML while a client-owned
dashboard module uses typed programmatic views in the same application.
5.1 - HTML template views
Render a component with readable HTML, interpolation, directives, controller methods, and explicit bindings.
What you will build
Render the task count as a small template component.
The binding is the component’s public input. $ctrl is the component controller
in its template. Interpolation converts the value to displayed text and keeps
that text current.
Templates are a strong default for mostly static HTML, designer-edited markup,
and teams that want structure visible without reading JavaScript.
A function in a child or property position is a reactive reader. AngularTS runs
it, records the controller values it reads, and updates that DOM position when
those values change. A plain value is a snapshot.
Typed views are useful when compile-time model-to-view checks, dynamic element
construction, native DOM properties, or direct browser API integration matter.
Normal array.map() creates a one-time DOM snapshot. each() observes the
collection, preserves nodes by a stable unique key, moves existing nodes when
order changes, and disposes nodes for removed keys. Its renderer receives a
reader because an item object can be replaced while retaining the same key.
Bindings and framework listeners are cleaned up automatically. Register sockets,
channels, timers, observers, and other application-owned resources with
onDestroy().
The browser owns model consistency and compilation
AngularTS adds a small enhancement
A complex screen is primarily TypeScript
Server-rendered markup is reused
Checked model-to-view bindings are required
Normal HTML remains the clearest representation
Native properties and browser APIs dominate
Both choices use module.component(), controller injection, reactive state,
transclusion, routing, and lifecycle cleanup. They can interoperate, but avoid
splitting one complex client-owned model across TypeScript and loosely typed
HTML bindings.
Start with server-rendered HTML, not a client template that recreates the same
page. Keep using HTML for server-owned pages, route fragments, and focused
enhancements. If the browser must take ownership of a complex model, a typed
programmatic view can add compile-time checks between the model and DOM. It is
optional; HTML templates remain supported. Make REST contracts, cache
consistency, and browser security policy primary at the same ownership threshold;
simple HTTP remains preferable before it.
Choose at the module boundary. A server-rendered login module can use HTML while
a dashboard module in the same application uses a client-owned model and its
preferred view style. Do not convert one merely to make it match the other.
Learn AngularTS directives by the job they perform before using the individual directive reference pages.
Directives are easier to learn by role than by name. Start here when you want to
understand how AngularTS extends HTML, then use the Directives
reference for individual attributes and API
details.
Learning path
Overview: how directives are
discovered, compiled, linked, and prioritized.
Data Binding: directives
that synchronize scope data with text, HTML, classes, styles, and form
controls.
Structural Directives:
directives that add, remove, repeat, switch, include, or protect DOM
sections.
Forms: validation, model options,
form state, and messages.
HTTP Directives: declarative
requests and Server-Sent Events from HTML.
Advanced Directives: workers,
WebAssembly, channels, DOM references, accessibility, and event helpers.
Reference lookup
When you already know the directive name, use the per-directive
reference. The reference keeps pages small
and specific, while this section keeps the conceptual grouping that helps new
users build a mental model.
AngularTS advanced directives for Web Workers, WebAssembly, lazy loading, event channels, DOM references, and accessibility — all declarative in HTML.
AngularTS ships several advanced directives that expose modern browser APIs —
Web Workers, WebAssembly, Intersection Observer, and custom event channels —
directly in HTML without requiring JavaScript. These directives are registered
as part of the core ng module and are available in every AngularTS
application.
Controller and scope directives
ng-controller
Attaches a controller function to a section of the DOM, creating a new child
scope populated by the controller.
<divng-controller="TodoController as ctrl"><p>{{ ctrl.title }}</p></div>
Assigns a stable name to the current scope so debugging tools and integration
code can find it later. The name is required. ng-scope does not create a child
or isolate scope.
Initializes scope variables inline in the template. Best suited for simple
one-off values or prototyping.
<divng-init="name = 'World'; count = 0"> Hello {{ name }}, count is {{ count }}
</div>
Tip: Prefer controllers or services over ng-init for any real
application logic. ng-init is ideal for zero-JS demos and quick prototypes.
DOM reference directives
ng-el
Use ng-el when you only need the native DOM element. It is the simple DOM
reference API and is usually the clearest choice for canvas, focus management,
layout measurement, and pointer-driven UI.
<sectionng-controller="BoardController as $ctrl"><canvasng-el="$ctrl.boardEl"></canvas></section>
functionBoardController(){this.boardEl=null;}
ng-el="$ctrl.boardEl" stores the element on the controller. Use this form with
controller-as syntax.
For simple scope shorthand, pass a bare name:
<canvasng-el="boardEl"></canvas>
If the value is omitted, AngularTS uses the element id.
<canvasid="board"ng-el></canvas>
ng-ref
Use ng-ref when you need a component or directive controller reference, or
when you need to assign the reference to a full expression such as
$ctrl.child.
Use ng-ref-read="$element" only when you specifically need the DOM element
through an assignable expression. For the common DOM-only case, prefer ng-el.
functionBoardController(){this.boardEl=null;}
Event and communication directives
ng-channel
Subscribes to a named event channel (backed by $eventBus). When an event is
published on the channel, the expression is evaluated.
<div>Latest: {{ lastNotification }}</div>
ng-listener
Attaches a DOM event listener to the element using the native addEventListener
API — without the overhead of Angular’s event directive wiring.
<div>Scroll-tracked content</div>
ng-inject
Injects a named service directly into the scope, making it accessible in the
template without a controller.
<divng-inject="$http"><!-- $http is now available in this scope --></div>
Lazy loading and performance
ng-viewport
Uses the browser’s Intersection Observer API to run expressions when an element
enters or leaves the viewport. Ideal for below-the-fold loading, lazy request
triggers, analytics markers, and canvas/chart initialization.
<divng-viewporton-enter="loadComments($entry)"on-leave="visible = false"data-viewport-threshold="0.5"data-viewport-margin="200px 0px"><divng-repeat="comment in comments">{{ comment.text }}</div></div>
Use data-viewport-once when the enter expression should run only once:
Expression evaluated when the element intersects the viewport.
on-leave
Expression evaluated when the element stops intersecting.
data-viewport-threshold
Intersection threshold, such as 0.5 or 0, 0.5, 1.
data-viewport-margin
Root margin passed to IntersectionObserver, such as 200px 0px.
data-viewport-once
Disconnects the observer after the first enter callback.
on-enter and on-leave receive $entry and $entries locals from the
Intersection Observer callback.
ng-cloak
Prevents the browser from briefly displaying uncompiled template syntax
({{ }}) before AngularTS bootstraps. Add to the root element or any element
with interpolation.
<divng-cloak><p>Hello {{ user.name }}</p></div>
Note: Always include the CSS rule [ng-cloak] { display: none; } in your
stylesheet when using ng-cloak.
Web Workers and WebAssembly
ng-worker
Runs a JavaScript file in a Web Worker and binds the result back to scope. The
worker communicates via postMessage.
<divng-worker="/workers/sort.js"data-params="{ items: largeList }"on-result="sortedItems = $result"><divng-repeat="item in sortedItems">{{ item.name }}</div></div>
ng-worker
Type:string
Required: yes
Path to the Web Worker JavaScript file.
Use an empty ng-worker with data-handle="name" to reuse a worker registered
through app.worker(name, script). Shared handles are application-owned rather
than terminated with the directive scope.
data-params
Type:expression
Data to pass to the worker as the message payload.
on-result
Type:expression
Expression evaluated when the worker posts a result. $result contains the
worker’s response.
Worker results are not inserted as HTML when this expression is absent.
data-request
Type:boolean data attribute
Use the standard correlated request protocol instead of consuming every stream
message. This is the preferred mode for shared workers.
interval
Type:number
Re-send the params to the worker on a millisecond interval.
throttle
Type:number
Throttle worker invocations to at most once per N milliseconds.
The worker file uses standard onmessage / postMessage:
data-event-passive cannot be combined with data-event-prevent, because
passive listeners cannot cancel default browser behavior.
ng-pointer-capture can be combined with an application directive that installs
native pointer listeners for game and board interactions:
<divng-pointer-capturepointer-drag></div>
6.2 - Animation Directives and CSS Hooks
Use CSS class hooks and JavaScript animation factories with structural directives such as ng-if, ng-repeat, and ng-show.
AngularTS integrates animations into the same lifecycle that drives structural
directives. Add the animate attribute to an element and the $animate service
applies entry, exit, and class-change hooks at the appropriate DOM update.
How $animate integrates with directives
Structural directives such as ng-if, ng-repeat, ng-show, ng-hide,
ng-switch, and ng-include check for animation data before manipulating the
DOM. When animate is present, they delegate DOM operations to $animate:
Operation
Without $animate
With $animate
Insert element
element.after(clone)
$animate.enter(clone, parent, after)
Remove element
element.remove()
$animate.leave(element)
Toggle class
element.classList.add(cls)
$animate.addClass(element, cls)
Swap classes
el.add(a); el.remove(b)
$animate.setClass(element, add, remove)
This means you can add CSS transitions to any structural operation simply by
adding the animate attribute and writing the corresponding CSS rules.
CSS animation hook classes
When $animate performs an operation it applies a sequence of CSS classes in
two frames to give the browser time to set up the transition:
ng-CLASS-add → ng-CLASS-add + ng-CLASS-add-active → CLASS applied
ng-CLASS-remove → ng-CLASS-remove + ng-CLASS-remove-active → CLASS removed
.card.ng-enter{opacity:0;transform:translateY(-8px);transition:opacity0.25sease,transform0.25sease;}.card.ng-enter-active{opacity:1;transform:translateY(0);}/* Fade out when the element is removed */.card.ng-leave{opacity:1;transition:opacity0.2sease;}.card.ng-leave-active{opacity:0;}
Animating ng-repeat lists
ng-repeat triggers ng-enter and ng-leave animations for items added to or
removed from the collection. Add the animate attribute on the repeated element
to opt in.
<ul><liclass="task-item"ng-repeat="task in tasks"animate><spanng-bind="task.title"></span><buttonng-click="removeTask(task)">Remove</button></li></ul>
.controller('TaskCtrl',['$scope',function($scope){$scope.tasks=[{id:1,title:'Design wireframes'},{id:2,title:'Write tests'},{id:3,title:'Deploy to staging'}];$scope.removeTask=function(task){constidx=$scope.tasks.indexOf(task);if(idx!==-1)$scope.tasks.splice(idx,1);};}]);
Animating ng-show / ng-hide
ng-show and ng-hide both apply a temporary ng-hide-animate class alongside
ng-hide when the $animate service is active. This class serves as the
transition anchor:
<divanimate>{{ message }}</div>
transition:opacity0.3sease,max-height0.3sease;overflow:hidden;}/* State when hidden */.notification.ng-hide{opacity:0;max-height:0;}/* Applied during the animation frame to enable the transition */.notification.ng-hide-animate{display:block!important;}
JavaScript animations
Register JavaScript animation hooks with the module’s animation() method:
angular.getModule('app').animation('.flip-card',function(){return{enter:function(element,done){// Use Web Animations API or any library
element.animate([{transform:'rotateY(90deg)'},{transform:'rotateY(0deg)'}],{duration:300,easing:'ease-out'},).onfinish=done;returnfunction(cancelled){if(cancelled)element.style.transform='';};},leave:function(element,done){element.animate([{transform:'rotateY(0deg)'},{transform:'rotateY(-90deg)'}],{duration:250,easing:'ease-in'},).onfinish=done;},};});
<divng-repeat="card in cards"animate>{{ card.content }}</div>
Tip: JavaScript animations and CSS animations can coexist on the same
element. The $animate service runs CSS animations and JavaScript animation
hooks in parallel, calling done only after both complete.
Animation and the HTTP directives
The HTTP directives (ng-get, ng-post, etc.) also support the animate
attribute. When present, swapped content uses $animate.enter and
$animate.leave instead of direct DOM manipulation:
6.3 - Data Binding Directives in AngularTS Explained
Explore ng-bind, ng-bind-html, ng-model, ng-class, ng-style, and ng-init with real examples covering one-way and two-way data binding.
Data binding directives synchronise values between the scope and the DOM without
requiring you to write event listeners or DOM queries by hand. AngularTS
provides one-way and two-way binding variants to cover every use case, from safe
text output to full form synchronisation.
ng-bind
ng-bind is the attribute equivalent of double-curly interpolation ({{ }}).
It watches the given scope expression and writes the result as the element’s
textContent. Because it sets textContent, it is immune to XSS — no HTML is
ever parsed.
<p>Hello, {{ user.name }}!</p><!-- Using ng-bind — element is empty until AngularTS compiles --><p>Hello, <spanng-bind="user.name"></span>!</p>
The implementation observes the expression’s reactive dependencies and updates
the text when one of them changes.
Tip: Prefer ng-bind over {{ }} in the <body> of server-rendered
pages to avoid the flash of uncompiled template (FOUC). The element starts
empty and is filled when Angular bootstraps.
Parameters
ng-bind
Type:expression
Required: yes
Any AngularTS expression. The result is converted to a string via stringify()
and written to element.textContent. null and undefined render as an empty
string.
ng-bind-template
ng-bind-template lets you embed multiple interpolated expressions inside a
single attribute value — useful when the surrounding text cannot hold a child
element.
<titleng-bind-template="{{pageTitle}} — {{siteName}}"></title><!-- Equivalent using child elements (not possible inside <title>) -->
The directive reacts to interpolation dependencies and writes the resulting
string to textContent.
ng-bind-template
Type:string with {{ }} expressions
Required: yes
A string literal containing one or more {{ expression }} placeholders. The
entire interpolated string is written to textContent.
ng-bind-html
ng-bind-html inserts the expression result directly into element.innerHTML.
Use this directive when you need to render server-provided or pre-sanitised
markup. It calls $parse during compilation to check for interpolation errors,
then watches the expression and sets innerHTML on every change.
Warning:ng-bind-html sets innerHTML directly. Always sanitise content
on the server before placing it in scope. Avoid interpolating raw user input
with this directive.
ng-bind-html
Type:expression
Required: yes
An expression evaluating to a string of HTML. The string is written to
element.innerHTML. null and undefined clear the element.
ng-model
ng-model establishes a two-way binding between a form input and a scope
property. Changes to the input update the scope; changes to the scope property
update the input. It is the backbone of AngularTS form handling.
viewValue — the formatted value shown in the control (always a string
for native inputs)
modelValue — the parsed value stored in the scope
parsers — pipeline from view to model (view → model transformation)
formatters — pipeline from model to view (model → view transformation)
validators — synchronous validator functions keyed by error name
asyncValidators — async validator functions that return Promises
error — object whose keys are failing validator names
State properties
Property
Type
Description
pristine
boolean
true if the user has not interacted with this control
dirty
boolean
true after the user has changed the value
touched
boolean
true after the control has lost focus
untouched
boolean
true before the control has ever been blurred
valid
boolean
true if all validators pass
invalid
boolean
true if any validator fails
CSS classes
AngularTS automatically toggles CSS classes on the element:
.ng-valid{border-color:green;}/* Applied when the model is invalid */.ng-invalid{border-color:red;}/* Applied when the user has not yet changed the value */.ng-pristine{}/* Applied once the user has changed the value */.ng-dirty{}/* Applied once the input has been blurred */.ng-touched{}
ng-model
Type:expression
Required: yes
An assignable AngularTS expression. The expression must be assignable (pointing
to a scope property), otherwise AngularTS throws an ngModel:nonassign error.
ng-class
ng-class conditionally adds and removes CSS classes based on a scope
expression. It supports three input formats: an object, an array, or a string.
Object syntax
Keys are class names; values are expressions. A class is applied when its value
is truthy.
For larger views, prefer projecting domain state into a view-model class map and
binding that map directly. This keeps templates readable and lets JavaScript
type checking validate the object shape.
The implementation uses reference counting internally (digestClassCounts) so
that when multiple ng-class directives compete on the same element, classes
are not prematurely removed while still in use.
Alternating repeated rows
Use the repeater’s $even and $odd locals in a normal ng-class map.
Object — keys are class names, values are truthy/falsy conditions.
Array — each element is recursively processed using the above rules.
ng-style
ng-style watches a scope object and applies its key-value pairs as inline CSS
properties using element.style.setProperty. When the expression changes, the
old properties are first removed before the new ones are applied.
Note: CSS property names must use hyphen-case (background-color, not
backgroundColor) since the implementation calls element.style.setProperty
rather than assigning to element.style[key].
ng-style
Type:object
Required: yes
An expression evaluating to an object where keys are CSS property names in
hyphen-case and values are CSS value strings. Setting the expression to null
or undefined removes all previously applied inline styles.
ng-init
ng-init evaluates an expression during the pre-link phase — before child
directives are linked. It is most useful for initialising scope variables inline
without requiring a separate controller.
The implementation checks whether a controller is present on the element (via
getController) and, if so, evaluates the expression on the controller’s scope.
Otherwise it falls back to the current scope.
Warning: Use ng-init sparingly. For any non-trivial initialisation, use
a controller instead. Keeping logic in templates makes it harder to test and
maintain.
ng-init
Type:expression
Required: yes
An AngularTS expression or semicolon-separated list of expressions evaluated
once in the pre-link phase. The expression has access to the current scope.
6.4 - Form Directives and Validation in AngularTS
Complete guide to AngularTS form directives: ng-model, built-in validators, ng-messages, ng-model-options, and form state properties like valid and dirty.
AngularTS treats HTML forms as first-class citizens of the framework. Every
<form> element (and ng-form directive) creates a FormController instance
that tracks the aggregate validity and dirty state of all its inputs. Each input
bound with ng-model gets its own
NgModelController that
manages the view-to-model pipeline, validation, and CSS class hooks.
The form directive
A plain <form> element is enhanced by AngularTS into a FormController. Give
the form a name attribute and the controller is published on the current scope
under that name, giving you programmatic access to validation state.
Keys are validator names; values are arrays of failing controls
pending
object
Keys are validator names with pending async validators
CSS classes on forms
AngularTS automatically toggles these classes on both the <form> element and
each ng-model input:
Class
Applied when
ng-pristine
Control has not been changed
ng-dirty
Control has been changed
ng-valid
All validators pass
ng-invalid
Any validator fails
ng-submitted
Form has been submitted
ng-touched
Input has been blurred at least once
ng-untouched
Input has never been blurred
ng-pending
An async validator is in progress
input.ng-invalid.ng-dirty{border:2pxsolid#ef4444;}input.ng-valid.ng-dirty{border:2pxsolid#22c55e;}/* Show error messages only after the form is submitted or field is touched */.field-error{display:none;}input.ng-touched.ng-invalid~.field-error{display:block;}
ng-form
ng-form creates a nested FormController that tracks its own controls
independently. Because browsers do not allow nested <form> elements, ng-form
is the standard way to group controls into sub-sections with their own validity
state.
<formname="outerForm"><!-- Shipping address group --><ng-formname="shippingForm"><inputname="street"ng-model="shipping.street"required/><inputname="city"ng-model="shipping.city"required/><png-if="shippingForm.invalid">Please complete shipping address.</p></ng-form><!-- Billing address group --><ng-formname="billingForm"><inputname="street"ng-model="billing.street"required/><png-if="billingForm.invalid">Please complete billing address.</p></ng-form><buttonng-disabled="outerForm.invalid">Submit</button></form>
The outer form’s valid is false if any nested ng-form is invalid, allowing
you to check a single top-level form while still presenting granular errors per
section.
Input directives
All standard HTML input types are supported. For <input>, <textarea>, and
<select> elements, AngularTS registers type-specific parsers and validators
automatically when ng-model is present.
<label><inputtype="radio"ng-model="plan"value="free"/> Free </label><label><inputtype="radio"ng-model="plan"value="pro"/> Pro </label><label><inputtype="radio"ng-model="plan"value="enterprise"/> Enterprise
</label><p>Selected: {{ plan }}</p>
Select
<!-- Simple string options --><selectname="country"ng-model="user.country"required><optionvalue="">-- Choose a country --</option><optionvalue="us">United States</option><optionvalue="gb">United Kingdom</option></select><!-- Object options with ng-options --><selectng-model="selectedRole"ng-options="role.id as role.label for role in roles"><optionvalue="">-- Select a role --</option></select>
Built-in validators
required / ng-required
required makes the field mandatory. ng-required accepts an expression so you
can make a field conditionally required.
pattern accepts a regex literal (as an attribute value subject to
interpolation). ng-pattern accepts a scope expression that evaluates to a
RegExp object or a string.
<inputtype="text"ng-model="user.phone"pattern="^\d{10}$"/><!-- Dynamic pattern from scope --><inputtype="text"ng-model="user.code"ng-pattern="validationRules.codePattern"/>
codePattern:/^[A-Z]{2}\d{4}$/};
Note: Avoid the g (global) flag on patterns used with ng-pattern.
RegExp objects with the g flag maintain internal state between calls to
.test(), which causes alternating pass/fail results.
ng-messages
ng-messages simplifies displaying validation error messages. It watches a form
control’s error object and renders the first matching ng-message child (or
all matching, with the multiple attribute).
<formname="signupForm"><divclass="field"><label>Email</label><inputtype="email"name="email"ng-model="user.email"required/><divng-messages="signupForm.email.error"role="alert"><png-message="required">Email address is required.</p><png-message="email">Please enter a valid email address.</p></div></div></form>
By default, only the first matching error is shown. Add the multiple attribute
to show all failing validators at once:
<divng-messages="signupForm.password.error"><png-message="required">Password is required.</p><png-message="minlength">At least 8 characters required.</p><png-message="pattern">Must contain a number and special character.</p></div>
ng-message-default
ng-message-default acts as a fallback shown when none of the specific
ng-message keys match but there is at least one truthy error:
<divng-messages="field.error"><png-message="pattern">Invalid format.</p><png-message-default>This field has an error.</p></div>
Reusable message templates
Use ng-messages-include to load a shared message template file:
<divng-messages="signupForm.email.error"><divng-messages-include="'/partials/common-messages.html'"></div><png-message="email">Not a valid email address.</p></div>
ng-model-options
ng-model-options controls when the model is updated and how updates are
debounced. It can be placed on a form (to apply to all controls) or on
individual inputs.
Debounce
Delay model updates by a given number of milliseconds after the user stops
typing:
Space-separated DOM event names that trigger model updates. Use "default" as a
placeholder for the control’s standard update event (e.g., "input" for text
inputs).
debounce
Type:number | object
Milliseconds to wait before committing the view value. Can be an object mapping
event names to delays: { 'blur': 0, 'default': 500 }.
allowInvalid
Type:boolean
When true, the model is updated even when the value is invalid (normally the
model is set to undefined when validation fails).
timezone
Type:string
Timezone offset for date/time input parsing, e.g. "UTC" or "+0530".
Complete form example
The following example demonstrates a registration form with multiple input
types, validators, debouncing, and ng-messages error display.
<formname="regForm"ng-submit="register()"><inputname="email"type="email"ng-model="form.email"required/><divng-messages="regForm.email.error"ng-if="regForm.email.touched"><png-message="required">Email is required.</p><png-message="email">Enter a valid email.</p></div><buttonng-disabled="regForm.invalid">Create account</button></form>
.controller('RegistrationCtrl',['$scope',function($scope){$scope.form={};$scope.submitRegistration=function(){$scope.regForm.setSubmitted();if($scope.regForm.valid){// Call your API
console.log('Registering:',$scope.form);}};}]);
Make HTTP requests and stream SSE from HTML using ng-get, ng-post, ng-put, ng-delete, and ng-sse — no JavaScript required for common data-fetching patterns.
AngularTS ships a family of declarative HTTP directives. Rather than wiring up
$http calls in a controller, you attach ng-get, ng-post, ng-put,
ng-delete, or ng-sse directly to HTML elements. The directives handle the
request lifecycle, DOM insertion, loading states, error handling, and explicit
response assignment — all configured through HTML attributes.
Similarity to HTMX
Developers familiar with HTMX may recognize a similar declarative, server-first
approach: requests can start from HTML, the server can return fragments, and an
element can declare where a response belongs. That similarity is a useful mental
model, not a compatibility or migration contract.
Learn each AngularTS pattern from its AngularTS contract. AngularTS combines
HTTP directives with scope state, bindings, components, and router states. For
example, an HTTP directive can prefetch JSON into scope while ng-click and
ng-if decide when to reveal it.
Do not infer AngularTS syntax or behavior from HTMX documentation. AngularTS
does not automatically implement HTMX attributes, extensions, events, request
headers, or history behavior. Use the attributes documented on this page and
AngularTS services when a pattern needs capabilities beyond them.
How they differ from $http
When you use $http directly you write controller code: define the request,
subscribe to the promise, update scope properties, handle errors, and trigger a
digest. The HTTP directives do all of this declaratively:
JSON is available to on-success as $res; it never mutates scope implicitly.
HTML responses are compiled and inserted using the configured swap strategy.
ng-get
ng-get fires a GET request when the configured event occurs (default: click
for buttons, change for inputs, submit for forms).
ng-post fires a POST request. On <form> elements, the default trigger is
submit and the form data is collected via FormData. On buttons and other
elements, the trigger defaults to click.
<buttonng-post="/api/cart/add"on-success="cartMessage = $res.message"on-error="cartError = $res.error"> Add to cart
</button>
When the form element has an enctype attribute, the request body is
URL-encoded using Content-Type: application/x-www-form-urlencoded. Without
enctype, form data is sent as a JSON object.
ng-put
ng-put fires a PUT request. Use it to update resources:
ng-sse opens a persistent Server-Sent Events connection using the $sse
service. Incoming JSON is available to on-success as $res; HTML strings are
inserted using the configured swap strategy.
The connection is torn down automatically when the scope is destroyed (e.g.,
when navigating away). Reconnect behaviour and error logging are provided by the
$sse service.
The DOM event that fires the request. Defaults to click for buttons and
generic elements, change for inputs/selects/textareas, and submit for forms.
Use "load" to trigger immediately on link.
latch
Type:interpolated expression
Re-fires the request every time the interpolated value changes.
Expression evaluated when the response has a 4xx or 5xx status code. The
response data is available as $res.
<buttonng-post="/api/login"on-success="redirect('/dashboard')"on-error="loginError = $res.message"> Log in
</button>
data-state-success
Type:string
Router state name to navigate to on success (calls $state.go).
data-state-error
Type:string
Router state name to navigate to on error (calls $state.go).
animate
Type:none
When present, enables $animate transitions for the swap operation. Requires
the $animate CSS hooks to be defined.
enctype
Type:string
Sets the Content-Type request header and URL-encodes form data. Use
"application/x-www-form-urlencoded" to replicate a native HTML form
submission.
Practical examples
Loading a data table on mount
<table><thead><tr><th>Name</th><th>Status</th><th>Joined</th></tr></thead><tbodyng-get="/api/users"trigger="load"swap="innerHTML"><!-- Populated with server-rendered rows --></tbody></table>
Infinite scroll
<ulid="post-list"><ling-repeat="post in posts">{{ post.title }}</li></ul><buttonng-get="/api/posts?page={{ nextPage }}"ng-viewporton-enter="loadMore()"data-swap="beforeend"data-target="#post-list"> Load more
</button>
Learn how AngularTS directives attach behavior to HTML, how built-in directive groups are organized, and how to create custom directives.
Directives are how AngularTS attaches behavior and structure to HTML. A
directive is a marker on a DOM element or attribute that tells $compile to
attach behavior, transform DOM, wire events, create scopes, or connect
controllers.
Every built-in directive in AngularTS is applied as an HTML attribute using the
ng- prefix. The compiler normalizes prefixes, so ng-bind, data-ng-bind,
and x-ng-bind all match the same directive.
Use compile() when the directive must transform template DOM before linking.
Use link() when it only needs to attach behavior to each compiled instance.
Execution Order
When multiple directives appear on the same element, AngularTS sorts them by
priority from highest to lowest. Directives with equal priority run in
registration order.
<ling-repeat="item in items"ng-class="{ active: item.selected }"> {{ item.name }}
</li>
Structural directives such as ng-repeat and ng-if use high priorities and
terminal behavior because they replace or remove DOM before lower-priority
directives are linked.
Scope Choice
Use the default inherited scope for simple behavior. Create a child scope when
the directive needs local state. Use isolate scope bindings when the directive
is designed as a reusable widget with explicit inputs and callbacks.
scope:{title:"@",value:"=",onSave:"&",}
Prefer components for larger reusable UI pieces. Use directives for behaviors,
DOM integrations, structural transforms, and lightweight element coordination.
Master AngularTS structural directives including ng-if, ng-show, ng-hide, ng-repeat, ng-switch, ng-include, and ng-non-bindable.
Structural directives reshape the DOM by adding, removing, or repeating
elements. Unlike attribute directives that modify an existing element,
structural directives use transclusion to stamp out or remove entire sections of
the template. They run at high priority (600–1000) and set terminal: true so
that lower-priority directives do not compile until the structure is resolved.
ng-if
ng-if conditionally includes or removes a DOM element based on a scope
expression. When the expression becomes falsy, the element and its entire
subtree — including child scopes and event listeners — are destroyed. When it
becomes truthy again, a fresh element and child scope are created.
<divng-if="user.role === 'admin'"><h2>Admin Panel</h2><p>Welcome, {{ user.name }}. You have full access.</p></div>
<divng-if="isLoggedIn"><p>Welcome back, {{ user.name }}!</p><buttonng-click="logout()">Log out</button></div><divng-if="!isLoggedIn"><p>Please <ahref="/login">sign in</a> to continue.</p></div>
The implementation uses transclude: "element" at priority 600, which means the
compiler replaces the element with a comment placeholder and hands the element
template to the transclusion function. When the expression turns truthy, the
transclusion function clones the template and inserts it after the comment. When
it turns falsy, the child scope is destroyed and the clone is removed.
If the $animate service is available on the element, ng-if delegates to
$animate.enter and $animate.leave to trigger CSS transition hooks.
Note:ng-if vs ng-show: ng-if completely removes the element
from the DOM (destroying the child scope), while ng-show keeps the element
in the DOM and toggles an ng-hide CSS class. Use ng-if when the hidden
content is expensive to keep around or when it should not run watchers while
invisible.
Parameters
ng-if
Type:expression
Required: yes
An expression evaluated on each digest. When truthy, the element is inserted
into the DOM in a new child scope. When falsy, the element and its child scope
are destroyed.
ng-show / ng-hide
ng-show and ng-hide toggle element visibility without removing the element
from the DOM. They work by adding or removing the ng-hide CSS class, which
sets display: none.
ng-show
Removes ng-hide when the expression is truthy, showing the element.
<divng-show="form.submitted"><p>Thank you! Your form was submitted successfully.</p></div>
ng-hide
Adds ng-hide when the expression is truthy, hiding the element.
<divng-hide="isLoading"><p>Content is ready.</p></div><divng-show="isLoading"><p>Loading...</p></div>
Both directives integrate with $animate. When the animate service detects the
element has animation hooks, they call $animate.addClass or
$animate.removeClass with the temporary class ng-hide-animate for the
duration of the animation.
When truthy, ng-hide class is removed from the element, making it visible.
ng-hide
Type:expression
Required: yes
When truthy, ng-hide class is added to the element, hiding it.
ng-repeat
ng-repeat iterates over a collection (array or object) and stamps a copy of
the template for each item. Each copy gets its own child scope populated with
the iteration variables below.
Special scope properties
Variable
Type
Description
$index
number
Zero-based index of the current iteration
$first
boolean
true for the first item
$last
boolean
true for the last item
$middle
boolean
true when neither first nor last
$even
boolean
true when $index is even
$odd
boolean
true when $index is odd
Repeating over an array
<ul><ling-repeat="product in products"><strong>{{ product.name }}</strong> — ${{ product.price }}
<spanng-if="$first">(newest)</span><spanng-if="$last">(oldest)</span></li></ul>
Repeating over an object
Use the (key, value) tuple syntax to iterate over an object’s own enumerable
properties. Keys starting with $ are excluded automatically.
<dl><divng-repeat="(key, value) in user"><dt>{{ key }}</dt><dd>{{ value }}</dd></div></dl>
Alias with as
The as clause aliases the filtered collection to a new scope variable. This is
useful when you apply filters to the collection and need the filtered count.
<p>{{ filtered.length }} of {{ products.length }} items</p><ul><ling-repeat="product in products | filter:searchText as filtered"> {{ product.name }}
</li></ul>
Animation support
Add the animate attribute to enable $animate.enter / $animate.leave on
items:
ng-switch renders exactly one child block matching the current value of a
watched expression. It is the structural equivalent of a JavaScript switch
statement.
<divng-switch="user.status"><divng-switch-when="active"><p>Account is active. Welcome, {{ user.name }}!</p></div><divng-switch-when="suspended"><p>Your account has been suspended. Please contact support.</p></div><divng-switch-when="pending"><p>Your account is awaiting verification.</p></div><divng-switch-default><p>Unknown account status.</p></div></div>
ng-switch-when registers its transclusion function under the key !value (to
avoid collisions with $-prefixed keys) using an internal NgSwitchController.
When the watched expression changes, the currently shown block has its scope
destroyed (with an optional $animate.leave) and the matching block is
transcluded into place.
Multiple values on one case
The ng-switch-when-separator attribute allows a single ng-switch-when to
match multiple values:
The expression whose value is compared against ng-switch-when values.
ng-switch-when
Type:string
Required: yes
The literal string value to match against the ng-switch expression.
ng-switch-default
Type:none
Rendered when no ng-switch-when matches the current value.
ng-include
ng-include fetches an external HTML template, compiles it in the current scope
(or a new child scope), and inserts it into the DOM. It watches the URL
expression and replaces the content whenever the URL changes.
<divng-include="'/partials/user-profile.html'"></div><!-- Include a dynamic template based on user role --><divng-include="'/partials/nav-' + user.role + '.html'"></div>
The directive emits three events on the scope:
$includeContentRequested — when the template request begins
$includeContentLoaded — when the template is compiled and inserted
$includeContentError — when the template request fails
Expression evaluating to the URL of the template to load. The URL is fetched via
$templateRequest (which uses the template cache if available).
onload
Type:expression
Expression evaluated after the template is compiled and inserted.
autoscroll
Type:expression
When truthy, calls $anchorScroll after the template is inserted.
ng-non-bindable
ng-non-bindable tells the AngularTS compiler to skip the element and all its
descendants entirely. No interpolation, directives, or watches are set up inside
the marked section.
<preng-non-bindable><p>Use {{ expression }} to interpolate values.</p><div ng-bind="myValue"></div></pre>
<divng-non-bindable>{% raw %}{{ jinja_template_variable }}{% endraw %}</div>
Note:ng-non-bindable is essential when displaying code documentation,
Jinja/Twig snippets, or any content that uses {{ }} but should not be
treated as AngularTS expressions.
7 - Service guides
Learn how reusable services handle models, HTTP, REST, realtime messages, routing, and browser storage.
A service is one reusable object or function that AngularTS can provide to
controllers, components, directives, and other services. The act of requesting
that service by name is called dependency injection.
Start with shared models, then choose
the guide for the feature you need. Use the service
reference when you already know a service token
such as $http.
7.1 - Models
Use app.model() to declare shared reactive application state as an injectable service.
app.model(name, initial) declares a shared reactive model service. The
injected value is the proxied model object itself. Use models for shared,
declarative application state that should outlive one DOM scope tree.
When the model changes, DOM interpolation, ng-bind, and directive expressions
that read that model update automatically. Mutating the model proxy is
sufficient to schedule every affected observer.
Model data belongs to the app context, not to $rootScope. Every root scope
managed by the same app context resolves the same model instance. Destroying a
root scope removes that root’s observations, but the model data survives while
the app context remains alive.
Access From External Code
After the app has started with angular.bootstrap() or angular.injector(),
use angular.getModel(name) to access a model from external code:
constuser=angular.getModel<{name: string;authenticated: boolean}>('user');// An external callback can update the reactive model directly.
setTimeout(()=>{user.name='Jane';},1000);
The first lookup initializes the model if needed. Subsequent lookups and
dependency injection return the same instance. Mutations schedule updates for
every observing view without an explicit digest or $apply() call.
The getter only accepts models declared by loaded modules. Unknown names,
ordinary service names, and access before app initialization or after app
destruction throw errors. Use the Angular runtime instance that owns the app
when working with multiple applications.
Models Versus Local Scope
Use models for shared domain state such as user, cart, session, and
settings.
Use $scope for view-local state that only exists to support one template, such
as an open menu flag, a focused row id, or transient form display state.
Use $eventBus for cross-boundary messages, $machine for finite-state
transitions, and $workflow for command orchestration.
<sectionng-controller="ProfileCtrl as profile"><inputng-model="profile.user.name"/><buttonng-click="profile.signIn()">Sign in</button></section><sectionng-controller="HeaderCtrl as header"><strong>{{ header.user.name }}</strong><span>{{ header.user.authenticated ? "signed in" : "guest" }}</span></section>
Nested object reads, parent replacement, array length reads, array mutation, and
property deletion follow the same scope proxy semantics as local scope state.
Model Shape
Root models must start as plain objects. Primitives, null, arrays, and class
instances are rejected as root model values. Nested values follow AngularTS
scope proxy behavior. Duplicate model names in one module are rejected.
Model factories should initialize state only. Put sockets, workers, timers, DOM
listeners, and other side effects in services, workflows, machines, or
policy-backed browser abstractions.
Model Methods
Injected models are proxy-backed objects. They expose their own data properties,
the scope-proxy methods used by AngularTS reactivity, and model lifecycle
methods for whole-model coordination.
Those methods exist because models are implemented with the same proxy engine as
scopes. Application code should still treat a model as app-owned state, not as a
DOM scope.
Model-specific methods use ordinary method names:
snapshot() creates a plain snapshot of model state.
restore(snapshot, options?) restores model state and notifies observers.
sync(target) synchronizes model updates with a direct target or injectable
target factory.
sync() is intended for integrations such as Unity, WebSocket sessions,
workers, CRDT documents, and custom stores. The external runtime may update the
model imperatively, but AngularTS keeps the rest of the app declarative:
runtime event
-> model assignment
-> model.sync(...)
-> websocket/store/worker/crdt target
Target factories should use normal AngularTS injectable form. Bare string
service-name shortcuts are intentionally avoided for consistency:
The worker exchanges standard
ng.WorkerModelMessage
envelopes. Incoming snapshots update every DOM scope that consumes the model,
while model mutations are sent back with change metadata and loop-prevention
origin tracking.
sync() batches synchronous model mutations, sends a plain snapshot plus modest
change metadata, and uses origin metadata to prevent bidirectional sync loops.
Failures should report through AngularTS exception handling by default.
Durable storage should start as an explicit sync() target. A settings model
can restore from $cookie and write future snapshots back to the same storage
boundary without adding persistence behavior to the model declaration itself.
A sync target can implement any combination of restore, write, receive,
and dispose:
sync() checks the target at runtime. A target must implement at least one
recognized operation, and every supplied operation must be a function.
restore() results and snapshots passed to receive() must be plain objects;
invalid external values are reported through the configured sync failure policy
without partially updating the model. receive() must return either a disposer
function or undefined.
Runtime validation establishes the model container contract, but it cannot
recover erased TypeScript property types. Sync targets remain responsible for
decoding and validating domain fields received from untrusted external data.
restore(snapshot, { mode: "merge" }) keeps existing model keys and writes the
incoming keys. The default mode replaces the model root’s keys with the
snapshot’s keys.
Executable Sample
<!doctype html><html><head><scripttype="module">import"../../utils/source-bootstrap.js";import"./user-model.js";</script></head><bodyng-app="modelDemo"><sectionng-controller="ProfileCtrl as profile"><label> Name
<inputng-model="profile.user.name"aria-label="Name"data-testid="profile-name"/></label><buttonng-click="profile.signIn()">Sign in</button><buttonng-click="profile.reset()">Reset</button></section><sectionng-controller="HeaderCtrl as header"><strongdata-testid="header-name">{{ header.user.name }}</strong><spandata-testid="header-status"> {{ header.user.authenticated ? 'signed in' : 'guest' }}
</span><spandata-testid="cart-count">{{ header.cart.items.length }}</span></section></body></html>
Rejected requests use the same response object shape as successful requests. Use
status for HTTP errors and xhrStatus for transport outcomes such as timeouts
and aborts.
$http.get<User>('/api/users/99').catch((error)=>{if(error.xhrStatus==='timeout'){this.error='Request timed out.';}elseif(error.status===404){this.error='User not found.';}elseif(error.status===0){this.error='Network error.';}});
XSRF
$http can read a configured XSRF cookie and send it in a configured request
header. Cross-origin APIs must be listed as trusted origins before AngularTS
sends the token to them.
subscribe() returns an unsubscribe function. Keep that function and call it
during teardown, especially from long-lived services, directive controllers, or
manually bootstrapped integrations.
One-time listeners
Use subscribeOnce() for initialization handshakes where only the first event
matters.
This makes $eventBus useful for browser callbacks, WebSocket messages, Web
Worker results, and other boundaries where you want the publisher to stay
independent of Angular controller timing. When a subscriber changes view state,
write through proxied scope or controller state so AngularTS can observe the
assignment.
Error handling
If a subscriber throws, $eventBus forwards the value unchanged to
$exceptionHandler. The default handler rethrows, so the current publication
stops at that listener. Do not rely on later listeners running after an
exception.
Subscribers that need independent failure ownership should start an operation
with its own result or rejection boundary instead of using listener order as a
recovery mechanism.
Control delivery with a policy
Configure one callable policy when event delivery must follow an
application-wide rule. The callback is contextually typed as
EventDeliveryPolicy, so
ordinary configuration does not require a separate decision type.
Return "deliver" or "drop" for the common case. Return a structured policy
decision when shared policy infrastructure needs metadata such as a reason or
status.
Scope events
Use scope events when the relationship is already expressed by the scope tree.
Publisher and subscriber do not share a scope ancestry
Yes
No
Event comes from non-Angular code
Yes
No
Communication is strictly parent/child
Usually no
Yes
Delivery should be async
Yes
No
Listener cleanup should be tied to a scope
Works with $destroy
Built for it
Use $eventBus for cross-boundary messages such as WebSocket events, global
notifications, analytics readiness, and application-level domain events. Use
scope events for local component coordination.
EventSource does not support arbitrary custom headers in browsers. For authenticated streams, use cookies with withCredentials or include a short-lived token in query params.
WebSockets
$websocket returns a managed WebSocket connection with reconnects, heartbeat handling, message transforms, and send support.
send() serializes values as JSON before passing them to the native WebSocket.
WebTransport
$webTransport opens a browser-native WebTransport session. Use it when an
endpoint can serve HTTP/3 and the client benefits from unreliable datagrams,
reliable streams, or both in the same session.
The service expects the browser WebTransport API to exist and requires an
https: URL with an explicit port. The test backend exposes certificate hash
metadata at /webtransport/cert-hash for local browser tests.
Reconnect is opt-in at the service layer. When enabled, the
WebTransportConnection object stays stable while its native transport
instance is replaced. Use onReconnect as the renegotiation hook for
subscriptions, authentication messages, or other session state that the server
does not remember across HTTP/3 sessions.
For template-level feeds, ng-web-transport connects on load by default and
evaluates lifecycle expressions. data-mode="datagram" is the default;
data-mode="stream" reads server-opened unidirectional streams.
data-transform accepts bytes, text, or json. Message expressions receive
$connection, $data, $message, $event, and $text for text/json modes.
Reconnect is opt-in with data-reconnect="true"; tune it with
data-retry-delay and data-max-retries. on-reconnect runs after the
replacement session is ready and receives $attempt, $connection, error,
and $url.
Web Workers
Use ng-worker when a view action should run CPU-heavy JavaScript outside the main thread.
The directive evaluates data-params, posts the result to the worker, and
exposes worker responses as $result in on-result. Worker output is never
interpreted as HTML. Bind it into scope or a model and let normal directives
render the result.
Register expensive or shared workers once and reference their injectable
handle. data-request enables correlated request-response delivery:
The directive subscribes to a shared handle but does not terminate it when the
element scope is destroyed. Workers created directly from an ng-worker script
path remain element-owned and are terminated with that scope.
A worker module receives values through self.onmessage and returns results through self.postMessage.
Define typed REST endpoints once and use $rest for list, get, create, update, and delete flows backed by pluggable backends.
$rest wraps a REST backend with a small typed resource client. By default it
uses $http; pass a custom backend when a resource should read from another
data source or compose network and cache behavior.
Params that are not consumed by the template are forwarded to $http as query
params.
Map server data to classes
Pass an entity class when the raw response needs normalization, computed
properties, or methods.
classArticle{id: number;title: string;publishedAt: Date;constructor(data: any){this.id=data.id;this.title=data.title;this.publishedAt=newDate(data.published_at);}getisPublished() {returnthis.publishedAt.getTime()<=Date.now();}}constarticles=$rest<Article,number>('/api/articles',Article);constarticle=awaitarticles.get(42);if(article?.isPublished){// article is a real Article instance.
}
If you omit the entity class, $rest returns the parsed response data as-is.
Handle writes
create() sends POST, update() sends PUT, and delete() sends DELETE.
The methods intentionally stay close to HTTP semantics so errors and
interceptors still flow through $http.
get(), create(), and update() return null for an empty response.
delete() resolves with no value after the backend succeeds. Backend failures
reject for every operation, allowing one application error boundary or policy to
handle them consistently.
$rest does not perform framework-property cleanup itself. With the default
HTTP backend, $http deproxies scope payloads before JSON serialization, so
proxy helpers such as _target, _handler, and _proxy do not reach the
server. Generated repeat identity is stored as internal metadata rather than on
your model object, so it is not included in request bodies. Explicit
application-owned properties remain part of the payload.
Use a cached backend
CachedRestBackend wraps a
network backend and an async cache store. The default HTTP backend remains
available through
HttpRestBackend, while cache
storage can be memory, IndexedDB, the Cache API, or any object that implements
RestCacheStore.
Supported policy results are cache-first, network-first, and
stale-while-revalidate. Use the strategy option when every request should
use one static result. Writes always go to the network backend first; successful
writes invalidate cached collection and entity keys for the resource.
Cache keys are generated by
CachedRestBackend. A
RestCacheStore receives the
final key string in get(), set(), delete(), and deletePrefix() and
should treat that key as opaque. createRestCacheKey() is an internal REST
module helper, not a top-level namespace API.
Write a custom backend
A custom backend implements
RestBackend. It receives
normalized requests and returns raw response data for
RestService to map.
Use HttpRestBackend when the
backend should delegate to $http, and wrap it with
CachedRestBackend when
reads should use one of the cache strategies.
CRUD demo
The demo at /src/services/rest/rest-crud-demo.html uses the Go demo backend
through /api/tasks. It shows list(), get(), create(), update(), and
delete() against a real HTTP endpoint, renders rows with ng-repeat, and
includes a cache strategy toggle for network-first, cache-first, and
stale-while-revalidate.
Navigate between named states, read and modify the URL, manage history, and intercept route transitions.
AngularTS has two routing layers. $location manages the browser URL and
history directly. $state works at the application level with named states,
parameters, resolves, and transition hooks.
go() returns a transition promise. Use transition options when you need to
control reloads, parameter inheritance, URL updates, or relative navigation.
Generate Links
Use $state.href() when templates or controllers need a URL without starting
navigation.
Read and write cookies with $cookie, serialize objects, and use $window.localStorage or sessionStorage for client-side persistence.
AngularTS provides $cookie for typed, injectable cookie access and $window
for direct browser storage access. Prefer injected services over globals so unit
tests can replace browser APIs without patching window or document.
A cookie can only be removed when the path and domain used for removal match
the values used when it was created. If a cookie was created with path: "/",
pass the same path when removing it.
Cookie Defaults
Set defaults once when every cookie should share the same attributes.
Each page gives a default and the conditions that justify another choice.
8.1 - Templates or typed views
Choose between declarative HTML templates and type-safe DOM construction.
Default
Use typed views for new TypeScript components when model-to-view type safety
matters. Use templates when designers own substantial HTML or server-delivered
markup is required.
Choose a typed view when
Refactors must check property and event bindings.
The view is highly dynamic.
You want real DOM construction without parsing HTML.
A component and its view evolve together.
Choose a template when
Markup is mostly static and visually authored.
Existing template tooling is central to the team.
Progressive enhancement starts from server HTML.
Both use normal module registration, dependency injection, lifecycle, and change
detection.
8.2 - Controller state, models, or services
Put state at the narrowest lifetime that satisfies its consumers.
Default
Keep transient view state on the component controller.
Use a named model when state is reactive, shared by multiple roots or
components, and benefits from snapshot, restore, or synchronization behavior.
Use a service when the primary responsibility is an operation or external
resource rather than mutable view data.
Ask who owns the state, who may mutate it, how long it lives, and how it resets.
Global state is an application-level lifetime commitment, not a shortcut for
passing data.
8.3 - HTTP, SSE, WebSocket, or WebTransport
Select a network transport from the communication pattern and fallback requirements.
Default
Use HTTP for request-response work.
Use SSE for server-to-client events with straightforward reconnect behavior.
Use WebSocket for broadly supported bidirectional messages.
Use WebTransport when independent streams or datagrams are required and the
support contract permits it.
Decide from direction, ordering, reliability, backpressure, authentication,
proxy support, observability, and fallback behavior. Hide transport details
behind an application service.
8.4 - CSS or JavaScript animations
Choose the simplest animation mechanism that meets sequencing and control needs.
Default
Use CSS transitions or keyframes for visual state changes. They are easy to
cancel and keep view logic small.
Use JavaScript animation hooks when sequencing depends on application state,
measurements, asynchronous work, or coordinated entry and removal.
Always respect reduced-motion preferences. Animation should explain change or
preserve spatial context; it must not block input or carry required information
by itself.
8.5 - Component, directive, or web component
Choose the correct ownership and interoperability boundary.
Default
Use a component for an application-owned UI region with a controller and view.
Use a directive to add behavior to existing DOM when the host and surrounding
content remain owned by another view.
Use a web component when the element must cross framework or document boundaries
through browser-standard custom-element APIs.
Avoid directives that secretly own large subtrees and components that mutate
unrelated DOM. Clear ownership makes cleanup, testing, and change detection
predictable.
9 - Cookbook
Recipes for common AngularTS problems
Use the cookbook when you have a concrete problem and need a working pattern.
Start with debugging if AngularTS is new to you. Every progressive recipe ends
with a task to apply and checks that prove it worked.
Prefer the first applicable row. Do not introduce a client route or shared data
service when one server response and one declarative interaction solve the
problem.
Read and change scopes, controllers, and services from browser DevTools
Problem
A page is running, but you do not know which state, controller, or service owns
the DOM you are looking at. You want to inspect it before changing code.
Before you start
Run a development build with AngularTS debugging helpers available. Open the
page and browser DevTools; no source changes are required for the first
inspection.
Recipe
Open the browser’s Elements panel and select the element you want to
inspect. The DevTools console exposes that element as $0.
Inspect its scope
Select an element that creates a scope, such as an ng-controller, component,
or repeated row. Then run:
constscope=angular.getScope($0);scope;
angular.getScope() returns
the Scope attached to that compiled
element. Read the same properties used by its template:
scope.query;scope.results;
Change a value directly:
scope.query='boots';
AngularTS observes writes to the scope proxy and schedules the affected
bindings. You do not need to run a digest. Collection changes work the same way:
AngularTS gives each scope a numeric id when it creates it:
scope.id;// for example, 7
scope.root.getById(7);
An ID is useful while inspecting the current page, but it can change after a
reload or after AngularTS recreates part of the page. Give an important scope a
name when you want to find it repeatedly.
Add ng-scope with a non-empty name to the element whose scope you want to
find:
ng-scope="cart" assigns cart to that scope’s scopeName. It does not create
another child scope. The name is required. Find it from the console without
selecting the element again:
Use the injector to answer practical questions: whether a service is registered,
which instance the page uses, and what state that instance currently holds.
Choose the right application
A page can contain more than one ng-app. Each application has its own
injector, root scope, and service instances. Start from an element inside the
application you want to inspect.
When $0 is inside storefront, angular.getInjector($0) returns the
storefront injector. When $0 is inside admin, it returns the admin injector.
The same rule applies when reading scopes and controllers: select an element
inside the application you mean to inspect.
Do not fetch a service from one injector and assume it belongs to the other
application. Even when both applications register a service with the same name,
they can hold different instances and state.
Verify
Select the element whose text or value you want to change.
Read its scope or controller and find the matching property.
Change that property in the console.
Confirm only the expected part of the DOM updates.
If the page has several ng-app roots, confirm you selected the intended
one.
Reload the page and confirm the temporary change disappears.
Failure path
Helpers return state from the application containing the selected element. On
pages with several ng-app roots, selecting outside the intended root can
produce a different injector or no result.
Apply it now
Select one element whose behavior you do not understand. Find its application,
scope, and controller in DevTools. Change one reversible value and identify the
exact binding that updates. Keep the scope name only if you expect to inspect
this part of the page again.
9.2 - Select an element with ng-el
Let AngularTS place a native DOM element on a scope or controller
Problem
Your controller needs a native element so it can focus an input, draw on a
canvas, measure layout, or call another browser API.
Do not search the document with querySelector. Declare the element you need
and let AngularTS wire it to your state.
Before you start
Use this inside a compiled scope or controller that owns the element. Keep
document-wide elements in a service rather than attaching unrelated DOM to a
local scope.
Recipe
Add ng-el with the scope property that should receive the element:
If ng-el has no value, AngularTS uses the element’s id:
<inputid="searchInput"ng-el/>
The element is then available as scope.searchInput.
Failure path
The reference is unavailable before the directive links and is removed with the
element. Guard optional elements and do not retain the node in a longer-lived
service.
Apply it now
Search your controller code for one querySelector or getElementById call. If
the element belongs to that controller’s template, replace the search with
ng-el. Verify that the controller no longer knows the page-wide selector and
that the reference disappears when the element is removed.
Verify
Remove and recreate the element. Confirm the reference points to the new node
and no page-wide selector remains.
9.3 - Pass server data with ng-init or ng-setter
Initialize scope state once or keep it synchronized with server-rendered content
Problem
The server already knows values that the browser needs, such as the current
locale, a record ID, or the initial result count. You want AngularTS to use
those values without making another request.
Before you start
The server template must safely serialize and HTML-encode the initial value.
Decide whether the value is fixed at compilation or can change when another
system replaces element content.
AngularTS evaluates ng-init while it compiles the element. The resulting
page object becomes normal reactive scope state.
The HTML and initial JSON arrive in one server response. This avoids a separate
REST request, its network round trip, and an extra loading state. Use this when
the server already loaded the data while rendering the page.
Inspect it from DevTools by selecting the main element:
AngularTS initially sets scope.availability to 'In stock'. If server-driven
rendering or another integration later changes the span content, AngularTS
updates scope.availability again.
ng-setter observes the element for content changes. It assigns the trimmed
innerHTML string to the expression named by the directive:
Evaluates an AngularTS expression once during compilation. Use it for JSON objects, numbers, booleans, and other initial values.
ng-setter
Copies an element’s trimmed HTML into scope as a string and updates it whenever that content changes.
Both can place server-rendered data in the first HTML response and avoid an
extra REST request. Use ng-setter when later element updates must also reach
the scope.
Render the value on the server
The final HTML sent to the browser should contain the serialized value:
Use your server framework’s HTML attribute encoder when inserting serialized
data. Do not build the attribute by joining untrusted strings.
Serialize the value to JSON on the server, encode it for an HTML attribute, and
write it into ng-init. The browser can use the data as soon as AngularTS
compiles the page instead of waiting for another endpoint.
Use ng-init for small values already needed by the page. Keep large records in
the server-rendered HTML or load them through an application service when the
user requests them.
Failure path
Malformed or unsafely encoded JSON can break the containing attribute. Encode on
the server and keep large or sensitive records out of HTML attributes.
Apply it now
Find one request the browser makes for data the server already loaded while
rendering the page. Move the smallest useful value into ng-init. If the value
comes from HTML that another system may replace later, use ng-setter and
change the element once in DevTools to confirm the scope follows it.
Verify
Inspect the first document response and confirm the initial value needs no
follow-up request. Change an ng-setter element and confirm scope follows it.
9.4 - Update text, classes, styles, and visibility
Let bindings update existing DOM when scope state changes
Problem
State changes, and the page must update without finding elements or assigning
DOM properties by hand.
Before you start
Identify the scope or controller property that owns the visible result.
User-provided text must remain text unless it has passed an explicit
trusted-HTML policy.
Recipe
Bind each part of the element to scope state:
<articleng-controller="ProductController as product"><h2ng-bind="product.name"></h2><png-class="{ featured: product.featured, soldOut: !product.inStock }"ng-style="{ opacity: product.inStock ? 1 : 0.5 }"> {{ product.price | currency }}
</p><png-show="product.inStock">Available now</p><png-hide="product.inStock">Out of stock</p><buttonng-click="product.featured = !product.featured"> Toggle featured
</button></article>
AngularTS updates only the bindings that read those properties.
Choose the binding
Task
Binding
Replace text
ng-bind or {{ expression }}
Add and remove classes
ng-class
Change inline styles
ng-style
Hide while keeping the DOM alive
ng-show or ng-hide
Use ng-bind-html only for trusted HTML. Normal interpolation and ng-bind
write text, so user content cannot become markup.
Failure path
Do not switch to ng-bind-html to fix formatting. Treat unexpected markup as a
data or formatting problem unless the content has an explicit trust boundary.
Apply it now
Find one place where application code assigns textContent, className,
style, or hidden. Name the state that actually controls that result and bind
the DOM to it. Change the state from DevTools and confirm that no DOM-writing
code remains.
Verify
Change each source property independently. Confirm only its bindings update and
user text is rendered as text rather than HTML.
9.5 - Replace part of the page from state
Add, remove, or switch DOM subtrees with structural directives
Problem
A page has mutually exclusive steps or an expensive section that should not
exist while it is inactive.
Before you start
Decide whether hidden DOM state must survive. Structural directives destroy
branches; visibility directives keep their controls, media, and browser state
alive.
Use ng-include when the replacement comes from a template URL:
<sectionng-include="panelTemplate"></section>
Use ng-show instead when the same DOM and local form or media state must
remain alive while hidden.
Failure path
Using ng-if for a frequently toggled form recreates controls and loses local
state. Using ng-show for expensive hidden work keeps that work alive.
Apply it now
Choose one conditional panel in your application. Use ng-show if its form,
media, or local DOM state must survive hiding. Use ng-if if its work should
stop while absent. Use ng-switch if one state chooses among several panels.
Write down which lifecycle behavior you need before changing the directive.
Verify
Toggle the state repeatedly. Confirm hidden state is preserved only with
ng-show and removed branches release their scopes and browser resources.
9.6 - Swap server-rendered HTML into the page
Select a target and insert, replace, append, or remove server HTML
Problem
An action needs fresh HTML from the server, but it does not justify a client
route or a JSON API followed by client-side rendering.
Before you start
The endpoint must return a fragment rather than a complete document. Choose a
stable target that belongs to the same application and define what remains when
the request fails.
Recipe
Let the server return the final HTML. Select the target with data-target and
choose how to place the response with swap:
Append server-rendered rows without rebuilding the list:
<buttonng-get="/orders?page=2"data-target="#orders"swap="beforeend"> Load more
</button><ulid="orders"></ul>
Useful swap values are textContent for plain text, beforeend for append,
afterbegin for prepend, delete for removal, and none when only
on-success should update scope state.
The response HTML is compiled after insertion, so directives in the new fragment
join the same application.
Failure path
A failed response must not erase useful content. Return JSON to on-error or a
deliberate error fragment, and never insert untrusted HTML.
Apply it now
Find one action that fetches JSON only so the browser can rebuild HTML the
server already knows how to render. Return that fragment from the endpoint,
choose the smallest stable target, and select innerHTML, outerHTML, or an
append swap. Test the endpoint with JavaScript disabled before adding the swap.
Verify
Inspect the response content type and target. Confirm inserted directives
compile, failed responses preserve useful UI, and unrelated DOM nodes retain
identity.
9.7 - Run an effect when state changes
Use scope watches for work outside AngularTS bindings
Problem
A state change must update something outside AngularTS, such as analytics,
storage, a chart, or another browser library.
Before you start
Use an effect only for work outside AngularTS rendering. Identify the scope that
owns the external resource and the operation that releases it.
Recipe
Register a watch for the scope expression that drives the external work:
The owning scope removes its watches when it is destroyed. Keep explicit cleanup
when the effect also owns an external resource.
Use bindings for DOM work
Do not use an effect to copy scope values into text, classes, styles, or
visibility. Bind those values with ng-bind, ng-class, ng-style, or
ng-show. Use watch only when the result lives outside AngularTS.
Failure path
Effects can loop when they write the value they watch. Keep the watched input
and external output distinct, and make cleanup safe to call once.
Apply it now
Pick one watcher or state-change callback. If it only changes text, classes,
styles, or visibility, replace it with a binding. If it writes to storage,
analytics, a chart, or another external system, keep it as an effect and name
the resource that must be cleaned up when its scope is destroyed.
Verify
Write the watched value several times synchronously, destroy the scope, and
confirm one final effect runs and all external resources are released.
9.8 - Handle DOM and scope events
Respond to browser events and communicate through the scope tree
Problem
The page must react to user input, control browser event behavior, or notify
related scopes.
Before you start
Start from a native user interaction and decide whether communication is local
DOM behavior, a short scope notification, or durable shared state.
Handle a DOM event
Use the directive named after the native event:
<buttonng-click="cart.add(product)">Add to cart</button><inputng-keydown="$event.key === 'Escape' && dialog.close()"aria-label="Search"/>
$event is the native event. Declare event policy on the same element:
emit travels to parent scopes. broadcast travels to child scopes. Scope
event listeners run synchronously; reactive DOM updates still flush in the next
microtask.
Use DOM events for element interaction. Use scope events when scopes in the same
application tree need a short-lived notification. Use a service when unrelated
features share durable state.
Failure path
Passive listeners cannot prevent defaults, and broad scope broadcasts can hide
ownership. Keep event policy on the element and use services for lasting state.
Apply it now
Pick one manual addEventListener call. Move it to an ng-* event directive
when it belongs to a template interaction. Use a scope event only when a parent
or child scope needs a short notification. If the value must still exist after
the event, put it in a service instead.
Verify
Use keyboard and pointer input, inspect default prevention, and destroy the
scope. Confirm one handler runs and no listener survives its UI.
9.9 - Build and validate a form
Bind fields, show useful errors, and submit valid data
Problem
A form needs reactive values, validation messages, submit state, and a server
request without replacing native form behavior unnecessarily.
Before you start
The server endpoint must validate independently and return a deliberate success
or error format. Give every submitted native control a name.
Recipe
Name the form and every control. Bind values with ng-model and use native
validation attributes:
<formname="signupForm"ng-post="/api/signup"on-success="account = $res"on-error="submitError = $res.message"loadingloading-class="is-saving"><labelfor="email">Email</label><inputid="email"name="email"type="email"ng-model="user.email"required/><divng-messages="signupForm.email.error"ng-show="signupForm.email.dirty"><png-message="required">Enter your email.</p><png-message="email">Enter a valid email.</p></div><png-if="submitError"ng-bind="submitError"></p><buttontype="submit"ng-disabled="signupForm.invalid">Create account</button></form>
The form name exposes its controller on scope. Each named ng-model control has
an NgModelController.
Use dirty or touched to avoid showing errors before the user interacts.
Disable submission with invalid, but always validate again on the server.
Submit with ng-post or ng-put
Put ng-post on a form when the request creates something or runs an action.
Use ng-put when it replaces or updates a resource. Both directives:
Listen for the form’s submit event.
Prevent the browser from navigating away.
Collect the current form controls.
Send the request through $http.
Run on-success for a successful response or on-error for a failed one.
The first example sends a JSON object to /api/signup. $res is the parsed
response body. Assign it explicitly in on-success; the directive does not
guess which scope property should receive it.
Use an interpolated URL when updating an existing record:
The request body comes from the form’s native controls, not by serializing the
whole scope object. Give every submitted control a name. Controls without a
name, disabled controls, and unchecked checkboxes are omitted. This follows the
browser’s FormData rules.
Without enctype, AngularTS converts the collected entries to a JSON object.
Use URL encoding only when the server expects a traditional HTML form body:
Use the programmatic $http upload shown below for multipart/form-data. The
browser must create that request from the original FormData and add its
boundary; the declarative form directives convert entries to an object.
Handle the request lifecycle
loading sets data-loading="true" on the form while the request is running.
loading-class="is-saving" adds and removes that class at the same time. Use
either as a CSS hook for progress feedback.
Use on-success and on-error when the server returns JSON. If the server
returns HTML, data-target and swap can replace or append server-rendered
content. See Swap server-rendered HTML into the
page.
Route after a successful form
A login form can submit JSON, show server validation errors, and open a router
state without a controller or a JavaScript submit handler:
The inputs do not need ng-model. Their name and current native values are
enough for ng-post to produce this request body:
POST /_login
Content-Type: application/json
{"email":"user@example.com","password":"secret"}
For invalid credentials, return a 4xx status and an application/json body
whose keys match the form fields:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{"email":"Unknown account","password":"Password does not match"}
AngularTS parses the JSON and exposes it as $res. The inline
on-error="errors = $res" expression makes each message available to the
template. Editing a field clears only that field’s message.
For a 2xx response, AngularTS ignores the error expression and calls
$state.go('dashboard') through data-state-success. The dashboard state
must already be registered with the application’s router. The successful
response may be empty when the next state loads everything it needs.
ng-get and ng-delete can listen to a form submission, but they do not send
the form controls as a request body. Put their parameters in the URL, or use
them on a button. Use ng-post or ng-put when the form fields must be sent.
For search fields, debounce model updates instead of building a timer:
A file input writes its native FileList to ng-model. Send the selected
File in native FormData; do not set Content-Type because the browser must
add the multipart boundary.
The fetch transport supports cancellation through a promise-valued timeout,
but it does not currently expose upload progress. Use Cancel a file upload and
handle server rejection for the
complete controller, cancellation, abort, retry, and server-validation pattern.
Failure path
Native and AngularTS validation improve feedback but never replace server
validation. Preserve entered values on 4xx responses and prevent duplicate
destructive submissions.
Apply it now
Take the smallest real form in your application. Give each submitted control a
name, add ng-post or ng-put, and make the server return one realistic 4xx
JSON error body. Confirm the page renders those errors without a controller and
that a 2xx response performs the intended update or router transition.
Verify
Submit valid, invalid, slow, cancelled, and server-rejected requests. Confirm
named controls form the body, errors remain editable, and success performs
exactly one update.
9.10 - Read and write REST resources
Define a typed endpoint once and use it for CRUD operations
Problem
Several screens call the same endpoint and repeat URL construction, response
mapping, and CRUD request code.
Before you start
Register $rest and $http in the application that owns the data. Define
stable resource IDs and the server methods supported by the endpoint.
mouseenter starts the first request without revealing anything. AngularTS
parses the JSON response and on-success stores it on scope. When the user
clicks, ng-click changes only showPreview; if preview is ready, ng-if
renders it immediately without another request.
The inner ng-get is the fallback for keyboard, touch, and fast clicks. It is
created only when the preview is requested but no prefetched value is ready, and
data-trigger="load" starts the request immediately. A click made while the
hover request is still in flight can briefly create a second GET request. Use a
shared cached service when request deduplication matters.
Prefer mouseenter to mouseover for prefetching. mouseover bubbles when the
pointer crosses children and can issue repeated requests. The throttle limits
later pointer-triggered refreshes, but it is not a durable cache. Errors reject
normally, so the caller decides how to render retry and error states.
Failure path
Treat empty create responses, conflicts, cancellation, and stale replies
explicitly. Do not assume every successful write returns an entity body.
Apply it now
Identify data users often need immediately after hovering, focusing, or opening
a panel. Measure the normal click-to-content delay, add intent prefetching, and
measure it again. Decide what touch and keyboard users should trigger, how long
the result may stay fresh, and whether a duplicate in-flight GET is acceptable.
Verify
Inspect request methods and bodies for list, create, update, and delete. Repeat
the prefetched interaction with mouse, keyboard, touch emulation, and a delayed
response.
9.11 - Authenticate requests
Configure cookie or bearer credentials once for framework services
Problem
Requests need authentication, but controllers should not copy tokens or
credential flags into every call.
Before you start
The server must authenticate and authorize every request. Choose cookie or
bearer transport, define session expiry, and register any router states used
after login.
Recipe
Prefer a server-managed, HttpOnly cookie when the application and API can share
a cookie session:
Credential transport and login state are separate. credentials controls what
may be sent; isAuthenticated reports whether the application currently has a
signed-in user. The server remains responsible for authentication and
authorization.
Failure path
Client authentication state is not authorization. Clear local session state
after expiry, avoid token leakage, and let the server reject forbidden
operations.
Apply it now
Write down four facts for your login flow: where credentials are submitted,
which cookie or token proves later requests, how the client learns that login
succeeded, and which router state opens next. Then test invalid credentials, an
expired session, and a direct visit to a protected URL rather than testing only
the successful form.
Verify
Test login, logout, expiry, direct private navigation, and a forbidden action.
Confirm credentials use the intended transport and server authorization remains
decisive.
9.12 - Cache REST reads
Add cache-first, network-first, or stale-while-revalidate behavior
Problem
Reference data is requested repeatedly even though it changes infrequently.
Before you start
Provide a RestCacheStore
implementation and classify the response as public, user-specific, mutable, or
immutable before selecting a policy.
Recipe
Start with a cache store. This in-memory version lasts until the page reloads:
The object implements
RestCacheStore. Replace it
with IndexedDB or another persistent store when cached data must survive a page
reload. Then wrap the HTTP REST backend:
CachedRestBackend accepts
any RestCacheStore. Use
memory for one-page reuse, IndexedDB for durable structured data, or the Cache
API for HTTP-style storage.
Choose the strategy by what stale data means:
Strategy
Use it when
cache-first
Data is effectively immutable and startup speed matters most.
network-first
Fresh data matters, but cached data is a useful offline fallback.
stale-while-revalidate
Showing cached data now is better than waiting for a refresh.
Writes always go to the network first. Successful writes invalidate cached
collection and entity keys for that resource.
Failure path
The wrong policy can expose another user’s data or preserve an invalid value.
Partition private caches and invalidate after every relevant write.
Apply it now
Open the Network panel and find a GET that repeats during one normal task. Pick
a policy from the consequence of stale data: use cache-first for an immutable
country list, network-first for active orders, or stale-while-revalidate for
a product catalog. Perform a write and confirm the affected collection and
entity entries are invalidated. Never share user-specific cached responses
between signed-in users.
Verify
Compare network requests before and after caching, then perform a write and an
offline read. Confirm invalidation and stale behavior match the selected policy.
9.13 - Add application routes
Register states, render them in ng-view, and protect private pages
Problem
A client-owned area needs URLs, back-button support, nested views, and route
data. A normal server page is no longer enough for that area.
Before you start
Use client routing only inside an area the browser must own. The server must
still serve the initial application URL and authentication endpoints.
StateService also provides
href() for URL generation and matches() for active-state checks.
Keep the first page server-rendered. Add a route only when the browser must own
navigation and state across several related views.
Failure path
A route that works only after visiting another state is incomplete. Resolve
direct URLs, rejected policy, missing data, and superseded navigation.
Apply it now
List the URLs in one browser-owned area. Keep pages on the server unless users
need client-side back-button history and state across those URLs. Register the
smallest useful parent and child states, open a child URL directly, refresh it,
and use Back. If any step depends on first visiting another state, the route is
not complete.
Verify
Open every documented URL directly, refresh it, move through child states, and
use Back and Forward. Confirm private states enforce policy before rendering.
9.14 - Load content when it enters the viewport
Delay below-the-fold work with ng-viewport
Problem
A chart, comment list, or request should not start until the user scrolls near
it.
Before you start
Measure the initial request or CPU work first. The deferred feature must have a
visible loading or empty state when it has not started.
Recipe
Use ng-viewport and run the enter expression once:
The directive owns its browser observer and disconnects it when the scope is
destroyed.
Failure path
Intersection is only a scheduling hint. Keep keyboard access and an explicit
retry path, and disconnect observers when their scope is destroyed.
Apply it now
Record the requests and long tasks made before first paint. Choose one
below-the-fold feature that is not needed yet, move its start expression to
on-enter, and set the margin from measured load time rather than guessing.
Scroll past it twice and confirm data-viewport-once prevents duplicate work.
Verify
Scroll into and out of the element twice, then remove it. Confirm the margin,
once behavior, leave behavior, and observer cleanup match the recipe.
9.15 - Receive live server events
Update scope or append HTML through Server-Sent Events
Problem
The server must push notifications or status updates to the page without client
polling.
Before you start
The server must support text/event-stream, flush events, and handle
reconnects. Decide what the page shows before the first event arrives.
The browser side uses SseService
through ng-sse. AngularTS owns the
SseConnection, reconnects
it, and closes it when the scope is destroyed.
For a custom production runtime, include sseModule from
@angular-wave/angular.ts/runtime/realtime. Do not include the aggregate
realtimeModule unless the same runtime also uses WebSocket and WebTransport.
Set up a JavaScript SSE endpoint
An SSE endpoint is a long-running HTTP GET. Send the event-stream headers, end
every event with a blank line, flush each update, and stop work when the request
closes.
The blank line after data: completes one event. Lines beginning with : are
comments that keep idle proxies from closing the connection. In production,
publish real order changes rather than using timers.
Authenticate and authorize before writing the 200 response. Browser
EventSource cannot attach arbitrary headers, so prefer an HttpOnly,
Secure, SameSite session cookie and with-credentials="true". Do not put a
long-lived bearer token in the URL.
Send complete current-state snapshots when reconnecting is enough. If every
event must be delivered, add an id: field, store events durably, read the
browser’s Last-Event-ID header on reconnect, and replay events after that ID
before sending live ones. A process-local counter is not sufficient when more
than one server can handle the request.
AngularTS parses JSON for on-success and otherwise applies the configured DOM
swap. Keep each message small. Send an identifier and changed fields, or a small
current-state snapshot, instead of an entire page model.
Use SSE for one-way server updates. Use a WebSocket or WebTransport service when
the browser and server both need a long-lived message channel.
Failure path
SSE is one-way and connections can disappear through proxies, deployments, or
sleep. Show stale or disconnected state and make repeated events safe. Confirm
that your reverse proxy disables response buffering and permits connections to
remain open. Keep polling when your hosting path cannot stream responses.
Apply it now
Find one polling loop that only asks whether the server has new data. Write the
smallest event payload the server can push, replace the poll with SSE, and show
what the user sees before the first event and after the connection fails. Keep
polling when intermediaries cannot maintain long-lived HTTP connections.
Verify
Send valid JSON, HTML, malformed data, and a disconnect. Confirm the page shows
its initial, live, error, and reconnect states without polling. Reconnect with a
Last-Event-ID, test the page through the production proxy, and confirm the
server releases timers and subscriptions when the browser disconnects.
9.16 - Move CPU-heavy work off the main thread
Run JavaScript in a Web Worker with ng-worker
Problem
Sorting, compression, or data processing blocks input and rendering on the main
browser thread.
Before you start
Profile a realistic input and define a serializable message contract. Browser
APIs and DOM nodes cannot be passed to the worker as ordinary values.
Recipe
Point ng-worker at a worker file and bind its result:
AngularTS assigns the reply to $result and updates the scope expression in
on-result. The directive terminates its worker when its scope is destroyed.
Use data-request with a registered shared worker when several callers need
correlated request and response messages.
Failure path
Worker startup and cloning can cost more than the task. Ignore stale replies,
terminate owned workers, and keep errors visible in the owning UI.
Apply it now
Profile the interaction before adding a worker. Move work only when a measured
CPU task blocks the main thread long enough to affect input or rendering. Define
a small serializable request and response, test the largest realistic input, and
decide how a newer request cancels or ignores an older result.
Verify
Compare the main-thread trace before and after moving the task. Confirm input
stays responsive, stale replies are ignored, and the worker terminates with its
scope.
9.17 - Load a WebAssembly module
Expose WebAssembly exports to scope with ng-wasm
Problem
A calculation already exists as WebAssembly and the page needs to call its
exports without writing module-loading lifecycle code.
Before you start
Have a deployed WebAssembly module and know its exported function contract. The
server must send the correct MIME type.
Recipe
Load the module with ng-wasm and name its scope resource with as:
AngularTS exposes a
WasmResource as shipping.
Check status before calling the pricing export. The cart and destination stay
in normal reactive scope state.
The server must serve the module with the correct WebAssembly MIME type. The
directive owns loading and releases its resource when the scope is destroyed.
Keep DOM work in AngularTS. Use WebAssembly for compute-heavy functions with a
small, explicit input and output contract.
Failure path
Module loading, memory copies, and MIME failures can erase compute gains. Keep a
JavaScript or server fallback when the calculation is required.
Apply it now
Choose WebAssembly only for an existing compute-heavy algorithm, not ordinary UI
arithmetic. Measure the JavaScript version, module startup, and data-transfer
cost with realistic inputs. Keep the module only when repeated calls recover its
loading cost and the input/output boundary stays small.
Verify
Test loading, MIME failure, realistic calculations, repeated calls, and scope
removal. Confirm the measured gain exceeds module and transfer costs.
9.18 - Edit a server record in place
Load an editor, submit JSON, and keep server validation beside the field
Problem
A user should edit one record without leaving the page. The server owns
validation and returns the saved record.
Before you start
The page already has the record ID and summary. The endpoint accepts JSON and
returns field errors with status 422.
PUT /api/profiles/42
Content-Type: application/json
{"name":"Ada Lovelace"}
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{"name":"That name is already used"}
What AngularTS wires
The form creates JSON from named controls. A 2xx response replaces profile;
a 4xx response becomes $res and stays beside the editor.
Failure path
Keep the editor open after validation or network failure. Do not replace the
user’s input with the old server value until they cancel.
Apply it now
Choose one record users currently open on another page just to change one or two
fields. Define its update URL, successful JSON body, and one realistic 422
body.
Verify
Edit, reject, correct, and save the record. Confirm focus and entered values
survive the rejected request and the final summary uses the server response.
9.19 - Delete a row without rebuilding its list
Let the server authorize deletion and remove only the successful row
Problem
Deleting one item should not recreate the list, lose scroll position, or remove
the row before the server authorizes the action.
Before you start
The server must authorize deletion and return success only after the record is
gone. Give the rendered row a stable DOM ID that is unique on the page.
Return 204 No Content only after authorization and deletion succeed. Return a
403 or 409 JSON body such as {"message":"This task is locked"} when the
row must remain.
What AngularTS wires
The click sends DELETE. The delete swap removes the selected row only after
a successful response. Errors update scope instead of touching the list.
Failure path
Disable or mark the button while the request runs when duplicate deletion
matters. Always enforce ownership and authorization on the server.
Apply it now
Find a delete action that refreshes an entire collection. Give each row a stable
DOM ID and make the endpoint return an error that explains why deletion can
fail.
Verify
Delete the middle row, then reject another deletion. Confirm sibling nodes,
focus, scroll position, and the rejected row remain unchanged.
9.20 - Search while the user types
Debounce requests and ignore responses for an older query
Problem
Search should feel immediate without sending a request for every keystroke or
letting an older response replace newer results.
Before you start
The search endpoint must accept a bounded query and echo its normalized query in
the JSON response. Decide the minimum useful query length and result limit.
Echo the normalized query with the results: {"query":"boots","items":[...] }.
The equality check prevents a slow response for an earlier query from winning.
What AngularTS wires
data-delay waits for a pause. URL interpolation reads the current model, and
on-success applies only the response that still matches it.
Failure path
Render request errors separately from an empty result. Add a minimum query
length when broad searches are expensive, and enforce limits on the server.
Apply it now
Pick the search box with the highest request count. Record requests per query
and time to useful results before and after adding the delay and stale-response
check.
Verify
Type two queries quickly and delay the first response in DevTools. Confirm the
URL contains the latest query and the late response never replaces newer
results.
9.21 - Load one select from another
Request valid choices when a parent selection changes
Problem
A city, model, or category list depends on an earlier selection and should not
be loaded before it is needed.
Before you start
The parent values must use stable server identifiers. The child endpoint must
validate the parent identifier and return only choices valid for it.
Working example
<formng-init="countries = [{code:'LV', name:'Latvia'}, {code:'EE', name:'Estonia'}]; cities = []"><selectname="country"ng-model="country"ng-options="item.code as item.name for item in countries"ng-get="/api/cities?country={{ country }}"on-success="cities = $res; city = undefined"></select><selectname="city"ng-model="city"ng-options="item.id as item.name for item in cities"ng-disabled="!cities.length"></select></form>
Server contract
GET /api/cities?country=LV returns a bounded JSON array such as
[{"id":1,"name":"Riga"}]. Reject unknown country codes on the server.
What AngularTS wires
A select uses change as its default HTTP trigger. The response replaces the
available cities and clears a city that belonged to the previous country.
Failure path
Clear dependent values before showing new choices. Show a retry message when the
request fails instead of leaving stale choices enabled.
Apply it now
Find a form that initially downloads every possible option. Move one dependent
list behind its parent selection and compare initial payload size.
Verify
Change the parent twice. Confirm the child value clears, stale choices cannot be
submitted, and the request includes only an allowed parent identifier.
9.22 - Load the next page near the viewport
Append server-rendered rows and replace the paging marker
Problem
A long feed should load another server-rendered page shortly before the user
reaches the end without rebuilding existing rows.
Before you start
The server must expose normal paginated URLs and return HTML fragments with
stable row identity. Keep explicit pagination as the non-JavaScript and keyboard
fallback.
Return new <li> rows followed by the same marker configured for page 3. Omit
the marker on the last page. Keep a normal paginated URL so refresh and sharing
work.
What AngularTS wires
The viewport changes nextPage; the latch sends the request; outerHTML
replaces the old marker with rows and the next marker. Existing rows stay alive.
Failure path
Return a retry button when loading fails. Do not trap keyboard users in an
endless feed; provide a normal pagination link and a reachable page footer.
Apply it now
Choose one feed where users commonly request page 2. Measure its rendered row
count, DOM size, and requests before replacing the current pagination behavior.
Verify
Scroll with the Network panel open. Confirm each page loads once, rows are not
recreated, the final response removes the marker, and the fallback URL still
works.
9.23 - Update immediately and roll back on failure
Show a local change first while preserving the previous server value
Problem
A reversible action such as completing a task should feel immediate, but a
rejected request must restore the last confirmed value.
Before you start
Choose a reversible, low-risk action and retain the last confirmed server value.
The endpoint must return the canonical value or a clear conflict response.
Return the canonical task after success. Return 409 Conflict with
{"message":"Task changed on another device"} when the submitted version is
stale.
What AngularTS wires
The checkbox updates local rendering immediately. confirmed changes only after
a successful response and supplies the rollback value after failure.
Failure path
Use optimism only for reversible changes. Payments, destructive operations, and
permission changes should wait for server confirmation.
Apply it now
Choose one low-risk action that currently waits on the network. Name the
confirmed value, the optimistic value, and the exact response that requires
rollback.
Verify
Test success, offline failure, and conflict. Confirm rollback uses the last
confirmed server value rather than a guessed default.
9.24 - Return users to login after a session expires
Route a failed private request and preserve the intended destination
Problem
A user opens a private action after their cookie session expires. The
application must show login rather than leaving a broken panel.
Before you start
Register the login router state and configure the request credential transport.
Decide how the login flow stores and restores the intended destination.
The private endpoint returns 401 with {"message":"Your session expired"}.
The login state records the intended state and returns there after successful
login.
What AngularTS wires
The directive exposes the JSON error and asks the router to enter login.
Configure cookie credentials and route policy once; do not add auth logic to
every button.
Failure path
data-state-error runs for every failed status, not only 401. Use this direct
pattern only for an endpoint whose failures all require login; otherwise
centralize status-aware handling in the HTTP or security layer.
Apply it now
Expire a real session, then activate one private request and one private URL.
Decide where the intended destination is stored and which failures should not
route to login.
Verify
Confirm the login page appears, credentials are not placed in URLs or logs, and
a successful login returns to the original destination exactly once.
9.25 - Cache reference data without sharing user data
Cache stable lookup lists and invalidate them deliberately
Problem
Country, currency, or category lists are downloaded repeatedly even though they
change rarely and are identical for every user.
Before you start
Provide an application cache implementing the linked cache-store contract.
Confirm the endpoint is independent of user identity before sharing entries.
<label> Country
<selectng-model="shipping.country"ng-options="country.code as country.name for country in countries"></select></label>
The cached result is ordinary application state. Bind it like any other list;
the template does not need to know which cache policy supplied it.
Server contract
The endpoint returns stable IDs, supports conditional HTTP caching, and never
varies by the signed-in user. Publish a version change when administrators
update the list.
What AngularTS wires
The cached backend reads through the supplied
RestCacheStore. Successful
writes invalidate matching collection and entity entries automatically.
Failure path
Do not place account, permission, price, or personalized responses in a shared
cache. Use network-first when stale data can block a current operation.
Apply it now
Find a repeated GET in the Network panel. Prove its response is
user-independent, choose an acceptable stale lifetime, and identify the write
that invalidates it.
Verify
Load the same form twice, test offline fallback, update the reference list, and
sign in as another user. Confirm the cache never crosses a user-specific
boundary.
9.26 - Cancel a file upload and handle server rejection
Send FormData, abort through timeout, and distinguish cancellation
Problem
A large upload needs cancellation and useful server errors. The current fetch
transport does not expose upload progress events.
Before you start
Register the controller on the application module and attach it to the form. Set
server-side size, content, authorization, and storage limits before accepting
files.
<formng-controller="UploadController as upload"ng-submit="upload.send(upload.files[0])"><inputtype="file"name="file"ng-model="upload.files"required/><buttontype="submit">Upload</button><buttontype="button"ng-click="upload.cancel()">Cancel</button><png-if="upload.error">{{ upload.error }}</p></form>
Server contract
Validate size, MIME signature, filename, authorization, and storage limits on
the server. Return 413 for size limits and 422 JSON for rejected content.
What AngularTS wires
Resolving the promise supplied as timeout aborts the underlying request. An
aborted request rejects with xhrStatus === 'abort'.
Failure path
Do not set Content-Type manually for FormData; the browser adds the
multipart boundary. Do not advertise progress: uploadEventHandlers is ignored
by the fetch transport today.
Apply it now
Choose the largest upload your application accepts. Define its limit,
cancellation behavior, retry behavior, and the message returned for each server
rejection.
Verify
Cancel during transfer, reject an oversized file, and retry a valid file.
Confirm an abort shows no failure message and no partial object becomes
accessible.
9.27 - Load a dialog from the server
Swap server-rendered dialog content and restore focus on close
Problem
A confirmation dialog needs current server data, but its trigger must keep focus
and remain usable if loading fails.
Before you start
The trigger must remain in the DOM while the fragment is open. The server
fragment owns its accessible title, initial focus, close behavior, and
authorization result.
Return a labelled dialog fragment with a focused cancel button. Its close action
calls dialogTrigger.focus() before removing the fragment. Return 403 JSON
when the current user may no longer perform the action.
What AngularTS wires
The response is inserted and compiled in the trigger’s scope, so the fragment
can use dialogTrigger, event directives, validation, and another HTTP
directive.
Failure path
Keep the trigger in place while loading and failure occur. The fragment must
trap focus when modal, close on Escape, restore focus, and expose a visible
title.
Apply it now
Choose one modal whose content is stale or duplicated in client code. Move its
title, warning, and authorization decision to one fragment endpoint.
Verify
Open by keyboard, cancel, reopen, reject authorization, and complete the action.
Confirm focus always returns to the trigger and the background is not
interactive.
9.28 - Understand the similarity to HTMX
Compare the server-first approaches without assuming compatibility
Problem
AngularTS HTTP directives look familiar if you know HTMX. You want to understand
the shared approach without assuming the APIs behave alike.
Before you start
Start with the AngularTS HTTP directive reference. Treat HTMX knowledge as
background for the server-first design, not as documentation for AngularTS.
Similar design questions
Design question
AngularTS mechanism
Trigger a request
ng-get, ng-post, ng-put, ng-delete, data-trigger
Choose the destination
data-target
Choose insertion behavior
swap
Render a server fragment
Return HTML; AngularTS inserts and compiles it
Read JSON
Assign $res in on-success or on-error
Show request state
loading, loading-class, bindings
Navigate after success
data-state-success
Prefetch from intent
Event trigger plus hidden scope state
Lazy load near the viewport
ng-viewport plus an HTTP directive
Keep local interaction state
Scope, controller, or typed component view
Where the approach feels similar
Both approaches encourage useful questions for click-to-edit, active search,
polling, lazy loading, server validation, fragment swaps, pagination, and
prefetching: who renders the result, what triggers the request, where it goes,
and what failure looks like.
Do not infer compatibility
AngularTS does not promise HTMX syntax or behavior. It has its own attributes,
scope expressions, lifecycle, router, security configuration, response handling,
and tests. Similar names or outcomes do not make an HTMX example an AngularTS
example.
Failure path
Do not copy an HTMX attribute, extension, event, header, or history option and
assume AngularTS recognizes it. Build from the AngularTS reference, preserve the
normal server endpoint, and test error and navigation behavior independently.
Apply it now
Choose one server interaction in your application. Define its trigger, request,
response type, target, local state, fallback, and failure behavior using only
AngularTS documentation. Then note which design questions happen to feel
familiar from HTMX.
Verify
Test the normal link or form fallback, direct server endpoint, successful swap
or JSON assignment, failed response, keyboard path, and browser history. Do not
use an HTMX result as evidence that AngularTS behaves the same way.
9.29 - Design an HTTP response for the UI
Choose status, content type, and body before writing the directive
Problem
A directive can only handle success, validation, navigation, and swaps correctly
when the server response has a deliberate contract.
Before you start
Choose whether the server or browser renders the result. Write down what the user
should see after success, invalid input, expired authentication, conflict, and
unexpected failure.
Choose the response
Situation
Response contract
Saved data is needed locally
2xx application/json with the canonical saved record
Only success matters
204 No Content
Server owns the replacement UI
2xx text/html with one fragment
User can correct submitted values
422 application/json keyed by field name
Authentication expired
401 application/json with a safe user message
User lacks permission
403 application/json
Submitted version is stale
409 application/json with conflict details
Server failed unexpectedly
5xx application/json with a public message ID
Use on-success or on-error for JSON. Use data-target and swap for HTML.
Do not return HTML error pages to a JSON interaction or 200 for rejected form
data merely to avoid the error path.
Failure path
Never expose stack traces, database errors, tokens, or internal identifiers in a
public error body. Log a correlation ID on the server and return only that ID
with a safe message.
Apply it now
Choose one endpoint used by a cookbook recipe. Write its method, URL, success
status, success content type, failure statuses, and one real body for each path
before changing the template.
Verify
Inspect every response in the Network panel. Confirm its status and content type
match the body, an empty success is actually empty, and each failure reaches the
intended UI path.
9.30 - Make a server-driven interaction accessible
Preserve keyboard behavior, announce loading, and move focus deliberately
Problem
A request updates the page visually, but keyboard and screen-reader users also
need an operable trigger, announced status, and predictable focus.
Before you start
Use a native button or link for the trigger. Choose a stable result container and
decide whether focus should remain on the trigger or move to the new result.
Return a heading and order rows as one HTML fragment. Return JSON with a short,
actionable message for errors so the live region can announce it.
What AngularTS wires
The native button keeps keyboard activation. loading exposes request state,
role="status" announces text changes, and ng-el provides the stable result
element without a selector search.
Failure path
Do not move focus on failure or for background refreshes. Never replace the
focused trigger with a swap unless the response provides the next focus target.
Apply it now
Run one server-driven interaction using only a keyboard and a screen reader.
Name the trigger, loading announcement, error announcement, and final focus
location before changing markup.
Verify
Activate with Enter and Space, delay the response, reject it, and return an empty
result. Confirm status is announced once, focus is visible, and tab order remains
logical.
9.31 - Audit a server-driven interaction before shipping
Check authorization, CSRF, HTML trust, caching, uploads, and errors
Problem
A form or HTTP directive works, but its security depends on decisions spread
between markup, request configuration, the endpoint, and storage.
Before you start
Choose one real state-changing request. Record its authenticated user, resource,
method, credential transport, response type, cache path, and uploaded content.
Audit the interaction
Surface
Shipping check
Authorization
Server verifies this user may act on this resource
Cookie request
Server validates CSRF protection on every state-changing method
Bearer credential
Token never appears in URL, HTML, storage logs, or error output
HTML response
Server escapes untrusted values before AngularTS compiles it
JSON error
Body contains safe field messages, not stack or database details
Cache
Private entries are partitioned and cleared on logout
Upload
Server checks size, MIME signature, filename, and storage limits
Redirect
Destination is allowlisted rather than copied from user input
Client validation, hidden buttons, route policy, and disabled controls improve
the user experience. None of them authorize a server operation.
Failure path
Use 401 when authentication is missing, 403 when the authenticated user lacks
permission, and 422 for correctable input. Do not turn a security rejection
into a successful response just to simplify the template.
Apply it now
Capture one real request in the Network panel and answer every row using the
server implementation, not assumptions from the UI. Add a server test for each
answer that can regress.
Verify
Repeat the request signed out, as another user, without the CSRF proof, with
tampered IDs, with malicious text, and with an oversized upload when applicable.
Confirm every rejection is safe and leaves no unauthorized state change.
9.32 - Keep a form useful before AngularTS starts
Build a normal server form first, then add faster in-page behavior
Problem
A request should still work when JavaScript is slow, unavailable, or fails to
start.
Before you start
Make the endpoint accept a normal form submission and return a complete page.
Only then add AngularTS behavior to improve the interaction.
Without AngularTS, the browser posts the form to /orders. With AngularTS, the
HTTP directive submits it without a page reload and the router enters orders
after success. The endpoint must accept both the browser’s form encoding and the
enhanced request body.
Server contract
For a normal browser submission, return a complete success or validation page.
For the enhanced request, return 2xx on success or a JSON error object such as
{"quantity":"Choose at least one item"} with 422. Decide how the server
distinguishes these requests; do not depend on an undocumented client header.
Failure path
Keep native labels, required fields, methods, actions, and submit buttons. A
network failure may prevent either form of submission, but it must not leave a
control that only works after JavaScript wiring.
Apply it now
Choose one important form. Remove JavaScript mentally and check whether its
action, method, controls, and server response still complete the task.
Verify
Submit once with JavaScript disabled and once with AngularTS running. Test valid
input, invalid input, a signed-out session, and a server error in both paths.
9.33 - Debug an HTTP interaction from the browser
Trace an element, its scope, its request, and the resulting UI state
Problem
A click or submission produces the wrong result and you need to find whether the
problem is the element, scope, request, response, or rendering.
Before you start
Open the Elements, Console, and Network panels. Select the element that owns the
HTTP directive so it is available as $0 in the console.
Follow one interaction
constscope=angular.getScope($0);scope.order;
Check the directive attributes on $0, inspect the values used by its
expressions, then perform the interaction with Network recording enabled. Read
the request method, URL, payload, status, content type, and response before
looking at the rendered result.
After the response, inspect the scope again. If the response is correct but the
scope is not, check the directive assignment such as $res, on-success, or
on-error. If scope is correct but the page is not, inspect the binding or
conditional directive that renders it.
Failure path
Do not debug only the successful request. Preserve the failed response in the
Network panel and inspect the error branch, including status-specific behavior
for 401, 403, 409, 422, and 5xx responses.
Apply it now
Take one interaction that currently feels opaque and write down the value at
each step: element, scope before request, request, response, scope after request,
and DOM result. The first unexpected value identifies the layer to fix.
Verify
Repeat the trace after the fix. Confirm the request occurs once, the correct
branch runs, and a failed response leaves the scope and DOM in a usable state.
9.34 - Control repeated requests
Measure duplicate work, then delay, throttle, cache, or cancel it
Problem
Typing, scrolling, hovering, or repeated clicks can start more requests than the
user needs.
Before you start
Record the interaction in the Network panel. Count requests, transferred bytes,
server time, and stale responses. Do not add request control without seeing the
problem first.
Choose the smallest control
What you observe
First response
Fast typing starts a request per key
Add a short delay after input settles
Scroll or resize fires continuously
Throttle the event
The same read repeats unchanged
Reuse an appropriate public or user-partitioned cache
An old search overwrites a newer one
Reject stale results or cancel the old request
Hover prefetch repeats
Guard it with loaded and loading state
Double-click repeats a write
Disable while pending and make the server idempotent
Use a promise-valued $httptimeout to cancel an obsolete programmatic
request. Treat xhrStatus === "abort" as cancellation rather than a server
failure. HTTP directive flows should usually prevent another trigger while the
current request is pending instead of creating custom cancellation code.
Server contract
Search and list endpoints must tolerate reordered reads. Write endpoints must
protect against duplicate effects with an idempotency key, version, or another
server-side rule when repetition could charge, send, or mutate twice.
Failure path
Always clear loading state after success, error, or cancellation. Never cache an
authorization failure or leak user-specific data through a shared cache.
Apply it now
Find the noisiest interaction in the Network panel. Apply only the first matching
row, then measure it again before combining techniques.
Verify
Trigger the interaction rapidly, use a slow network profile, and force responses
to arrive out of order. Confirm the newest intent wins and writes happen once.
9.35 - Show validation errors returned by the server
Keep validation authoritative on the server and render field errors in place
Problem
A form needs immediate, useful errors without duplicating the server’s validation
rules in browser code.
Before you start
Make the endpoint return 422 with a JSON object whose keys match form control
names. Use short messages that are safe to show directly to the user.
The same error object drives the message, error class, and accessibility state.
Browser validation can improve feedback, but the server still checks every value.
Server contract
Return the saved profile with 2xx. Return a body such as
{"email":"Use a work email address"} with 422 for correctable input.
Reserve 401, 403, 409, and 5xx for their distinct meanings.
Failure path
If an error is not a field error, show a form-level message instead of assigning
it to an unrelated control. Never return stack traces or database messages.
Apply it now
Pick one form that reimplements server validation in JavaScript. Make the server
return named errors and bind those errors beside the matching controls.
Verify
Submit empty, malformed, duplicate, unauthorized, and valid values. Confirm the
right field is identified and keyboard focus remains usable after every response.
9.36 - Protect a cookie-authenticated form from CSRF
Send a server-issued proof with every state-changing form request
Problem
A form uses authentication cookies, so another site may be able to cause the
browser to send an unwanted state-changing request.
Before you start
Configure CSRF protection on the server. Generate a token tied to the user or
session and render it into the page. Cookie SameSite settings help, but they do
not replace server-side CSRF validation.
Submit the server-issued token
<formng-post="/account/email"on-error="errors = $res"><inputtype="hidden"name="csrf_token"value="SERVER_RENDERED_TOKEN"/><label> New email
<inputname="email"type="email"required/></label><buttontype="submit">Change email</button></form>
Replace SERVER_RENDERED_TOKEN in the server template. AngularTS serializes the
named hidden control with the rest of the form; the server validates it before
changing state.
Server contract
Reject a missing, expired, or mismatched token without performing the operation.
Rotate tokens according to the server framework’s policy and compare them using
its maintained CSRF implementation rather than custom cryptography.
Failure path
Return a safe 403 response when the proof fails. Do not automatically retry the
write because a fresh token may require a new authenticated page response.
Apply it now
Inspect every cookie-authenticated POST, PUT, PATCH, and DELETE endpoint.
Start with account, payment, and permission changes.
Verify
Send the request with the valid token, no token, a token from another session,
and an expired token. Confirm only the valid request changes state.
9.37 - Confirm a destructive action in place
Require an explicit second action before sending a delete request
Problem
A single accidental click can delete important data.
Before you start
Make deletion authorization and resource ownership checks authoritative on the
server. Decide whether the operation is reversible and say so in the interface.
The first button changes only local state. The second button sends the request
and names the exact consequence.
Server contract
Return 204 after deletion. Return 404 when the resource no longer exists and
403 when the user may not delete it. Make repeated deletion safe when possible.
Failure path
Keep the confirmation visible after failure so the user can read the message or
cancel. Do not remove the item until the server confirms deletion.
Apply it now
Find the most damaging one-click action in the application and split intent from
execution. Use a specific label such as “Delete report,” not “Yes.”
Verify
Test keyboard-only confirmation, cancellation, double activation, a missing
resource, and a permission rejection. Confirm only one authorized delete occurs.
9.38 - Handle two people editing the same record
Detect stale updates and let the user compare the current server value
Problem
One person can overwrite a change saved by someone else while both are editing
the same record.
Before you start
Give each record a version, revision, or strong ETag. Require the client to send
the version it originally loaded with every update.
Update only when the submitted version matches the stored version. Otherwise
return 409 with a safe representation of the current record. Increment the
version atomically with the successful update.
Failure path
Do not silently retry a conflicting write. Preserve the user’s draft while
showing the current server value so they can compare or merge intentionally.
Apply it now
Add a version check to one record that is commonly edited by multiple people.
Start with content, inventory, scheduling, or permission records.
Verify
Open the same record in two sessions. Save different values in both and confirm
the second save receives 409 without overwriting the first.
9.39 - Let the user retry a failed read
Keep the last useful state and retry only when the user asks
Problem
A temporary network or server failure leaves a section empty or forces a full
page reload.
Before you start
Keep reads safe and idempotent. Decide whether old data is more useful than an
empty screen while a refresh fails.
Keep recovery next to the result
<sectionng-init="orders = []; loadError = undefined"><buttontype="button"ng-get="/api/orders"on-success="orders = $res; loadError = undefined"on-error="loadError = $res"> Refresh orders
</button><png-if="loadError"role="alert"> Orders could not be refreshed. Try again.
</p><png-if="!loadError && orders.length === 0">No orders yet.</p><ul><ling-repeat="order in orders">{{ order.number }}</li></ul></section>
The existing list remains visible if refresh fails. The user controls when
another request is sent.
Server contract
Return a JSON array with 2xx. Use 401 for an expired session and 5xx for a
temporary server failure. Include a safe request ID in unexpected errors.
Failure path
Do not retry writes automatically. Avoid unlimited automatic read retries; they
can amplify an outage and consume mobile data while the user cannot intervene.
Apply it now
Choose one useful read that currently disappears on failure. Preserve its last
successful value and put a retry control beside the error.
Verify
Load successfully, then force offline mode and retry. Confirm the old result
stays visible, the error is announced, and each click sends exactly one request.
9.40 - Paginate a large result on the server
Keep URLs shareable while replacing only the result region
Problem
A large collection is slow to query, transfer, render, and navigate.
Before you start
Make the server return a bounded page and stable next and previous URLs. Prefer a
cursor when records change frequently; use page numbers when random access is
important and the data is stable enough.
Keep real navigation URLs
<section><divid="orders"><p>Orders 21–40</p></div><navaria-label="Order pages"><ahref="/orders?page=1"ng-get="/orders?page=1"data-target="#orders"swap="innerHTML"> Previous
</a><ahref="/orders?page=3"ng-get="/orders?page=3"data-target="#orders"swap="innerHTML"> Next
</a></nav></section>
The href remains useful for opening a page in another tab and for normal
navigation before AngularTS starts. The HTTP directive can replace only the list
when the enhanced interaction runs.
Server contract
Return a complete page for normal navigation and a trusted list fragment for the
enhanced request according to the application’s explicit response policy. Keep
sort and filter values in every pagination URL.
Failure path
Leave the current page visible if the next page fails. Do not advance the visible
page label or browser URL until the requested result succeeds.
Apply it now
Measure the largest rendered list. Move its limit, ordering, and cursor to the
server before adding virtual scrolling or client-side collection machinery.
Verify
Open next and previous links normally, in a new tab, with JavaScript disabled,
and with AngularTS running. Confirm the same records and ordering appear.
9.41 - Download a generated file with the browser
Use normal navigation for files instead of loading bytes into application state
Problem
A report or export needs to download without turning a large binary response into
scope state or manually constructing a browser blob.
Before you start
Make the server stream the file with the correct Content-Type and a safe
Content-Disposition filename. Authorize the download on every request.
Let the browser own the response
<formaction="/reports/orders.csv"method="get"><label> From
<inputname="from"type="date"required/></label><label> To
<inputname="to"type="date"required/></label><buttontype="submit">Download CSV</button></form>
A native form lets the browser stream, name, save, and expose download progress
without retaining the file in AngularTS memory.
Server contract
Validate dates, cap the export size, escape spreadsheet-formula prefixes when
creating CSV, and stream rows instead of building the entire file in memory.
Failure path
For long exports, create a background job and return a status page rather than
holding an HTTP request indefinitely. Show authorization or validation errors as
a normal server page.
Apply it now
Find one download implemented with $http, base64, or a manually created blob.
Replace it with a normal authorized download when no client transformation is
required.
Verify
Test small, empty, large, unauthorized, and maliciously named exports. Confirm
memory stays stable and the downloaded filename and content type are correct.
9.42 - Replace a region with trusted server HTML
Keep rendering on the server without compiling untrusted markup
Problem
The server already renders a useful partial and returning JSON would duplicate
the same presentation rules in a client template.
Before you start
Choose a stable target element. Treat every returned fragment as executable UI:
escape untrusted values on the server and allow only application-owned markup.
Swap the fragment into a named region
<section><buttontype="button"ng-get="/account/summary"data-target="#account-summary"swap="innerHTML"> Refresh summary
</button><divid="account-summary"aria-live="polite"><p>Summary not loaded.</p></div></section>
AngularTS wires directives and bindings in the returned application-owned
fragment. That makes server escaping and content ownership security requirements,
not optional cleanup.
Server contract
Return text/html containing valid fragment markup. Keep scripts, inline event
handlers, user-authored templates, and unsanitized rich text out of the response.
Failure path
Keep the old region on failure and show an error beside the refresh control.
Never swap an authentication page or generic proxy error into the target.
Apply it now
Choose one JSON endpoint whose client template simply recreates existing server
HTML. Return the existing partial directly and remove the duplicate renderer.
Verify
Test names containing <, >, quotes, and markup-like text. Confirm they render
as text, the fragment has balanced tags, and its controls work after replacement.
9.43 - Show a request ID when an operation fails
Give users and operators one safe identifier for tracing a failure
Problem
A user reports “Save failed,” but operators cannot connect that report to one
server request among thousands.
Before you start
Generate or accept a request ID at the trusted server edge, carry it through
logs and downstream calls, and return it in safe unexpected-error responses.
Render the identifier with the error
<formng-post="/api/invoices"on-error="saveError = $res"><label> Customer
<inputname="customer"required/></label><buttontype="submit">Create invoice</button><divng-if="saveError"role="alert"><p>The invoice could not be created.</p><png-if="saveError.requestId"> Support code: {{ saveError.requestId }}
</p></div></form>
The user sees a stable support code, not internal diagnostics.
Server contract
Return a body such as {"message":"Request failed","requestId":"req_..."} for
unexpected errors. Log the same ID with route, status, duration, and safe context.
Never put credentials, personal data, stack traces, or SQL in the response.
Failure path
If the client supplies an ID, validate its format before logging it. Keep an
independently generated internal trace identifier when crossing trust boundaries.
Apply it now
Take one high-value write and follow its request ID from the response to every
relevant server log. Remove any step that requires searching by timestamp alone.
Verify
Force a controlled 5xx, copy the displayed code, and find exactly one request
path in logs. Confirm the response exposes no implementation details.
9.44 - Check a server interaction before shipping
Walk one feature through behavior, failure, access, and operations
Problem
An interaction works in the happy path but its production behavior has not been
checked as one complete system.
Before you start
Choose one real user action and use its production-like endpoint and permissions.
Open the Elements, Console, and Network panels.
Walk the whole path
Check these in order:
Step
Question
Page
Does the useful server-rendered page work before enhancement?
Input
Are labels, names, constraints, and server validation aligned?
Request
Is the method correct and sent only once?
Access
Does the server check authentication, authorization, and CSRF?
Response
Are status, content type, and body appropriate?
State
Does success update only the intended scope and DOM?
Failure
Can the user understand, retry, cancel, or recover safely?
Concurrency
Can stale or duplicate requests corrupt state?
Accessibility
Are status and focus understandable without a pointer?
Operations
Can a request ID connect the visible failure to server logs?
Failure path
Treat any unanswered row as unfinished implementation rather than documentation
debt. Fix the smallest owning layer: browser markup, AngularTS wiring, endpoint,
storage transaction, or operational logging.
Apply it now
Run the table against the newest state-changing interaction. Record evidence for
each answer: a browser trace, server test, accessibility check, or log lookup.
Verify
Repeat with invalid input, no permission, slow networking, duplicate activation,
stale data, and a controlled server failure. Ship when every outcome is deliberate.
9.45 - Recover from a rate-limited request
Respect 429 responses without hiding retries or multiplying traffic
Problem
An endpoint limits repeated requests, but the page reports a generic failure or
immediately retries and makes the limit worse.
Before you start
Make the server return 429 and a Retry-After header. Use rate limits to
protect a resource, not as the only defense against abusive or unauthorized use.
Do not run a tight automatic retry loop. Keep the last useful result visible and
tell the user when another attempt can succeed.
Server contract
Return 429, Retry-After, and a safe body such as
{"message":"Preview limit reached","retryAfter":"14:30"}. Apply the limit to
the correct user, tenant, token, or resource rather than only an IP address.
Failure path
Treat 429 differently from authentication, permission, validation, and server
failures. Do not cache the response longer than the stated limit window.
Apply it now
Find the endpoint most likely to receive bursts. Confirm the page exposes the
limit instead of silently creating more traffic.
Verify
Exceed the limit, inspect the header and body, wait for the window, and retry.
Confirm no background loop continues while the request is limited.
9.46 - Run slow work as a background job
Start work quickly, expose status, and download the finished result
Problem
A report, import, or media conversion takes too long for one request and becomes
fragile behind browser, proxy, or deployment timeouts.
Before you start
Give the job a durable ID, owner, state, progress value, and expiry policy. Make
starting the same job twice safe when duplicate work would be expensive.
Start the job, then ask for status
<section><formng-post="/api/exports"on-success="job = $res"><label> Format
<selectname="format"><optionvalue="csv">CSV</option></select></label><buttontype="submit">Create export</button></form><divng-if="job"><p>Status: {{ job.status }}</p><buttontype="button"ng-get="/api/exports/current"on-success="job = $res"> Refresh status
</button><ang-if="job.downloadUrl"href="{{ job.downloadUrl }}">Download</a></div></section>
Use manual refresh first. Add measured polling or server-sent events only when
the user benefits from continuous progress.
Server contract
Return 202 with the accepted job representation. The status endpoint returns
queued, running, completed, failed, or cancelled. Authorize status and download
URLs against the job owner on every request.
Failure path
Keep a failed job record with a safe explanation and request ID. Do not expose
worker stack traces or leave failed uploads and generated files indefinitely.
Apply it now
Find the longest request in production traces. Move it to a job when its work can
continue independently of the browser connection.
Verify
Test duplicate starts, worker restart, cancellation, failure, expiry, another
user’s job ID, and download after completion.
9.47 - Make a critical write safe to repeat
Prevent double charges, sends, and submissions with an idempotency key
Problem
A double click, reconnect, proxy retry, or impatient refresh can submit the same
critical operation more than once.
Before you start
Generate a unique operation key on the server-rendered page. Scope it to the
authenticated user and operation, and store the first result atomically.
Disabling the button improves feedback but cannot provide this guarantee. The
server key protects retries from every source.
Server contract
For the same user, operation, key, and payload, return the stored first result.
Reject reuse of the key with a different payload. Expire records only after the
operation can no longer be retried safely.
Failure path
Do not mint a new key merely because the response was lost. Reuse the operation
key until the server confirms whether the first request completed.
Apply it now
Add idempotency to the operation with the highest cost of duplication: payment,
invitation, email, provisioning, or irreversible workflow transition.
Verify
Send identical requests concurrently, disconnect after sending, and retry with
the same key. Confirm one effect and one stable result are recorded.
9.48 - Build a multi-step form on the server
Save each step independently instead of keeping a fragile browser-only draft
Problem
A long form needs several steps, but refreshing, navigating back, or changing
devices must not discard completed work.
Before you start
Create a server-side draft with an owner and current step. Validate and save each
step independently. Make every step addressable with a normal URL.
Enhance a normal step form
<formaction="/checkout/address"method="post"ng-post="/checkout/address"data-state-success="checkout-payment"on-error="errors = $res"><label> Delivery address
<textareaname="address"required></textarea></label><png-if="errors.address">{{ errors.address }}</p><buttontype="submit">Continue to payment</button></form>
The server saves the address before advancing. AngularTS can enter the next
router state after success, while the native action remains a working fallback.
Server contract
Return 422 for step errors, redirect a normal successful submission to the next
URL, and return 2xx for the enhanced request. Reject steps the user is not yet
allowed to complete.
Failure path
Keep the saved draft when a later step fails. Never trust a hidden step number as
proof that earlier requirements or payment checks succeeded.
Apply it now
Choose the longest form and split it at points where the server can save a useful
draft. Keep related fields together rather than creating arbitrary page counts.
Verify
Refresh every step, use the Back button, open the draft on another device, submit
steps out of order, and resume after a failed request.
9.49 - Apply one action to several records
Submit explicit selections and return a result for every record
Problem
Users need to update several records without opening and submitting each one
individually.
Before you start
Choose a maximum batch size. Authorize every selected record independently and
decide whether the operation is atomic or may partially succeed.
Submit explicit selected controls
<formng-post="/api/tasks/archive"on-success="result = $res"><fieldset><legend>Tasks to archive</legend><label><inputname="task_42"type="checkbox"/> Draft invoice</label><label><inputname="task_43"type="checkbox"/> Review contract</label></fieldset><buttontype="submit">Archive selected tasks</button><png-if="result">Archived {{ result.archived }} tasks.</p><ul><ling-repeat="error in result.errors">{{ error.message }}</li></ul></form>
Named controls make the submitted selection inspectable in the Network panel.
For dynamic lists, use the server’s documented array representation consistently.
Server contract
Return a count and per-record failures. If partial success is allowed, make the
response identify exactly which records changed. If it must be atomic, perform
all authorization and validation before committing any record.
Failure path
Do not report the whole batch as successful when some records failed. Preserve
failed selections so the user can correct or retry them.
Apply it now
Find a repetitive list action and define its batch limit, authorization rule,
transaction behavior, and response before adding selection controls.
Verify
Test no selection, the maximum size, duplicates, mixed permissions, missing
records, partial failure, and a repeated submission.
9.50 - Archive first and offer restore
Make a destructive-looking action recoverable when the domain allows it
Problem
Users need to remove an item from active work, but permanent deletion is too easy
to regret.
Before you start
Model archived state on the server. Define retention, visibility, uniqueness,
restore permissions, and the separate conditions for permanent deletion.
The page changes only after each server operation succeeds.
Server contract
Make archive and restore idempotent. Record who changed the state and when. Keep
permanent deletion as a separate, more restricted operation.
Failure path
If restore fails, keep the archived message and explain the conflict or permission
problem. Do not imply that a client-side undo can reverse a committed server action.
Apply it now
Replace one permanent delete with archive and restore where legal and operational
requirements allow retention.
Verify
Archive twice, restore twice, restore after a naming conflict, test another user,
and confirm archived records stay out of active queries.
9.51 - Handle a permission rejection without hiding it
Let the server decide access and give the user a useful next step
Problem
The interface hides controls based on known permissions, but permissions can
change and direct or stale requests still reach the server.
Before you start
Authorize every operation on the server using the authenticated user and current
resource. Client visibility is presentation, not enforcement.
Render the server rejection
<formng-put="/api/team/42/settings"on-success="settings = $res; accessError = undefined"on-error="accessError = $res"><label> Team name
<inputname="name"value="{{ settings.name }}"required/></label><buttontype="submit">Save settings</button><png-if="accessError"role="alert"> {{ accessError.message }}
</p></form>
A disabled or hidden button can reduce confusion when permission is already
known, but the rejection path remains necessary.
Server contract
Return 401 when authentication is missing and 403 when the authenticated user
lacks permission. Keep the message safe when even resource existence is private.
Failure path
Do not redirect every 403 to login; signing in again cannot grant a missing
permission. Offer a useful next step such as returning, requesting access, or
contacting an administrator.
Apply it now
Open one privileged action in two sessions, remove permission in one, and submit
the stale form in the other. Make the resulting rejection understandable.
Verify
Test signed out, wrong role, wrong tenant, removed permission, deleted resource,
and authorized access. Confirm no rejected request changes state.
9.52 - Keep search and filters in the URL
Make filtered results reloadable, shareable, and usable without JavaScript
Problem
Filters exist only in browser state, so refresh, sharing, Back, and opening a new
tab lose the current result.
Before you start
Define stable query parameter names and defaults on the server. Return the same
result for the same authorized URL.
Start with a real GET form
<formaction="/orders"method="get"ng-get="/orders"data-target="#orders"swap="innerHTML"><label>Status <selectname="status"><option>Open</option></select></label><label>Search <inputname="q"type="search"/></label><buttontype="submit">Apply filters</button></form><sectionid="orders"aria-live="polite"><p>Choose filters to update the result.</p></section>
The native GET form defines the canonical URL. Enhancement may replace the result
region, but it must preserve a URL that reproduces the same filters.
Server contract
Parse, validate, normalize, and cap query values. Include normalized filters in
pagination links and render them back into the controls on complete pages.
Failure path
Leave the current result visible when an enhanced request fails. Do not write an
unconfirmed filter state into browser history.
Apply it now
Refresh the most-used filtered list. Move every lost filter into a named GET
control and make the resulting URL reproduce the page.
Verify
Submit normally, copy the URL, open it in another tab, use Back and Forward, and
repeat with JavaScript disabled and AngularTS running.
9.53 - Log out with a state-changing form
End the server session, clear private state, and avoid logout links
Problem
Logout is implemented as a GET link or only clears browser state, leaving the
server session active or allowing unwanted cross-site logout requests.
Before you start
Provide a server POST endpoint that invalidates the session and rotates relevant
credentials. Decide which private caches and client state must be discarded.
The normal form works without client routing. The enhanced form can enter a
public signed-out state after the server confirms invalidation.
Server contract
Invalidate the session, clear or expire authentication cookies, and return a
public response. Make repeated logout safe. Never use GET for this state change.
Failure path
If the server cannot confirm logout, do not claim the session ended. Avoid keeping
private response data in a cache that another signed-in user can inherit.
Apply it now
Inspect the current logout request and subsequent Back navigation. Remove any
private page or cached data that remains usable after a confirmed logout.
Verify
Log out normally and with AngularTS, repeat the request, use Back, open a private
URL, and sign in as another user in the same browser.
9.54 - Keep requests same-origin unless they must cross origins
Configure CORS and credentials deliberately for a separate API origin
Problem
The page and API use different origins, introducing preflight,
credential, cookie, caching, and deployment behavior that same-origin requests
avoid.
Before you start
Ask whether the application server can proxy the API under the page’s origin.
Prefer that simpler setup unless direct browser access to the other origin is a
real requirement.
Configure the server, not the browser workaround
Allow only known application origins, required methods, and required headers.
When cookies are involved, use explicit origins and credentials; wildcard origin
responses cannot safely represent authenticated access. Keep CSRF protection for
cookie-authenticated state changes.
Server contract
Handle preflight requests consistently, vary cached responses by origin when
needed, and return the same authorization and validation statuses as a same-origin
endpoint. Treat CORS as browser access policy, not user authorization.
Failure path
Do not disable browser security, reflect arbitrary Origin values, put secrets
in URLs, or convert a CORS failure into a JSONP-style executable response.
Apply it now
List every production origin that can call the API and every credential it sends.
Remove unused origins, headers, and methods from the server policy.
Verify
Test the allowed production origin, an unlisted origin, preflight, credentials,
cache behavior, and a state-changing request without CSRF proof.
9.55 - Handle a uniqueness race on submit
Let the database decide uniqueness and return the conflict to the field
Problem
The page checks that a name is available, but another request claims it before
the user submits.
Before you start
Enforce uniqueness with a database constraint. Availability checks improve
feedback but can never reserve the value unless the server explicitly does so.
Attempt the insert and translate the known uniqueness violation into 409 or a
field-oriented 422 body such as {"slug":"That address is already used"}.
Do not expose database error text.
Failure path
Preserve every other form value. Ask the user to choose another value instead of
retrying the same conflicting write automatically.
Apply it now
Find one pre-submit uniqueness check and confirm the final write still handles a
database constraint violation as an expected outcome.
Verify
Submit the same value concurrently from two sessions. Confirm one succeeds, one
gets the field error, and no duplicate record is stored.
9.56 - Submit money without floating-point surprises
Send a decimal string and perform currency arithmetic on the server
Problem
An amount changes by a cent because browser or server code treats currency as a
binary floating-point number.
Before you start
Choose the currency, accepted decimal precision, rounding rule, minimum, and
maximum on the server. Never infer currency only from the user’s locale.
Submit the entered decimal text
<formng-post="/api/invoices"on-error="errors = $res"><label> Amount in EUR
<inputname="amount"inputmode="decimal"autocomplete="off"required/></label><inputname="currency"type="hidden"value="EUR"/><png-if="errors.amount">{{ errors.amount }}</p><buttontype="submit">Create invoice</button></form>
Server contract
Parse the amount with a decimal or money type, validate its scale, and store
minor units or an exact decimal together with the currency. Return formatted
display text separately from the canonical value when needed.
Failure path
Reject ambiguous separators and unsupported precision with 422. Do not silently
round an amount when that changes what the user authorized.
Apply it now
Trace one amount from input through JSON, application code, database, and output.
Remove every binary floating-point conversion from that path.
Verify
Test 0.01, large values, too many decimal places, negative values, comma input,
and a currency with a different minor-unit rule.
9.57 - Submit dates and times with an explicit time zone
Distinguish calendar dates, local times, and exact instants
Problem
A date or appointment moves by a day or hour when users and servers use different
time zones.
Before you start
Decide whether each value is a calendar date, a local wall-clock time, or an exact
instant. These are different domain values and need different storage rules.
Send the local value and zone together
<formng-post="/api/appointments"on-error="errors = $res"><label> Local start
<inputname="localStart"type="datetime-local"required/></label><label> Time zone
<selectname="timeZone"required><optionvalue="Europe/Riga">Europe/Riga</option></select></label><png-if="errors.localStart">{{ errors.localStart }}</p><buttontype="submit">Schedule</button></form>
Server contract
Resolve local time with an IANA zone, reject nonexistent daylight-saving times,
and define how ambiguous times are chosen. Store exact instants in UTC while
retaining the zone when future local scheduling depends on it.
Failure path
Do not append Z to a local value or assume the server’s zone. Return 422 when
the local time cannot identify the intended instant.
Apply it now
Classify every date field in one workflow as date, local time, or instant. Rename
ambiguous fields and include the missing zone where required.
Verify
Test users in two zones, both daylight-saving transitions, leap day, midnight,
and a date-only value that must never shift.
9.58 - Keep identifiers as opaque strings
Avoid rounding, parsing, and leaking meaning through record IDs
Problem
A large numeric ID is rounded in JavaScript or application code starts depending
on structure that should be private to the server.
Before you start
Define public identifiers as strings even when the database uses numbers. Treat
them as labels, not values for arithmetic or ordering.
Accept and return the identifier as a string. Validate its syntax, then authorize
the referenced resource independently. An unguessable ID is not permission.
Failure path
Reject malformed IDs without coercing them. Avoid messages that reveal private
resource existence when the requester lacks access.
Apply it now
Search one API response for identifier fields typed as numbers. Convert public
IDs to strings before they cross the server boundary.
Verify
Round-trip IDs larger than JavaScript’s safe integer, leading-zero IDs, UUIDs,
and malformed strings without changing their text.
9.59 - Revalidate a cached response with ETag
Return 304 when unchanged instead of transferring the same representation
Problem
The browser repeatedly downloads a response that rarely changes.
Before you start
Confirm the request is safe and cacheable. Decide whether the representation is
public, user-specific, or private before choosing cache headers.
Let HTTP perform revalidation
Return a stable strong or weak ETag with the representation. On a later request,
compare If-None-Match and return 304 without a body when it still matches.
Normal navigation and standards-based HTTP clients can then reuse cached bytes.
Server contract
Change the ETag whenever the selected representation changes. Send appropriate
Cache-Control and Vary headers. Partition authenticated content and never mark
private user data as shared public cache content.
Failure path
Do not use 304 when authorization changed or when the selected representation
differs by language, encoding, origin, or user and the cache key ignores it.
Apply it now
Use the Network panel to find the largest repeated unchanged read. Add validators
at its server renderer before introducing a custom application cache.
Verify
Request once, revalidate, change the record, and revalidate again. Confirm 304
only while the body is unchanged and no private response crosses users.
9.60 - Keep a useful page during planned maintenance
Return a clear 503 response while preserving public navigation and recovery
Problem
A deployment or dependency outage turns the application into an indefinite
spinner, proxy error, or misleading authentication failure.
Before you start
Prepare a small server-rendered maintenance page that does not depend on the
database or application service being maintained.
Return the correct temporary status
Use 503 Service Unavailable and a realistic Retry-After header. Explain what
is unavailable, whether existing data is safe, and when the user should retry.
Keep status and support links on dependencies that remain available.
Server contract
Health and load-balancer behavior must remove unhealthy instances without caching
the maintenance page as a permanent success. Static assets required by the page
must remain reachable or be embedded conservatively.
Failure path
Do not return 200 for a failed application page, redirect repeatedly, or expose
deployment details. Avoid automatic browser retries that create a recovery surge.
Apply it now
Open the application with its database unavailable in a production-like setup.
Replace the first raw failure with the smallest dependable maintenance response.
Verify
Test planned maintenance, an unexpected dependency outage, crawler behavior,
cache headers, recovery, and multiple browser tabs retrying together.
9.61 - Return users safely after sign-in
Preserve a destination without creating an open redirect
Problem
The login flow accepts an arbitrary return URL, allowing attackers to send users
from a trusted sign-in page to a malicious site.
Before you start
Prefer a server-side return target stored in the session. If a destination must
travel through the form, restrict it to known local routes or an allowlist.
Resolve the destination against an allowlist or accept only normalized local
paths. Reject protocol-relative URLs, encoded scheme tricks, control characters,
and destinations outside the application.
Failure path
Use a safe default page when the destination is missing or invalid. Never copy an
unchecked query or form value into the redirect response header.
Apply it now
Trace every return, next, redirect, and continue parameter to the server
code that validates its final destination.
Verify
Test a valid local path, absolute external URL, //host, encoded separators,
backslashes, and nested redirect parameters.
9.62 - Return one safe error shape
Make expected failures predictable without hiding their HTTP meaning
Problem
Every endpoint returns errors differently, so forms contain endpoint-specific
parsing and unexpected failures leak internal details.
Before you start
Define a small envelope with a safe message, optional field errors, a stable code,
and a request ID. Keep HTTP status as the primary category.
Keep the envelope stable across 401, 403, 409, 422, 429, and 5xx, but
preserve those statuses. Use machine codes only for behavior the client actually
needs; do not mirror exception class names.
Failure path
If an intermediary returns HTML or an empty body, show a generic safe fallback.
Error rendering must not throw another exception while parsing an unknown body.
Apply it now
Compare three endpoints’ failure bodies. Replace accidental differences with the
smallest common envelope rather than a large universal error object.
Verify
Exercise every documented status plus a proxy HTML error and malformed body.
Confirm the page remains usable and exposes no internal data.
9.63 - Make full-page and fragment responses explicit
Let one server route return the representation the request actually needs
Problem
The same URL serves normal navigation and an enhanced region update, but the
server sometimes returns a full page where a fragment is expected.
Before you start
Define an explicit request policy using Accept, a documented request header, or
separate routes. Do not infer intent from fragile user-agent checks.
Return a complete document for normal navigation and a valid application-owned
fragment for the enhanced request. Set Content-Type, cache variation, and error
responses consistently for both representations.
Failure path
Never swap a complete login, maintenance, or proxy page into the region. Detect
authentication and server failures by status before rendering the body.
Apply it now
Capture the normal and enhanced requests for one route. Write down exactly which
request signal chooses each representation.
Verify
Open the link normally, in a new tab, and through the enhanced interaction. Test
signed-out and maintenance responses in all three cases.
9.64 - Render user-authored rich text safely
Sanitize stored markup and keep it separate from application templates
Problem
Users can author formatted content, but inserting their HTML as an AngularTS
fragment could compile attacker-controlled directives or executable markup.
Before you start
Choose a maintained allowlist sanitizer and a deliberately small formatting
policy. Sanitize on the server before storage or rendering, not only in the browser.
The shell may use AngularTS bindings. The user-authored body is sanitized content,
not a template and not a source of directives.
Server contract
Allow only required elements and attributes, normalize URLs, block executable
schemes, and add safe link behavior. Re-sanitize old content when policy defects
are fixed.
Failure path
Do not trust HTML because it came from your database, Markdown renderer, staff
account, or another service. Stored content can remain dangerous for years.
Apply it now
Trace one rich-text field from input to output and identify the exact sanitizer,
policy, version, and test suite protecting it.
Verify
Test scripts, event attributes, SVG, malformed nesting, encoded URLs, CSS escape
paths, and ordinary formatting that must remain intact.
9.65 - Add Content Security Policy in report-only mode
Measure executable-content dependencies before enforcing a restrictive policy
Problem
The application has no browser policy limiting where scripts, styles, frames, and
connections may come from.
Before you start
Inventory real production resources and choose a report endpoint that removes
sensitive URL and user data before storage.
Start by observing
Send Content-Security-Policy-Report-Only from the server. Begin with narrow
directives for default-src, script-src, style-src, img-src, font-src,
connect-src, frame-ancestors, base-uri, and form-action. Use nonces or
hashes for deliberate inline content instead of broad unsafe allowances.
Server contract
Generate a fresh nonce per response when used, attach it only to trusted server
scripts, and keep the policy consistent across normal, error, login, and
maintenance pages.
Failure path
Do not copy a restrictive example directly into enforcement. Missing origins can
break authentication, uploads, workers, WebTransport, or error reporting.
Apply it now
Run report-only policy on one production route, classify every violation, and
remove unnecessary resource origins before adding exceptions.
Verify
Exercise every application feature, browser extension noise, errors, and signed-out
pages. Enforce only after expected traffic produces no unexplained violations.
9.66 - Harden an authentication session cookie
Keep session credentials out of JavaScript and restrict browser delivery
Problem
Authentication state is readable by browser scripts or sent more broadly than
the application requires.
Before you start
Use the server framework’s maintained session implementation. Store only an opaque
session identifier in the browser, not authorization state or personal data.
Set the cookie at the server
Use Secure, HttpOnly, and an appropriate SameSite value. Keep Path and
Domain as narrow as the deployment allows. Rotate the session after sign-in and
privilege changes, and expire it on logout.
Server contract
Validate expiry and revocation on every authenticated request. Protect
state-changing cookie requests from CSRF. Avoid accepting the same credential in
both cookies and URLs.
Failure path
Do not fall back to local storage when a script cannot read an HttpOnly cookie;
that unreadability is the protection. A secure cookie still requires XSS defenses.
Apply it now
Inspect the production Set-Cookie response and one authenticated request. Explain
every attribute and remove any unnecessary domain or lifetime.
Verify
Test HTTPS, logout, expiry, session rotation, subdomains, cross-site requests, and
an attempted document.cookie read.
9.67 - Build a password-manager-friendly sign-in form
Use normal controls and autocomplete values before adding custom behavior
Problem
Custom sign-in controls prevent password managers, keyboard submission, or browser
security features from recognizing the form.
Before you start
Use a normal server POST endpoint over HTTPS. Keep labels, control names, and a
submit button in the document.
Use a generic authentication error, rate-limit abuse, rotate the session after
success, and keep account recovery separate. New-password forms use
autocomplete="new-password".
Failure path
Do not disable paste, invent text controls that imitate passwords, reveal whether
an account exists, or clear the username after a failed attempt.
Apply it now
Run the sign-in form with a browser password manager and keyboard only. Remove
custom behavior that blocks recognition or submission.
Verify
Test saved credentials, multiple accounts, failed login, Enter submission,
password reveal controls, and sign-in with JavaScript disabled.
9.68 - Keep secrets out of errors and logs
Return safe diagnostics while preserving enough context to trace failures
Problem
Request bodies, headers, URLs, or exceptions can copy credentials and personal
data into browser errors and long-lived logs.
Before you start
Classify credentials and sensitive fields. Define allowlisted log fields instead
of trying to redact an unlimited set after serialization.
Never return or log the submitted key. Log an allowlisted operation name, safe
resource ID, status, duration, and request ID. Redact downstream exception data
before it crosses service boundaries.
Failure path
Do not place secrets in query strings, analytics events, breadcrumbs, DOM data
attributes, or client-side persistent state. Treat screenshots as exportable data.
Apply it now
Trigger one failed credential submission in a safe environment and follow it
through browser tools, proxy logs, application logs, and error reporting.
Verify
Search every captured artifact for the exact submitted secret and related personal
values. The search must return nothing.
9.69 - Focus the first field rejected by the server
Move keyboard users directly to a field-level validation problem
Problem
The server returns validation errors below the visible area and keyboard users do
not know which control needs attention.
Before you start
Expose important controls with ng-el and return field errors using control names.
Focus only after the failed response, not while the user is typing.
For several fields, let the response handler choose the first rejected control in
document order. Keep the visible message associated with its input.
Server contract
Return stable field names and safe messages with 422. Form-level failures must
target a form summary rather than an arbitrary input.
Failure path
Do not steal focus for background refreshes or move it repeatedly as messages
render. If the control is hidden, reveal it before focusing.
Apply it now
Submit the longest form from its last field and note where focus remains. Move it
to the first actionable server error.
Verify
Use keyboard and screen reader navigation with one error, several errors, a hidden
section, and a form-level server failure.
9.70 - Turn an empty result into the next action
Distinguish no data from loading and failure, then explain what to do
Problem
An empty collection renders a blank region that looks broken and gives no path
forward.
Before you start
Separate first-use emptiness, filter emptiness, permission limits, loading, and
request failure. They require different messages and actions.
Render the empty case deliberately
<section><ulng-if="projects.length > 0"><ling-repeat="project in projects">{{ project.name }}</li></ul><divng-if="projects.length === 0"><h2>No projects yet</h2><p>Create the first project to organize this work.</p><ahref="/projects/new">Create a project</a></div></section>
Server contract
Return an empty collection as a successful result. Do not use 404 for a valid
collection with no records. Preserve active filters so the empty explanation can
offer clearing them when appropriate.
Failure path
Never render the empty state while the request failed or has not completed; doing
so falsely tells the user that their data is gone.
Apply it now
Find one blank list and write the exact reason it can be empty. Add the next useful
action for that reason.
Verify
Test first use, filters with no matches, permission-limited data, slow loading,
request failure, and a non-empty result.
9.71 - Preserve focus when replacing server HTML
Keep the trigger stable or move focus to a deliberate result heading
Problem
A fragment replacement removes the focused element or leaves keyboard users
outside the newly displayed content.
Before you start
Choose whether focus should remain on a stable trigger or move to the result. Add
a programmatically focusable heading only when moving focus is useful.
Keep the target stable and focus its heading
<section><buttonng-get="/account/summary"data-target="#summary-body"swap="innerHTML"on-success="summaryHeading.focus()"> Load summary
</button><article><h2ng-el="summaryHeading"tabindex="-1">Account summary</h2><divid="summary-body"><p>Summary not loaded.</p></div></article></section>
Server contract
Return only the body fragment so the stable heading and its ng-el reference are
not removed. Use valid application-owned HTML.
Failure path
Keep focus on the trigger after failure and announce the error nearby. Do not move
focus for silent prefetch or background refreshes.
Apply it now
Run one fragment swap using only the keyboard and observe the active element before
and after. Make that transition deliberate.
Verify
Test success, failure, repeated replacement, screen reader announcements, and a
user who activates the control with keyboard or pointer.
9.72 - Deliver a feature flag with the server page
Avoid a second request and keep authorization separate from rollout
Problem
The page waits for a second request before deciding which small interface variation
to show, causing flicker and inconsistent first rendering.
Before you start
Evaluate the flag on the server using the authenticated request and rollout policy.
Treat the result as presentation, never as permission.
Initialize the evaluated result
<sectionng-init="flags = { compactCheckout: false }"><ang-if="flags.compactCheckout"href="/checkout/compact"> Use compact checkout
</a><ang-if="!flags.compactCheckout"href="/checkout"> Continue to checkout
</a></section>
Render the evaluated boolean into the server template. Do not expose targeting
rules, experiment secrets, or unrelated flags.
Server contract
Keep both paths authorized and compatible with current data. Record the evaluated
variant with operational events needed to compare rollout health.
Failure path
Choose a safe default when flag evaluation fails. Removing a UI path does not make
its endpoint unavailable to direct requests.
Apply it now
Find one startup request used only for feature configuration. Move the required
evaluated values into the initial server response.
Verify
Test both variants, evaluation failure, direct endpoint access, rollback, cached
pages, and users moving between rollout groups.
9.73 - Record who changed sensitive data
Write an audit event in the same server operation as the state change
Problem
A sensitive record changed, but the application cannot explain who changed what,
when, or through which operation.
Before you start
Define which domain actions require audit history, who may read it, retention,
redaction, and the difference between audit events and diagnostic logs.
Ask for context when the domain needs it
<formng-post="/api/accounts/42/suspend"on-error="errors = $res"><label> Reason for suspension
<textareaname="reason"required></textarea></label><buttontype="submit">Suspend account</button></form>
Server contract
Derive actor, tenant, request ID, time, and affected resource on the server. Record
the domain action and safe before-and-after facts in the same transaction or a
reliable transactional outbox.
Failure path
Do not trust actor or timestamp fields from the form. Avoid placing credentials,
unbounded record snapshots, or unnecessary personal data in the audit event.
Apply it now
Choose one permission, billing, account, or data-export action and prove its state
change cannot commit without its required audit event.
Verify
Test success, transaction rollback, retry, impersonation where supported, redaction,
retention, and unauthorized audit-log access.
9.74 - Roll out a request contract without breaking old pages
Expand, migrate, and contract server behavior across cached browser versions
Problem
A deployment changes a field or response shape while users still have pages,
workers, or tabs running code from the previous release.
Before you start
Measure how long old clients and cached pages remain active. Identify producers,
consumers, integrations, queued jobs, and persisted data using the contract.
Use an expand-and-contract rollout
First make the server accept old and new requests. Then deploy clients that use
the new shape. Observe adoption and errors. Migrate stored data and background
producers. Remove old behavior only after the supported compatibility window.
Server contract
Ignore additive response fields safely, preserve field meaning, and use a new field
or endpoint for incompatible semantics. Keep rollback possible throughout rollout.
Failure path
Do not rename or repurpose a field in one deployment, assume all tabs refresh, or
couple database migration completion to a single frontend release instant.
Apply it now
Take the next contract change and write its expand, client migration, observation,
data migration, and removal steps before implementation begins.
Verify
Run old client against new server, new client against compatible old behavior,
mixed workers, rollback, delayed jobs, and a tab kept open across deployment.
9.75 - Find a beginner recipe
Start with inspection, server data, DOM wiring, events, forms, and HTTP
Start here
Work through these recipes in order until you reach the problem you need to solve.
Each one assumes only the earlier browser and HTML concepts.
Build a bidirectional chat channel with reconnect and cleanup
Problem
The browser must send messages while the server pushes new messages over the
same long-running connection. Repeated HTTP requests or SSE only solve half of
that conversation.
Before you start
Use WebSocket only when both sides need to send independently. Prefer SSE when
the browser only receives updates, and ordinary HTTP when messages are
infrequent.
For a custom production runtime, include websocketModule from
@angular-wave/angular.ts/runtime/realtime. Do not include the aggregate
realtimeModule unless the same runtime also uses SSE and WebTransport.
Open a chat connection
Keep the connection in one controller or service. Update the scope-owned view
model from callbacks and close the connection with the scope.
send() serializes the object as JSON. Give every client message an ID so the
server can ignore a duplicate after a reconnect. Validate the message type,
size, authorization, and payload on the server before broadcasting it.
<sectionng-controller="ChatController as chat"><p>Connection: {{ chat.status }}</p><olaria-live="polite"><ling-repeat="message in chat.messages">{{ message.text }}</li></ol><formng-submit="chat.send()"><label>Message <inputng-model="chat.draft"/></label><buttontype="submit"ng-disabled="chat.status !== 'connected'"> Send
</button></form></section>
Use wss:// from an HTTPS page. At the server or reverse proxy, enable the HTTP
upgrade, authenticate the handshake, check the request origin, cap frame and
queue sizes, and enforce idle timeouts. Cookies can authenticate the handshake;
a short-lived connection ticket is safer than a reusable token in the URL.
Failure path
A reconnect creates a new transport, not a continuation of the old one. The
server may have accepted a message before the connection disappeared, so retry
only messages with stable IDs and deduplicate them server-side. Resume incoming
messages from a server cursor when gaps matter.
Do not queue without a limit while offline. Disable writes or keep a small,
visible outbox. When the queue reaches its limit, reject new entries and tell
the user instead of allowing memory use to grow.
Apply it now
Choose one interaction where either side must speak without waiting for the
other, such as chat, collaborative presence, or live control. Define the message
types and IDs first, then add reconnect and resume behavior before building the
interface.
Verify
Send a valid message, malformed JSON, an oversized frame, and an unauthorized
message. Interrupt the connection before and after server acceptance. Confirm
duplicates are ignored, missing messages resume, the offline queue stays
bounded, reconnect attempts stop after scope destruction, and the production
proxy keeps the upgrade open.
9.86 - Integrate AngularTS with another framework
Choose DOM ownership, messages, observed attributes, or shared reactive state
Problem
An AngularTS module must coexist with a web component or another UI framework
without both runtimes fighting over the same DOM and state.
Before you start
Give each framework its own root element. One framework owns rendering and
cleanup inside that root. Integrate at the root boundary instead of compiling
the same subtree twice.
Use browser custom events directly when one element and its immediate owner are
the only participants. Use the event bus when producers and consumers should not
know each other. Use a model when consumers need the current value, not only a
notification that it changed.
Failure path
Do not use the event bus as a state store, poll DOM attributes that can be
observed, or let two frameworks render the same children. Every subscription,
observer, and external framework root needs one clear lifecycle owner.
Apply it now
Choose one existing cross-framework callback and classify what crosses the
boundary: DOM state, a transient event, or durable current state. Replace the
custom glue with the matching bridge.
Verify
Mount and unmount both roots repeatedly. Confirm messages are delivered once,
attribute state remains current, models update both consumers, and no listener
or observer survives its owner.
9.87 - Send messages across framework roots
Use the application event bus for asynchronous cross-boundary notifications
Problem
A component in another framework needs to notify an AngularTS module without
holding its controller, scope, or DOM implementation.
Before you start
Name the event after something that happened, such as product:selected. Keep
the payload small and define its shape at the integration boundary.
The injected $eventBus and angular.eventBus expose the same application bus.
Delivery is asynchronous through a microtask and listeners run in subscription
order. Passing a scope as the subscription context removes the listener when the
scope is destroyed and skips queued delivery to that destroyed scope.
Use the right event mechanism
Use scope events for parent and child communication in one scope tree. Use DOM
custom events for an element and its owner. Use the event bus for decoupled
modules, workers, realtime services, and non-Angular framework roots.
Failure path
The bus does not retain the latest payload and does not own state. A late
subscriber does not receive an earlier event. Subscriber failures are sent to
the configured exception handler.
Apply it now
Replace one global callback registry with a named event bus topic. Give the
subscription a scope owner or call the returned unsubscribe function explicitly.
Verify
Publish before and after subscribing, destroy the owning scope before queued
delivery, publish twice, and mount the external root twice. Confirm the expected
listener runs once per publication and is removed on teardown.
9.88 - Read Fluent UI custom-element state with ng-observe
Mirror reflected web-component attributes into AngularTS scope
Problem
A custom element owns its internal behavior and reports current state by
changing an attribute. AngularTS needs that value without taking over the
element’s DOM.
Before you start
Confirm the component reflects the required state to an attribute. ng-observe
watches attributes with MutationObserver; it does not poll JavaScript-only
properties.
Observe the Fluent UI attribute
This example is taken from the repository’s Fluent UI observe demo.
<section><fluent-selectng-observe-current-value="selectedSection"title="Select a section"><fluent-optionvalue="Beginning">Beginning</fluent-option><fluent-optionvalue="Middle">Middle</fluent-option><fluent-optionvalue="End">End</fluent-option></fluent-select><p>Selected section: {{ selectedSection }}</p></section>
ng-observe-current-value="selectedSection" watches the current-value
attribute and writes its value to selectedSection on the current scope. If the
directive value is omitted, the camel-cased attribute name becomes the scope
name: ng-observe-aria-checked writes to ariaChecked.
The same shipped demo observes activeid on fluent-tabs, value on
fluent-radio-group, and aria-checked on fluent-switch.
Failure path
Use the exact reflected attribute name documented by the web component. If the
component changes only a JavaScript property, use its custom event or an adapter
instead. The observer disconnects automatically when the scope is destroyed.
Apply it now
Open the custom element in the Elements panel and interact with it. Identify the
attribute that actually changes, then bind only that attribute with
ng-observe.
Verify
Check the initial reflected value, change the selection, set the same value
again, and destroy the AngularTS root. Confirm scope follows real attribute
changes and the observer is disconnected on teardown.
9.89 - Share current state through an application model
Give AngularTS and another framework one reactive domain object
Problem
Two framework roots need the current shopping-cart state. Sending only
item:added events makes late consumers reconstruct state and handle missed
messages.
Before you start
Choose one owner for the model and its mutations. Retrieve the same registered
model from the AngularTS application injector instead of copying it into each
framework store.
Inject cart into AngularTS controllers, services, or component views. A
non-Angular root can retrieve the same instance from the injector associated
with the shared application root.
Nested object and array mutations are reactive. AngularTS interpolation,
watchers, repeats, and component views consuming the model receive the updated
cart. Framework roots managed by the same application context receive the same
named model instance.
Render the shared value
<asideng-controller="CartSummary as summary"><p>Cart items: {{ summary.cart.items.length }}</p><ul><ling-repeat="item in summary.cart.items">{{ item.name }}</li></ul></aside>
Failure path
Do not maintain a second cart and synchronize it with event messages. Do not put
view-only details such as open menus or focused controls into the domain model.
Use the event bus for transient notifications and the model for current state.
Apply it now
Choose one concept shared across roots, such as cart, session, playback, or
workspace selection. Register it once and replace copied stores with that model.
Verify
Add an item from each framework, mount a consumer after items already exist,
mutate a nested array, and destroy one root. Confirm every live consumer sees
the same cart and destroyed views stop observing it.
9.90 - Build a smaller production runtime
Compose, bundle, measure, and verify only the AngularTS features an application uses
Problem
The production JavaScript bundle is larger than its measured budget even though
the application uses only a small part of AngularTS. Importing the full runtime
ships directives and services that this page never uses.
Before you start
Optimize the server response, images, fonts, and application code before
maintaining a custom runtime. Use this recipe only after a production bundle
report shows that unused AngularTS features are material.
createAngular accepts an
AngularComposition and
returns an AngularRuntime. Its
core contains compilation, scopes, controllers, expressions, interpolation, and
exception handling. You explicitly add every directive, service, filter, and
optional RuntimeModule used by
your application.
Create one runtime entry
Create src/runtime.ts. Import package subpaths directly so the bundler can
discard modules that are not reachable from this entry.
Register the exact directive names used by the HTML. A custom runtime does not
discover ng-click from a template and add it automatically.
<sectionid="counter-app"ng-controller="CounterController as counter"><strong>{{ counter.count }}</strong><buttontype="button"ng-click="counter.increase()">Increase</button></section>
Import optional modules only when the application uses them. For example, add
routerModule from @angular-wave/angular.ts/runtime/router for browser-owned
routing. Import sseModule, websocketModule, or webTransportModule from
@angular-wave/angular.ts/runtime/realtime when only one live transport is
needed. Use machineModule or workflowModule from
@angular-wave/angular.ts/runtime/orchestration instead of their aggregate when
only one orchestration feature is needed. Do not import the package root from
this entry; it restores the general runtime and its auto-bootstrap behavior.
Build it with Vite
Point the production build at the runtime entry. Keep property names intact:
HTML expressions and dependency-injection tokens refer to them by name.
Run npm exec vite build. Serve the hashed file with a long-lived immutable
cache policy and deploy the generated HTML or manifest that references its new
hash. Upload the hidden source map to error monitoring, but do not serve it as a
public asset unless that is intentional.
Vite’s default minifier does not mangle object properties. If a different
minifier is introduced, do not property-mangle controller members, model
members, directive attributes, injection tokens, or names read by server HTML.
Use static $inject or array injection for every dependency-bearing function.
Measure the artifact
Compare the custom build with the existing production build under the same
target, minifier, and compression settings. Record raw, gzip, and Brotli sizes;
the compressed transfer size is the useful network budget. For example, run
gzip -9 -c dist/assets/runtime-*.js | wc -c after the build.
Set a CI budget only after recording a stable baseline. Fail on unexpected
growth, but allow an intentional increase when it removes more application code
or improves runtime behavior. Bundle size alone is not a performance result;
also measure startup and the interaction that motivated this work.
Failure path
Server-rendered and remotely loaded templates are not visible to the bundler.
Inventory their directives explicitly and compile representative responses in
tests. If templates or plugins can introduce unknown AngularTS features, ship
the standard runtime for that application instead of discovering missing
directives in production.
Do not create several nearly identical runtime entries. Share one composition
per application architecture and add a feature deliberately when a tested
template needs it. A few saved bytes are not worth inconsistent behavior across
pages.
Apply it now
Build the current application and record its compressed runtime size. List the
directives, filters, services, and optional modules used by production HTML.
Create one explicit runtime entry, remove one unused feature group, and compare
both size and startup measurements before removing anything else.
Verify
Compile every shipped server fragment and route template with the production
runtime. Exercise bootstrap, forms, errors, lazy content, and teardown from the
minified artifact. Confirm omitted services fail in a test, source maps resolve
in monitoring, the server sends compressed hashed assets, and the CI size budget
detects an intentional temporary increase.
9.91 - Add behavior to server-rendered HTML
Enhance one part of a page without building an SPA
Problem
The server already renders a useful page. One part needs client-side behavior,
but the page should still work when JavaScript fails or loads slowly.
Recipe
Keep navigation, forms, and initial content in HTML. Put AngularTS on the
smallest element that needs it.
<main><h1>Search products</h1><formaction="/products"method="get"ng-app="catalog"ng-controller="SearchController as search"><labelfor="query">Search</label><inputid="query"name="q"ng-model="search.query"/><spanaria-live="polite"ng-bind="search.message()"></span><buttontype="submit">Search</button></form></main>
constapp=angular.createModule('catalog',[]);app.controller('SearchController',functionSearchController(){this.query='';this.message=()=>{constcount=this.query.trim().length;returncount===0?'Enter a search term':count+' characters';};});
The form remains a normal GET request. AngularTS adds feedback, but does not
own navigation or the initial result page.
Why this works
The server keeps the model, URLs, validation, authorization, and HTML together.
The browser handles only immediate feedback.
This gives users useful content before AngularTS starts and leaves a working
fallback if the script does not run. It also avoids creating a second model that
must stay synchronized with the server.
If the browser must take over
Keep router content server-rendered while the server can still own the model.
Move the model into the browser only when a complex feature needs independent
client ownership, such as substantial offline work or a long-lived editing
workspace.
At that point the browser owns model consistency and compilation. Typed
programmatic views are an optional way to type-check model-to-view bindings;
HTML templates remain supported. REST services, explicit cache ownership, and a
browser security policy become primary at the same point. Before that threshold,
prefer normal HTTP forms and server-rendered fragments.
Make that choice per module. A login form can stay as server-rendered HTML while
a separate dashboard ng-app owns a browser model. The dashboard can choose a
typed programmatic view without requiring the rest of the application to adopt
the same view style.
Verify
Load the page with JavaScript disabled.
Submit the form and confirm the server returns results.
Enable JavaScript and confirm the live message appears.
Confirm the same URL and server response are used in both cases.
Avoid
Do not fetch the initial page through AngularTS only to reproduce HTML the
server can already send. Use a client-owned route only when the interaction
truly needs long-lived browser state. See Keep the application on the
server.
9.92 - Make a component view reactive
Fix state changes that do not update a typed component view
Problem
A component changes its controller state, but a value in its component view
stays frozen at the initial value.
Recipe
Pass a reader function for values that can change.
Use () => controller.count, not controller.count, for the changing child.
Why this works
controller.count reads the value while the view is being created. The view
receives one number and has nothing to read later.
() => controller.count gives the runtime a reader. AngularTS tracks the
properties read by that function and updates the affected DOM when they change.
Inspect the DOM and confirm the component is not rebuilt for each click.
Avoid
Do not wrap constant strings or numbers in functions. Readers are for values
that can change. Keep static children static.
9.93 - Keep repeated rows stable
Preserve row DOM when refreshed data represents the same records
Problem
Refreshing a collection replaces every object. ng-repeat then rebuilds rows,
which loses focus, selection, local element state, and animation continuity.
Recipe
Reconcile incoming records with the existing objects before replacing the
collection.
<ul><ling-repeat="user in users"><inputng-model="user.name"/></li></ul>
Why this works
ng-repeat tracks objects by identity. Reusing the object for the same id
lets AngularTS reuse that row. New records get new rows, removed records lose
their rows, and existing records keep their DOM.
The server ID decides which record is the same. The repeat directive still uses
its built-in identity behavior.
Verify
Focus an input in one row.
Refresh the collection with new objects carrying the same IDs.
Confirm focus stays in that row.
Add and remove records and confirm only those rows change.
Avoid
Do not reconcile records that have no stable identity. Do not reuse an object
when the server ID changed. For small primitive arrays where row state does not
matter, replacing the collection is simpler.
9.94 - Stop hidden UI from doing work
Choose between removing UI and only hiding it
Problem
A closed panel still runs watchers, observers, timers, or network work because
CSS hides it without removing it.
Use ng-show only when the DOM and its state must stay alive:
<video-playerng-show="playerOpen"></video-player>
Why this works
ng-if removes the subtree and destroys its child scope. Reopening it creates a
fresh subtree.
ng-show changes visibility. The subtree, scope, form state, media state, and
watchers stay alive.
Choose based on what should happen while the UI is closed, not on which
directive is shorter.
Verify
Open and close the panel repeatedly.
Confirm ng-if runs component cleanup when the panel closes.
Confirm hidden panels no longer issue requests or handle browser events.
If state must survive, confirm the ng-show version preserves it.
Avoid
Do not use ng-if for a control that opens and closes constantly when
rebuilding it is expensive. Do not use ng-show for a large inactive subtree
that keeps doing work.
9.95 - Ignore stale request results
Keep older responses from replacing newer search results
Problem
A user searches for cat, then quickly searches for caterpillar. The second
request finishes first. The slower cat response arrives later and replaces the
correct results.
Recipe
Give each request a sequence number. Apply a response only when it belongs to
the newest request.
Network responses can arrive in any order. The sequence number records which
request currently owns the screen. Older requests may finish, but they cannot
change results, errors, or loading state.
This check is still needed when the transport supports cancellation. A request
can finish just before cancellation reaches it.
Verify
Delay short-query responses in the browser network tools.
Search for a short term, then immediately search for a longer term.
Confirm only the longer term changes the screen.
Repeat with a failed older request and confirm it does not replace the latest
state.
Avoid
Do not use one shared loading boolean without checking which request finished.
An older request can otherwise hide the loading indicator for a newer one.
9.96 - Clean up browser resources
Stop listeners, observers, and timers when a view is removed
Problem
A component adds a browser listener or timer. After navigation or ng-if
removes the component, the callback still runs and keeps old state in memory.
Recipe
Create the resource inside the component view and register its cleanup at the
same time.
Setup and cleanup live next to each other, so a later edit is less likely to add
one without the other. AngularTS runs the registered disposer when it removes
the component view.
Verify
Open and remove the component several times.
Confirm only one timer or listener runs while it is visible.
Confirm no callback runs after removal.
Use the browser memory tools to confirm removed controllers can be collected.
Avoid
Do not store component timers in a global list. Do not wait for a later route to
clean up resources created by the current component.
9.97 - Find the slow part of an interaction
Measure whether delay comes from JavaScript, layout, rendering, or the network
Problem
An interaction feels slow, but changing watchers or rendering code without a
trace only moves work around.
Recipe
Record one interaction and optimize the longest measured task.
Reproduce the problem with realistic data and a production build.
Open the browser Performance panel.
Start recording immediately before the interaction.
Stop after the screen reaches its final state.
Find the longest main-thread task or network wait.
Expand that task until you can name the function, layout, paint, or request
responsible.
Change one cause and record the same interaction again.
Add marks when the trace does not show where application work starts:
Read the result in the browser Performance panel or with:
performance.getEntriesByName('results:update');
Why this works
A slow click can come from unrelated causes: request latency, JavaScript, style
calculation, layout, paint, image decoding, or too much DOM. Each cause needs a
different fix.
Measuring through the next animation frame includes the browser work needed to
show the change, not only the assignment.
Verify
Keep the before and after traces. Compare the same data, browser, build mode,
and interaction. Accept the change only when the measured delay improves without
moving the cost to another step.
Avoid
Do not use development timings as release numbers. Do not remove reactivity,
accessibility, or cleanup code because it looks expensive without a trace that
shows it is the cause.
9.98 - Render every request state
Show loading, success, empty, and error states without contradictory UI
Problem
A request-driven screen shows stale content during loading, treats an empty
result as an error, or leaves the loading indicator visible after failure.
<sectionaria-live="polite"><png-if="orders.status === 'loading'">Loading orders...</p><buttonng-if="orders.status === 'error'"ng-click="orders.load()"> Try again
</button><png-if="orders.status === 'empty'">No orders yet.</p><ulng-if="orders.status === 'ready'"><ling-repeat="order in orders.orders"ng-bind="order.number"></li></ul></section>
Why this works
One status value prevents impossible combinations such as loading and error
appearing together. Empty data is a successful response, so it gets its own
state instead of sharing the error path.
Keeping the last successful data is also valid. If you do that, show a separate
refresh indicator and make it clear that the visible data is old.
Verify
Test all four responses:
A delayed success shows loading, then results.
An empty success shows the empty message.
A failure shows retry and no loading indicator.
Retry can move from error through loading to ready or empty.
Avoid
Do not derive loading from orders.length === 0; empty results are not still
loading. Do not clear an error only after the next request succeeds; clear it
when retry starts.
10 - Routing
Build applications with multiple screens, browser URLs, nested layouts, data loading, and guarded transitions.
Routing connects a browser URL to a screen in your application. Begin with the
routing overview, then learn states,
URLs, transitions, and route maps in that order.
10.1 - State-based routing in AngularTS applications
Learn how AngularTS implements state-based routing with states, views, resolves, transitions, and URL matching configured through module declarations.
AngularTS ships a full-featured router ported from UI-Router. Unlike simple
URL-based routers, it models your application as a state machine: each
screen or workflow step is a named state, and navigation means transitioning
from one state to another. URLs are a way to enter a state, not the primary
concept.
Router naming, inference, and public-type placement follow the
shared API ergonomics contract.
Throughout the router documentation, a router state is one named navigation
node. A route tree is the module-owned hierarchy made from router states.
Machine and workflow states are execution modes and are not router states.
Key concepts
The router is built from five interlocking primitives:
States — named nodes in the application state tree. Each state can have a
URL, template, component, and resolved data.
Views — the rendered output of a state, inserted into a ng-view element
in the DOM.
Resolves — asynchronous data-fetching functions that run before a state is
entered.
Transitions — the lifecycle of moving between states with hooks you can
intercept.
URL matching — an optional layer that maps browser URLs onto states and
keeps them in sync.
Register and deregister state declarations at runtime
For ordinary app authoring, $state is usually sufficient. $transitions and
$stateRegistry are classified as advanced runtime APIs for policy wiring,
late-bound navigation policies, and runtime diagnostics.
Setting up the router
Register your states
Call module.router(declaration) with one
RouterModuleDeclaration
or a readonly forest of declarations. Every route must have a unique name. A
single typed forest keeps registration and route-name inference in one source
of truth.
router() has no separate public input or provider type. Its generic
TDeclaration is inferred from the literal above; it is not a type users must
name. The router is part of the built-in runtime, so the module does not need an
ng.router dependency.
Add ng-view to your layout
Place ng-view where you want the active state’s template to render. An unnamed
ng-view receives the default view.
<!DOCTYPE html><htmlng-app="app"><body><nav><ang-state="'home'"data-state-active="active">Home</a><ang-state="'contacts'"data-state-active>Contacts</a></nav><!-- Active state template renders here --><divng-view></div></body></html>
Navigate between states
Use $state.go() in controllers or services to perform programmatic navigation.
ng-view is a viewport directive that renders the template and controller for
the currently active state. When a transition completes, the view is swapped in.
Multiple named views can coexist:
<divng-view></div><!-- Named views --><divng-view="header"></div><divng-view="content"></div><divng-view="sidebar"></div>
A state targets named views through its views property:
The ng-view directive emits $viewContentLoading before the DOM is rendered
and $viewContentLoaded after. It also supports autoscroll and onload
attributes.
Router-wide scroll and focus behavior belongs in typed module config. This keeps
accessibility and scroll restoration consistent across the whole route tree
without repeating settings on every route:
Use scroll: true or scroll: "top" to reset to the top after every successful
navigation, scroll: "preserve" to leave the current position alone, or
scroll: { selector: "#content" } to scroll a specific element into view. Use
focus: true for the default focus target ([autofocus],
[data-router-focus], main, then h1), a selector string, or
{ selector, preventScroll } for explicit focus behavior.
When $router.viewTransitions is not disabled and the browser exposes
document.startViewTransition(), connected routed views commit inside the View
Transitions API so CSS can animate the outgoing and incoming route content as
one navigation. Detached, ignored, redirected, loading-resume, or explicitly
disabled transitions keep the normal synchronous view commit path.
ng-state is the router link primitive. Its value is always an AngularTS
expression: quote a static route name and use an identifier or property path
for a dynamic route name. A bare identifier is never treated as an implicit
route-name string, and ng-state does not use interpolation.
Fetch data asynchronously before a state is entered, with eager and lazy
policies.
10.2 - States
Define state hierarchies, resolve data, register states, and navigate with $state.
A state represents a place in an AngularTS application: a page, a nested layout,
a modal, or a step in a workflow. States are declared as plain objects and
registered through module.router() before bootstrap or $stateRegistry at
runtime.
Route resolves are authored through the resolve property on
StateDeclaration. The object and array helper containers are internal typing
details and are intentionally not part of the public ng namespace.
Register States
Register most states as one module-owned route tree or forest through
module.router().
Use $state.go() for normal application navigation. It supports absolute
states, parent-relative states, sibling-relative states, params, and transition
options.
$state.go() returns a TransitionPromise. When navigation starts immediately,
the active Transition is available as .transition; lazy route loading may
defer creation of that transition until after the promise has been returned.
In this example, leaving edit prompts for unsaved changes and can redirect to
discard when declined. list uses canExit with a transition param to skip
the transition in a confirmation flow.
Retry and Fallback
Use retry to control how the router re-runs transiently failing work before
the transition continues, and use fallbackTo to route to a recovery state when
the final attempt fails.
transient retries its resolve up to two times (attempt is 0-based in the
context), then enters the target only if it eventually resolves. stable shows
fallback policy using a static and object target form.
When policy-driven boundaries are declarative, transitions do not require manual
$transitions.onBefore/onStart/onError handlers for common retry/fallback
behavior.
Use $router config when most routes should share the same transition policy:
Route-level policy.transition values override these router-wide defaults for
that route subtree. Set loading: false or retry: false on a route when that
route should opt out of the default behavior.
Route-level loading/error decisions can be expressed in policy.transition too.
Use error to select the boundary for a recoverable transition failure.
angular.createModule('editorDemo',[]).router({name:'profile',url:'/profile',template:'<profile/>',policy:{transition:{loading:true,errorBoundary:'error',},},}).router({name:'error',url:'/error',template:'<p>Unable to load this state.</p>',});
loading is an executable decision point that currently accepts a boolean, a
state target, or an injectable policy.
Loading and error boundaries are UX-level transition concerns. They are
evaluated after the inherited security decision is made, so
authentication/authorization still happens in $security and route policy
decides what loading/error flow the app shows on the router side.
Retention
Use policy.retention for route branches that should deactivate while inactive
instead of losing DOM, scope, and component state on every navigation.
In this branch, workspace.tabA, workspace.tabB, and workspace.tabC inherit
the same retention policy. Leaving a tab deactivates its route tree. Returning
to the same retained key reactivates it with the previous scope and DOM state.
Because max is 2, entering a third distinct retained tab evicts one cached
entry and destroys its retained scope.
Use $router.retention when the entire application or module should share a
default retention policy:
Route-level policy.retention overrides the router-wide default for that route
subtree. A route can opt out by declaring policy.retention.mode = 'destroy'.
Retention keys default to state name plus route/query params. Use a string key
to share a retained instance across params, or an injectable key policy when the
cache identity needs app-specific route context.
pause: "schedulers" tells retained route scopes to pause scheduler-bound work
while inactive and resume it on reactivation. App-owned model schedulers remain
app-context owned; retention applies to route/root-owned work.
Reload the current state or an ancestor subtree through go():
Understand the AngularTS transition lifecycle and use hooks like onBefore, onStart, onSuccess, and onError for auth guards, analytics, and redirects.
Every navigation in AngularTS is a
Transition—a structured object
that describes moving from one state (or set of states) to another. Transitions
carry the from-state, the to-state, all parameter values, the tree of
entering/exiting/retained states, and the resolve context. You hook into their
lifecycle to implement auth guards, loading indicators, analytics, scroll
resets, and more.
What is a transition
When you call $state.go('contacts.detail', { contactId: 42 }), the router
creates a
Transition[ instance. Internally it computes a ](../../../typedoc/classes/Transition.html)TreeChanges
object with five paths:
Path
Description
from
All currently active nodes, from root to the current leaf state
to
All nodes that will be active after the transition
exiting
Nodes that are active now but will not be active after (in reverse order, deepest first)
retained
Nodes that are active both before and after (unchanged)
entering
Nodes that are not active now but will be active after (parent first)
The
Transition[ exposes these paths through ](../../../typedoc/classes/Transition.html)trans.exiting(), trans.retained(), and trans.entering(), each returning an array of StateDeclaration objects. Use trans.treeChanges(pathname)for the rawPathNode[]
arrays.
Each transition has a numeric id and a promise that resolves to the
StateDeclaration for the
to-state on success, or rejects with an error object on failure.
Transition lifecycle
The router runs hooks in a fixed sequence of phases:
onCreate (synchronous)
Fires during transition construction before the transition is returned. Used
internally for view config setup and global state updates. Not available for
application hooks.
onBefore (synchronous)
Fires before any state is exited or entered. This is the right place for
synchronous guards that should prevent the transition from even starting.
onStart (async)
Fires as the transition starts running—after onBefore is settled but before
any states are exited. Eager resolves are fetched here.
onExit (async, deepest first)
Fires for each state being exited, starting with the deepest and moving toward
the root.
onRetain (async)
Fires for each state that is being retained (neither entered nor exited).
onEnter (async, shallowest first)
Fires for each state being entered, starting with the parent and moving toward
the leaf. Lazy resolves for entering states are fetched before each state’s
onEnter.
onFinish (async)
Fires after all onEnter hooks are done. This is the last chance to cancel or
redirect before the transition is committed.
onSuccess / onError (synchronous, after commit)
Fires after the transition is fully committed. onSuccess hooks run when the
promise resolves; onError hooks run when it rejects. Return values are ignored
at this stage.
Registering hooks
All hook registration methods are on the $transitions service (injected as
$transitions or typed as
ng.TransitionsService). Each
method returns a deregistration function.
$transitions.onBefore(matchCriteria: HookMatchCriteria,callback:(transition: Transition)=>HookResult,options?: HookRegOptions):()=>void// deregister function
Hook match criteria
HookMatchCriteria is an
object with optional keys to, from, exiting, retained, and entering.
Each value is:
A state name string or glob ('contacts.**')
A function(state, transition) => boolean
true to match any state (the default when the key is omitted)
$transitions.onStart({},callback);// Matches transitions going to any child of 'admin'
$transitions.onBefore({to:'admin.**'},callback);// Matches transitions where a specific state is being exited
$transitions.onExit({exiting:'contacts.detail'},callback);// Matches using a function predicate
$transitions.onStart({to:function(state){returnstate.policy?.navigation?.authenticated===true;},},callback,);
Hook registration options
priority:10,// higher priority runs first (default: 0)
bind:myObject,// `this` inside callback
invokeLimit:1// auto-deregister after N invocations
});
Hook return values (HookResult)
The return value of a hook controls the transition:
Transition waits for the promise; cancels if it resolves to false
Promise<TargetState>
Transition waits for the promise; redirects when it resolves
Rejected Promise
Transition fails with the rejection reason
Note:onSuccess and onError hooks run after the transition is
committed. Their return values are ignored—you cannot cancel or redirect from
these hooks.
Redirecting from a hook
Return a TargetState created with
$state.target():
// Always redirect 'home' to 'home.dashboard' as a default substate
return$state.target('home.dashboard');});
The router internally creates a new transition to the redirect target and chains
the promises. If the original transition was triggered by a URL change, the
redirect uses location: 'replace' so the original URL is removed from history.
Cancelling a transition
Return false from any hook that runs before onSuccess:
if(appIsLocked){returnfalse;// abort
}});
A cancelled transition is rejected with an internal cancellation error type. The
browser URL is reset to the previous location by the built-in updateUrl hook.
Common hook patterns
Authentication guard
This pattern intercepts any transition to a state with a navigation policy and
redirects unauthenticated users to the login page.
.run(['$transitions','$state','AuthService',function($transitions,$state,AuthService){$transitions.onBefore({to:function(state){returnstate.policy&&state.policy.navigation;}},function(transition){if(!AuthService.isAuthenticated()){// Redirect to login, passing the intended destination
return$state.target('login',{redirectTo:transition.to().name});}});}]);
varAuthService=transition.injector().get('AuthService');if(!AuthService.isAuthenticated()){// Return a promise; transition waits for it to settle
returnAuthService.authenticate().catch(function(){return$state.target('guest');});}});
Transition outcomes
$state.go() is an asynchronous navigation boundary, so it uses normal Promise
control flow rather than returning an { ok, status } object. Branch by
awaiting the
TransitionPromise:
fulfillment means navigation completed or was ignored, while rejection carries
the router failure. When present, the attached
Transition[ records ](../../../typedoc/classes/Transition.html)successanderror()
as evidence after settlement.
A transition can finish in one of four states:
Result
Description
Success
All hooks passed, the to-state is now active. transition.success === true.
Error
A hook threw, returned a rejected promise, or a resolve failed. transition.success === false, transition.error() contains the reason.
Ignored
The transition targets the same state with the same parameters that are currently active. Treated as successful navigation.
Superseded
A newer transition started before this one finished. The older transition is abandoned; the newer one continues.
Redirected transitions are not an error from the caller’s perspective:
$state.go() transparently chains to the redirect target’s promise.
The router does not expose a persistence snapshot. Route declarations describe
navigation, and retention policy manages live views; neither is a durable
machine or workflow snapshot.
Accessing the transition object
The active transition is stored on $router.transition. Inside any hook, the
Transition instance is passed as
the first argument.
transition.redirectedFrom() returns the previous transition in a redirect
chain. transition.originalTransition() walks the entire chain back to the
first transition.
Per-transition hooks
Hooks can also be registered on a specific
Transition instance rather than
globally on $transitions. These hooks only affect that single transition:
trans.onSuccess({},function(){console.log('This specific transition succeeded.');});
Warning: Per-transition hooks must be registered before the transition has
finished running. Register them synchronously in the same call stack as the
navigation, or in an onStart global hook.
Dynamic resolvables in hooks
An onBefore hook can add additional resolve data to the current transition
using transition.addResolvable(). The added resolvable is available to hooks
and views that run after it:
Within a phase (e.g., all onStart hooks), hooks are invoked in descending
priority order. The default priority is 0. Built-in hooks like URL update and
view activation use priorities in the range 9000–10000, so application hooks
with the default priority run before them.
For onExit, hooks are invoked deepest-first (children before parents). For
onEnter, hooks are invoked shallowest-first (parents before children). This
ordering is fixed regardless of priority.
10.4 - Route maps
Use route maps to make $state navigation, route params, resolves, and ng-state examples auditable by TypeScript.
Route maps are optional TypeScript contracts for applications that want route
names, params, and resolves checked at author time. They do not change the
runtime router; they describe the route contract that the runtime already
enforces.
RouterModuleDeclaration
is the only public route-registration shape. router() accepts either one
declaration or a readonly array of declarations. TDeclaration in its generated
signature captures the exact argument literal for inference; it is not another
public type. There is no router<TRouteMap>(...) assertion overload and no
public state provider. Put an explicit route map on
createModule<TRouteMap>(...) when inference is not the desired boundary.
Use StateService<TRoutes> at
injection boundaries where code calls $state.go(...) or $state.href(...).
RoutesOf infers dot-composed child
names, absolute child names, explicit params keys, built-in literal URL
parameter value types, custom $router.paramTypes value types, and object-style
resolve return values.
Pass the same custom param type map used by $router.paramTypes as the second
generic argument. decode return types are used first; if a type has no
decode, an is(value): value is T predicate is used as the value type.
Large modules can pin the route contract directly on the module. This keeps
later route trees and lazy namespaces route-name aware without exposing provider
APIs.
When the tree is declared inline, router(...) infers a
ng.RouterModule<ng.RoutesOf<typeof tree>>
return type automatically. Typed router modules check the router(...) tree
itself, keep route names checked across chained router(...) calls and
lazyState(...) prefixes, and narrow object-style params and resolve
declarations to the route contract. Array-style resolves remain available when a
route needs explicit resolve metadata such as token, deps, or eager.
If a route map is supplied with angular.createModule<Routes>(...), that explicit
module contract is preserved after router(...). This lets a feature module
declare one route tree while the route map also includes lazy boundaries or
states registered later in the same module.
Use that explicit module contract when states must be registered individually:
The route map is declared once on createModule(). Each router() call is then
checked against it. Prefer a single inferred tree or forest when the states are
available together, because that avoids repeating route names in a handwritten
map.
Typed lazy prefixes accept known route names, parent route prefixes, and the
explicit ".**" lazy namespace form. Unknown lazy namespaces are rejected when
the module has a route map.
Typed router trees accept known route names and parent prefixes. This lets a
module declare an abstract or shell parent such as admin while still rejecting
unknown concrete children such as admin.audit when they are not in the module
route map.
Use
ng.Transition<TRoutes, { to: ToName; from: FromName }>
when code receives an injected $transition$. params() and params('to')
read the destination route params; params('from') can be narrowed when the
source route is known.
The same route map should be the source of truth for resolves and routed
component inputs. At runtime, routed components already receive resolve values
by matching binding names. The route map makes that contract visible to
TypeScript without asking application code to touch internal view records.
The example keeps route resolves and component inputs in one named contract so
admin.users consumers can evolve in lockstep. This stays explicit and does not
require internal router records.
Lazy route map composition
Lazy route declarations are composed per module rather than centralized in one
monolithic map.
These types are public authoring helpers only. They are not aliases for built
state records, transition internals, retained-view cache entries, or view
service records. If a route map needs to include a lazy-loaded module, compose
the public route-map types at the TypeScript boundary rather than importing
router internals.
ng-state always evaluates its value as an expression. Use a quoted literal
route name when the target is static; this keeps examples and editor tooling
aligned with the same route-map contract. An unquoted identifier is a scope
lookup, not an implicit route-name string. Do not wrap ng-state expressions in
interpolation braces.
When a template route is static, mirror that literal route name in the component
or docs-example TypeScript boundary. This gives TypeScript the same route-map
contract without adding a template compiler.
Use an identifier or property-path expression only when the route name is
genuinely dynamic. The AngularTS VS Code tooling checks simple TypeScript
literal unions such as $ctrl.route: 'admin.profile' | 'admin.users'. A broad
string cannot prove a finite target set, so use
StateService<TRoutes> in the code
that computes that value when it needs author-time validation.
Current boundary:
$state.go(...), $state.href(...), params, and resolve maps have executable
public type coverage.
ng.RouterModule<TRoutes> lets
angular.createModule<TRoutes>(...) and router(...) carry route-name checking
into module declarations.
ng.RoutesOf<typeof tree> derives a
route map from a literal router(...) tree without exposing internal state,
transition, or view records.
Built-in literal URL parameter types and custom $router.paramTypes value
types are inferred from route URL patterns.
Editor tooling completes and validates literal ng-state route names and
ng-state-params keys in inline templates and templateUrl files. It also
validates simple dynamic route-name unions when TypeScript can prove them.
state-links.html demonstrates the
quoted literal ng-state template shape.
10.5 - URL matching and configuration in AngularTS router
Configure URL parameters, typed path and query params, hash vs HTML5 mode, base href, and custom parameter types in the AngularTS state-based router.
The AngularTS router matches the browser’s URL against registered state
declarations and activates the best matching state. URL matching is an optional
layer on top of the state machine—states can be navigated to programmatically
without any URL involvement—but most applications use URLs to make deep-linking
and browser history work correctly.
How URL matching works
When the browser URL changes (or on initial load), the router syncs registered
URL rules in priority order and finds the best match using a weighted scoring
system. The winning rule’s handler calls $state.go() with the matched state
and extracted parameter values.
URL rules are created automatically for every state that has a url property.
Rules with the same sort order are ranked by weight so the most specific match
wins.
URL parameters
Parameters are declared in the state’s url string. The router uses a
UrlMatcher compiled from this pattern to test URLs and extract values.
Give a parameter a default value in the params block and set squash: true to
make it optional. When the URL is visited without the parameter, the default
value is used. When navigating with the default value, the parameter is omitted
from the URL:
name:'userList',url:'/users/:page',params:{page:{value:'1',// default value
squash:true// remove from URL when value equals default
}},component:'UserList'});// /users/ → { page: '1' } (squashed)
// /users/3 → { page: '3' }
Built-in parameter types
The router ships the following built-in parameter types. Specify the type inline
in the URL pattern or in the params block:
string
Default for path parameters. Encodes and decodes as a plain string. Slashes
within the value are encoded as ~2F in AngularTS’s patched path type to avoid
ambiguity in Angular 1’s $location.
Encodes Date objects as YYYY-MM-DD strings. Parses the string back to a
Date on the way in.
url:'/events?{startDate:date}&{endDate:date}';// { startDate: new Date('2024-01-15') }
// → /events?startDate=2024-01-15
hash
The internal parameter type used for the # (hash/anchor) portion of the URL.
Has inherit: false so the hash is not carried forward to child state
transitions.
URL configuration with $router
$router config controls global URL matching behavior.
Case sensitivity
URL matching is case-sensitive by default. Allow case-insensitive matching:
Control the global default for how parameters with default values appear in
URLs:
angular.getModule('app').config({$router:{// 'false' (default): include the default value in the URL
// 'true': omit default value from URL
// '~': replace default value with '~' in the URL
defaultSquash:true,},});
Custom parameter types
Register a custom ParamType before
using it in state URL patterns. The type must implement encode, decode,
is, equals, and optionally pattern:
Note: Register custom types in $router.paramTypesbefore any state
that uses them.
Hash mode vs HTML5 mode
AngularTS inherits Angular 1’s $location modes. Configure the mode with
module.config({ $location: ... }):
Hash mode (default)
URLs use a hash fragment: http://example.com/app/#/contacts/42. No server
configuration is needed; the hash segment is never sent to the server.
angular.getModule('app').config({$location:{html5Mode:false,hashPrefix:'!',// optional: use #!/ instead of #/
},});
Generated hrefs are prefixed with # (plus the hash prefix) in hash mode.
HTML5 mode (pushState)
URLs use real paths: http://example.com/contacts/42. The server must return
the app’s index.html for all routes.
angular.getModule('app').config({$location:{html5Mode:{enabled:true,requireBase:true,// <base href="..."> must be present in <head>
},},});
In HTML5 mode, generated hrefs use the base path rather than a hash.
Base href
In HTML5 mode, the base href is read from the <base> tag in the document
<head>:
<head><basehref="/myapp/"/></head>
If no <base> tag is present, the runtime falls back to
window.location.pathname. The base href is used when constructing absolute
URLs via $state.href(..., { absolute: true }) and when pushing new history
entries.
Add motion to elements as AngularTS inserts, removes, moves, shows, or hides them.
Start with the animation overview.
Use CSS animations for ordinary transitions and JavaScript animations only when
the browser animation lifecycle must be controlled directly.
11.1 - CSS-based animations with the AngularTS CSS driver
Write CSS transitions and keyframe animations for AngularTS structural directives using ng-enter, ng-leave, ng-move, and stagger classes.
The CSS animation driver is the default mechanism for animating AngularTS
structural directives. When a directive calls $animate.enter(),
$animate.leave(), $animate.move(), $animate.addClass(), or
$animate.removeClass(), the CSS driver reads the element’s computed
transitionDuration and animationDuration after applying the appropriate
preparation classes. If it detects a non-zero duration, it manages the full
lifecycle: blocking premature transitions, applying active classes after a
requestAnimationFrame, listening for transitionend and animationend
events, and cleaning up all temporary classes when the animation finishes.
How the CSS driver works
The driver goes through a fixed sequence for every animation:
Preparation classes are added. For a structural event, this means
.ng-enter, .ng-leave, or .ng-move. For class-based events, it means
.foo-add or .foo-remove.
Transitions are temporarily blocked by applying a large negative
transitionDelay inline style. This prevents the browser from computing a
transition before the active class is applied.
The driver waits for the next quiet requestAnimationFrame. This forces
the browser to flush style calculations, so getComputedStyle() returns
accurate timing values.
Active classes are added (e.g., .ng-enter-active). The negative delay
is removed at the same time, causing any defined CSS transition to start.
The driver listens for transitionend / animationend. A fallback
setTimeout fires at delay + 1.5 * duration to handle browsers that may
not fire the event reliably.
Cleanup. All preparation and active classes are removed, inline
transition and animation style overrides are reverted, and the
AnimateRunner is resolved.
Note: The CSS driver only runs an animation if getComputedStyle()
reports a non-zero transitionDuration or animationDuration after the
preparation classes are applied. If no duration is detected, the driver skips
the animation and immediately resolves the runner.
CSS transitions
The simplest way to animate an element is to define CSS transitions on the
preparation classes. The transition must be set on the preparation class
(.ng-enter) and the final state on the active class (.ng-enter-active).
.my-element.ng-enter{transition:opacity0.3sease,transform0.3sease;opacity:0;transform:translateY(-10px);}/* The ending state — applied one rAF later to trigger the transition */.my-element.ng-enter-active{opacity:1;transform:translateY(0);}/* Leave animation — reverse the enter */.my-element.ng-leave{transition:opacity0.3sease,transform0.3sease;opacity:1;transform:translateY(0);}.my-element.ng-leave-active{opacity:0;transform:translateY(10px);}
CSS keyframe animations
You can also use @keyframes animations. Define the keyframe animation on the
preparation class using the animation shorthand property:
With keyframe animations the active class (.ng-enter-active) is not required
to drive the animation, but you can still use it to override or extend
properties applied during the active phase.
Class-based transitions (ng-show / ng-hide)
When ng-show or ng-hide adds or removes the ng-hide class,
$animate.addClass() and $animate.removeClass() are called internally. The
CSS driver applies .ng-hide-add / .ng-hide-add-active for the hide
transition and .ng-hide-remove / .ng-hide-remove-active for the show
transition.
.my-panel.ng-hide-add{transition:opacity0.25sease;opacity:1;}.my-panel.ng-hide-add-active{opacity:0;}/* Fade in when revealed */.my-panel.ng-hide-remove{transition:opacity0.25sease;opacity:0;}.my-panel.ng-hide-remove-active{opacity:1;}
The same pattern applies to any class you add or remove via
$animate.addClass() or $animate.removeClass(). If you call
$animate.addClass(el, 'highlighted'), the driver applies .highlighted-add
and .highlighted-add-active.
Example: animating ng-repeat list items
ng-repeat calls $animate.enter() when a new item is added,
$animate.leave() when one is removed, and $animate.move() when the list is
reordered.
<ul><liclass="list-item"ng-repeat="item in items">{{ item.name }}</li></ul>
When multiple elements animate simultaneously under the same parent — common
with ng-repeat — the CSS driver detects a stagger class automatically. Define
.ng-enter-stagger with transition-delay and zero transition-duration:
transition:opacity0.3sease,transform0.3sease;opacity:0;transform:translateY(8px);}.list-item.ng-enter-active{opacity:1;transform:translateY(0);}/* Stagger: each later item is delayed by 80ms */.list-item.ng-enter-stagger{transition-delay:0.08s;transition-duration:0s;}
The driver reads the transitionDelay from .ng-enter-stagger and multiplies
it by the item’s index within the batch. The first item starts immediately; each
additional item is offset by the stagger delay.
CSS animations can be expensive when they trigger layout or paint on every
frame. Follow these guidelines to keep animations smooth:
Use transform and opacity
transform and opacity are the only properties that browsers can animate
entirely on the GPU compositor thread without triggering layout or paint. Prefer
these over width, height, top, left, margin, or padding.
Avoid animating box-model properties
Properties like height, padding, and margin force layout recalculation on
every frame. Use transform: scaleY() or max-height tricks as alternatives
where possible.
Use will-change sparingly
Adding will-change: transform hints to the browser that the element will be
animated, creating a new compositor layer. Use it only on elements you know will
animate — overuse increases memory consumption.
Limit simultaneous animations
Use targeted animation class names and avoid attaching animation hooks to broad
selectors. Animating large numbers of DOM nodes simultaneously causes frame
drops on low-powered devices.
11.2 - JavaScript-based animations with the AngularTS JS driver
Register JavaScript animation handlers with module.animation(), implement enter/leave/move hooks with done callbacks, and use the Web Animations API.
The JavaScript animation driver lets you write fully imperative animations in
code. Instead of defining CSS classes, you register a factory function against a
CSS class selector. When an element carrying that class goes through a
structural or class-based animation event, the driver looks up and invokes the
matching handler. This makes the JS driver the right choice when you need
precise timing control, want to integrate an animation library such as the Web
Animations API, or need to coordinate multiple elements that CSS transitions
cannot express.
How the JS driver works
During each animation request, $animate checks registered JavaScript animation
factories before falling back to CSS/custom-property animation handling. It
inspects the element’s class list against factories registered through
module.animation(). If a matching factory is found, it retrieves the singleton
handler object from the injector and packages the appropriate lifecycle hook as
a runnable operation.
JavaScript animation handlers can define two phases for most events:
before* — e.g., beforeAddClass, beforeRemoveClass. Runs
synchronously before the DOM change.
after* (or the event name itself for enter, move) — runs after the
DOM change.
For leave, the hooks are leave (before removal) and afterLeave (after
removal). For enter and move, only the after-phase hook is called (named
enter / move respectively), because the before-phase does not make sense for
elements being inserted.
Note: The JS driver and CSS driver are not mutually exclusive per element,
but the animation queue calls invokeFirstDriver() which returns on the first
driver that produces a handler. If a JS animation is registered for an
element, the CSS driver is skipped for that animation event.
Registering a JS animation
Use module.animation() to associate a factory with a CSS class selector. The
selector must begin with .. The factory is an injectable function that returns
an object containing lifecycle hook methods.
angular.createModule('app',[]).animation('.fade',function(){return{enter:function(element,done){// animate element in, then call done()
element.style.opacity='0';requestAnimationFrame(function(){element.style.transition='opacity 0.3s ease';element.style.opacity='1';element.addEventListener('transitionend',functiononEnd(){element.removeEventListener('transitionend',onEnd);done();});});},leave:function(element,done){element.style.transition='opacity 0.3s ease';element.style.opacity='0';element.addEventListener('transitionend',functiononEnd(){element.removeEventListener('transitionend',onEnd);done();});},};});
The same method can be chained with other module declarations:
The object returned by your factory can implement any combination of these
hooks. Unimplemented hooks are simply skipped.
Hook
Signature
When it fires
enter
(element, done)
After element is inserted into the DOM.
leave
(element, done)
Before element is removed from the DOM.
afterLeave
(element, done)
After element is removed from the DOM.
move
(element, done)
After element is moved to a new position.
beforeAddClass
(element, className, done)
Before the class is added to the element.
addClass
(element, className, done)
After the class has been added.
beforeRemoveClass
(element, className, done)
Before the class is removed.
removeClass
(element, className, done)
After the class has been removed.
beforeSetClass
(element, addedClasses, removedClasses, done)
Before the atomic add/remove.
setClass
(element, addedClasses, removedClasses, done)
After the atomic add/remove.
animate
(element, from, to, done)
For $animate.animate() calls.
The done callback
Every hook receives a done function as its last argument. You must call
done() when the animation finishes — whether that is after a transitionend
event, a setTimeout, a Web Animations API finish event, or any other
mechanism. If done() is never called, the AnimateRunner associated with this
animation will never resolve, blocking any chained work.
// If you return early (e.g., no animation needed), still call done()
if(window.matchMedia('(prefers-reduced-motion: reduce)').matches){done();return;}// Otherwise call done() when the animation actually finishes
runMyAnimation(element).then(done);}
You may also return a cleanup function from the hook. This function is invoked
if the animation is cancelled before it completes:
constanimation=element.animate([{opacity:0},{opacity:1}],{duration:300,easing:'ease'});animation.onfinish=done;// Return a cancel handler
returnfunction(wasCancelled){if(wasCancelled){animation.cancel();}};}
Example: Web Animations API
The Web Animations API provides a clean way to drive animations imperatively. It
returns a promise-like Animation object with onfinish and oncancel
callbacks.
Apply the animation by adding the .pop-in class to any element managed by a
structural directive:
<divclass="panel"ng-if="panelOpen"><p>This panel animates in and out with the Web Animations API.</p></div>
Example: class-based JS animation
The addClass and removeClass hooks fire when $animate.addClass() or
$animate.removeClass() is called — including internally by ng-show and
ng-hide. The className argument is the space-separated string of classes
being added or removed.
When you need to animate related elements in sequence — for example, a leaving
element that exits while an entering element waits — you can share state via
closure:
Animation factory functions participate in dependency injection. List
dependencies in the array notation or use $inject:
return{enter:function(element,done){$log.debug('enter animation started');// e.g., fetch data to drive animation parameters
$http.get('/api/animation-config').then(function(response){constduration=response.data.duration||300;element.animate([{opacity:0},{opacity:1}],{duration,fill:'forwards'}).onfinish=done;});},};}]);
Warning: Long-running asynchronous work inside animation hooks (such as
HTTP requests) can make your UI feel sluggish. Prefer pre-fetching animation
configuration and caching it rather than fetching it inside a hook on every
animation.
Combining JS and CSS animations
If you want the JS driver to apply classes and then let CSS transitions handle
the visual animation, you can manipulate classes directly in the hook and detect
completion via a transitionend listener. This approach gives you the control
of JS hooks with the performance of CSS compositing:
Tip: When coordinating JS and CSS this way, always use transform and
opacity for the animated properties so the browser can run the transition on
the compositor thread without layout recalculation.
11.3 - AngularTS animations: CSS and JavaScript drivers overview
Overview of the AngularTS animation system — the $animate service, CSS and JS drivers, structural directive hooks, and class-based transitions.
AngularTS ships a first-class animation system built into the core framework.
When you use structural directives such as ng-if, ng-repeat, ng-show,
ng-hide, ng-include, or ng-view, the framework automatically coordinates
with the $animate service to apply CSS class hooks and invoke registered
JavaScript animation handlers at the exact moment DOM changes occur — before and
after insertion, removal, or class toggling.
How animations are triggered
Animations in AngularTS are not triggered by calling an animation API directly.
Instead, they are a side-effect of normal directive activity. When ng-if
removes an element, it calls $animate.leave() internally. When ng-repeat
inserts a new item, it calls $animate.enter(). This means you never need to
change your directive usage — you only need to provide CSS rules or a registered
JavaScript animation for the matching class names.
The $animate service sits between directives and the animation drivers. It
queues animation work, deduplicates competing animations on the same element,
and dispatches to whichever driver is configured. All animation requests are
deferred until after the current digest cycle completes, so DOM changes and
class mutations are always applied in a stable, predictable order.
The two animation drivers
AngularTS provides two built-in drivers that are consulted in sequence. The JS
driver is checked first; if it returns a handler, the CSS driver is skipped for
that element. If no JS handler matches, the CSS driver reads the element’s
computed styles to detect transitions or keyframe animations.
Reads transitionDuration, animationDuration, and related computed style
properties after applying preparation classes. Handles staggering, delays, and
both CSS transitions and @keyframes animations with no JavaScript required.
Invokes factory functions registered via module.animation(). Each factory
returns an object with lifecycle hooks (enter, leave, move, addClass,
removeClass, setClass, animate) that receive a done callback. Suitable
for Web Animations API, GSAP, or any imperative animation library.
CSS class hooks
The CSS driver applies a pair of classes for every animation event. The first
class (the preparation class) is added immediately; the second class (the active
class) is added one requestAnimationFrame later so the browser can compute a
transition between the two states. Both classes are removed when the animation
completes.
Event
Preparation class
Active class
enter
.ng-enter
.ng-enter-active
leave
.ng-leave
.ng-leave-active
move
.ng-move
.ng-move-active
addClass foo
.foo-add
.foo-add-active
removeClass foo
.foo-remove
.foo-remove-active
During structural animations (enter, leave, move), the element also
receives .ng-animate for the full duration of the animation.
For staggered animations — such as list items entering one after another —
define a stagger delay class:
The CSS driver detects .ng-enter-stagger automatically when more than one
element is being animated simultaneously under the same parent.
The $animate service API
The $animate service is injectable and provides the full animation API. Every
method returns an AnimateRunner that you can use to react to completion or
cancel the animation early.
Insert element into the DOM and trigger an enter animation.
leave(element, options?)
Trigger a leave animation, then remove the element.
move(element, parent, after?, options?)
Move element within the DOM and trigger a move animation.
addClass(element, className, options?)
Add one or more CSS classes with an animation.
removeClass(element, className, options?)
Remove one or more CSS classes with an animation.
setClass(element, add, remove, options?)
Add and remove classes as a single atomic animation.
animate(element, from, to, className?, options?)
Animate from one set of inline styles to another.
cancel(runner)
Cancel a running animation; the end state is still applied.
Observing animation completion
Every $animate method returns an animation handle. Use done() when code
needs to run after an animation settles, or pass onStart, onDone, and
onCancel callbacks in native animation options when the callback belongs to
one request.
The registered animation is matched against the element’s class list. If the
element has .fade-animation when an enter event fires, the enter hook is
invoked.
12 - Reference
Find exact directive, service, filter, value, router, animation, and generated TypeScript API contracts.
Reference pages answer exact API questions. If you are learning a concept, start
with Get Started or a task
guide.
An ng-* HTML name is usually a directive. A name injected into a function is a
service or value token. A capitalized TypeScript name is a generated type.
Read a TypeScript signature
value?: string means the property is optional and, when supplied, must be a
string. string | null means either value is accepted. () => void means a
function takes no arguments and returns no useful value. Array<T> means an
array whose items have type T.
Check lifecycle and ownership
Before using an API, determine when it is created, when callbacks run, and who
releases listeners or resources. Component, directive, scope, route, and
application lifetimes are different.
Prefer the public contract
Use exported package members, documented injection tokens, and generated TypeDoc
pages. Names marked internal, generated DOM details, and implementation files
are not application contracts.
Return to a guide
Once you find the exact signature, use the linked task or concept page to see
the API in an application workflow.
12.3 - Error and symptom catalog
Diagnose common AngularTS bootstrap, injection, rendering, routing, and integration failures.
Application does not bootstrap
Check that the bootstrapped module name matches the registered module, the root
exists, and AngularTS is loaded once. Do not bootstrap on ng-if, ng-include,
or ng-view.
Unknown provider or injection token
Confirm the provider is registered before use, its module is in the dependency
graph, and the token spelling is correct. Regenerate language bindings after
changing the public namespace.
State changed but the view did not
Read the value through a reactive binding. In typed views, use a reader such as
() => controller.value. In keyed collections, call the item reader.
A collection row is recreated
Use stable domain keys. Do not key mutable records by array position when
insertion, removal, or sorting is possible.
Navigation succeeds but no view appears
Confirm an ng-view outlet exists, the state names a registered component or
template, and parent/child names match the declaration.
Sanitization or CSP blocks content
Review the boundary instead of disabling security. Identify the source, expected
sink, and smallest safe trust policy.
Generated integration files are stale
Run the integration’s generation command and aggregate integration validation.
Never hand-edit generated bindings, externs, or parity artifacts.
Browser capability is missing
Detect optional APIs before constructing a service. Select a fallback and show
an actionable unsupported state when none exists.
Understand how runtime modules map to learning guides and API reference.
Every shipped directive module, service module, filter module, public injection
token, and built-in configuration key must map to documentation.
A mapping is either:
Dedicated: the page documents that feature directly.
Canonical: a broader page owns the feature as part of one workflow.
The mapping is stored in docs/public-feature-docs.json. CI discovers the
source surface and fails when a module or public token changes without updated
documentation ownership.
Utility, worker, WebAssembly, and transport helpers are documented at their
user-facing boundary rather than by internal source file. Generated TypeScript
API pages remain the authoritative signature reference; guides explain when and
why to use those signatures.
13 - Directive reference
Look up the purpose, syntax, parameters, and examples for each directive shipped by AngularTS.
A directive is an HTML attribute or element that adds framework behavior. If you
do not yet know which directive you need, begin with the directive
guides. This section is an alphabetical
lookup reference.
13.1 - ng-repeat
Complete reference for ng-repeat: iterating arrays and objects, special scope variables, filters, animation, and performance guidance.
ng-repeat instantiates a template for each item in a collection, creating a
child scope for every element. It is the primary directive for rendering lists
and grids in AngularTS.
Basic syntax
<ling-repeat="item in items">{{ item.name }}</li><!-- Iterate an object (key-value pairs) --><trng-repeat="(key, value) in user"><td>{{ key }}</td><td>{{ value }}</td></tr>
ng-repeat
Type:expression
Required: yes
One of these forms:
item in collection — iterate array or array-like
(key, value) in object — iterate object properties
item in collection | filter:fn — with inline filter
Special scope variables
Every ng-repeat child scope exposes these read-only properties:
$index
Type:number
Zero-based position of the item in the collection.
Preserve item object identity when updating large collections to maximize DOM
reuse.
Avoid complex expressions in ng-repeat — compute derived values in the
controller.
Use limitTo to paginate rather than rendering thousands of items.
One-time bind static content: {{ ::item.name }} avoids watches on items that
never change.
13.2 - ARIA directives
$aria adds accessibility-supporting defaults for the ARIA attributes that
AngularTS manages automatically. It preserves authored ARIA, role, and
tabindex attributes and should be treated as framework support for accessible
applications, not as a complete WCAG conformance guarantee.
Use module.config({ $aria: ... }) when you need to adjust global ARIA support.
ng.AriaConfig checks the standardized aria-current token used by router
links. ARIA attributes authored in templates remain native HTML rather than
framework-specific TypeScript contracts.
Use ng-aria-disable on an element when framework-authored ARIA behavior should
not apply locally.
Set diagnostics: true in development or tests when you want $aria to warn
about common authored mistakes on framework-managed interactive elements:
positive tabindex, missing accessible names, missing ARIA reference targets,
and interactive elements hidden with aria-hidden="true".
ng-state route links consume $aria defaults. Non-anchor and non-button route
links receive role="link", tabindex="0", and Enter/Space keyboard activation
when enabled. Links using data-state-active or data-state-exact receive
framework-managed aria-current while current unless an authored aria-current
value already exists.
Executable examples
<!doctype html><html><head><scripttype="module">import"../../utils/source-bootstrap.js";import"./ng-click-non-native.js";</script><style>.interactive-card{border:1pxsolid#cbd5e1;border-radius:6px;padding:12px;width:fit-content;cursor:pointer;}</style></head><bodyng-app="ariaClickNonNativeDemo"><sectionng-controller="AriaClickNonNativeCtrl as vm"><h2>Non-native ng-click accessibility</h2><p>Count: {{ vm.count }}</p><divclass="interactive-card"ng-click="vm.count = vm.count + 1"> Click or press Enter/Space
</div><p> AngularTS applies framework defaults (`role="button"` and
`tabindex="0"`), so this non-button element is keyboard accessible without
manual ARIA wiring.
</p></section></body></html>
Non-native ng-click accessibility
Count: {{ vm.count }}
Click or press Enter/Space
AngularTS applies framework defaults (`role="button"` and
`tabindex="0"`), so this non-button element is keyboard accessible without
manual ARIA wiring.
<!doctype html><html><head><scripttype="module">import"../../utils/source-bootstrap.js";import"./form-validation.js";</script><style>.field{margin-bottom:12px;}.error{color:#b91c1c;}input.ng-invalid.ng-touched{border-color:#b91c1c;}</style></head><bodyng-app="ariaFormValidationDemo"><sectionng-controller="AriaFormValidationCtrl as vm"><formname="signupForm"novalidate><divclass="field"><label>Email</label><inputname="email"type="email"ng-model="vm.user.email"requiredaria-describedby="email-help"/><pid="email-help">Enter a valid email to continue.</p></div><divclass="field"><label>Password</label><inputname="password"type="password"ng-model="vm.user.password"requiredminlength="8"/></div><divclass="field"><buttontype="button"ng-disabled="signupForm.invalid">Create account</button></div></form><pclass="error"> Form invalid: {{ signupForm.invalid }}
<br/> Email invalid: {{ signupForm.email.invalid }}
<br/> Password invalid: {{ signupForm.password.invalid }}
</p><p> Notice how `aria-required` and `aria-invalid` are injected by `$aria` when
form inputs are in their default states.
</p></section></body></html>
Notice how `aria-required` and `aria-invalid` are injected by `$aria` when
form inputs are in their default states.
<!doctype html><html><head><scripttype="module">import"../../utils/source-bootstrap.js";import"./ng-messages-live.js";</script><style>.field{margin-bottom:12px;}.error-box{margin-top:8px;padding:8px10px;border:1pxsolid#fecaca;background:#fff1f2;border-radius:4px;max-width:360px;}</style></head><bodyng-app="ariaMessagesDemo"><sectionng-controller="AriaMessagesCtrl as vm"><formname="contactForm"novalidate><divclass="field"><label>Email</label><inputname="email"type="email"ng-model="vm.user.email"requiredaria-describedby="email-hint"/><pid="email-hint">Use an address we can message.</p><divclass="error-box"ng-messages="contactForm.email.error"ng-show="contactForm.email.dirty || contactForm.submitted"><png-message="required">Email is required.</p><png-message="email">Please provide a valid email.</p></div></div><buttontype="submit">Submit</button></form></section></body></html>
13.3 - Form directives
API reference for form, ng-form, validation, and ng-messages directives.
AngularTS enhances native HTML forms with a form controller that tracks validity
and user interaction state. The form and ng-form directives attach this
controller to the scope, and the built-in validators populate it automatically.
Geolocation
The experimental HTML <geolocation> element can write its latest result to
ng-model:
<geolocationng-model="position"ng-el="locationPicker"watch><buttontype="button">Use my location</button></geolocation>
Each native location event replaces position with a plain, serializable
GeolocationValue. A
failed request clears the model and adds geolocation to the model controller’s
error object. Use ng-el when you also need native details such as
locationPicker.error or locationPicker.permissionStatus.
The binding is view-to-model only. Changing position does not request browser
permission and cannot write to the element’s read-only native position.
Attributes such as watch and autolocate keep their browser-defined behavior.
Browsers without <geolocation> support display its fallback content.
This element is experimental and has limited browser support. Check the
browser compatibility table
before relying on it.
form / ng-form
A plain <form> element with a name attribute creates a FormController on
the current scope. ng-form does the same but can be used on non-form elements
or for nested forms.
Display validation error messages tied to a field’s error object.
<divng-messages="userForm.email.error"><divng-message="required">Email is required.</div><divng-message="email">Please enter a valid email address.</div><divng-message-default>This field has an error.</div></div>
ng-messages
Type:expression
Required: yes
Expression pointing to an error object (typically formName.fieldName.error).
ng-messages-multiple
Type:none
By default, only the first matching message is shown. Add this attribute to show
all matching messages simultaneously.
ng-message
Type:string
Required: yes
The validator key to match against the error object. Shown when error[key]
is truthy.
ng-message-exp
Type:expression
Same as ng-message but evaluates an expression rather than a string literal.
ng-message-default
Type:none
Shown when no other ng-message matches. Useful as a generic fallback.
Complete form example
<formname="signupForm"><div><label>Email</label><inputname="email"type="email"ng-model="user.email"required/><divng-messages="signupForm.email.error"ng-show="signupForm.email.dirty"><divng-message="required">Required.</div><divng-message="email">Invalid email format.</div></div></div><div><label>Password</label><inputname="password"type="password"ng-model="user.password"requiredminlength="8"/><divng-messages="signupForm.password.error"ng-show="signupForm.password.dirty"><divng-message="required">Required.</div><divng-message="minlength">Must be at least 8 characters.</div></div></div><buttontype="submit"ng-disabled="signupForm.invalid">Sign Up</button></form>
13.4 - ng-app
Bootstrap AngularTS application
Description
Use this directive to auto-bootstrap an AngularTS application. The ng-app
directive designates the root element of the application and is typically placed
near the root element of the page - e.g. on the <body> or <html> tags.
13.5 - ng-bind
Sync or hydrate textContent of element with an expression
Description
The ng-bind attribute places the text content of the specified HTML element
with the value of a given expression, and to update the text content when the
value of that expression changes.
Typically, you don’t use ng-bind directly, but instead you use the double
curly markup like {{ expression }} which is similar but less verbose.
It is preferable to use ng-bind instead of {{ expression }} if a template is
momentarily displayed by the browser in its raw state before it is compiled.
Since ng-bind is an element attribute, it makes the bindings invisible to the
user while the page is loading.
An alternative solution to this problem would be using the ng-cloak directive.
ng-bind can be modified with a data-lazy data attribute (or shorthand lazy
attribute), which will delay update of element content until model is changed.
This is useful for rendering server-generated content, while keeping the UI
dynamic. In other frameworks, this technieque is known as
hydration.
Description: Expression to evaluate and modify
textContent
property.
Example:
<divng-bind="name"></div>
Directive modifiers
data-lazy
Type: N/A
Description: Apply expression once the bound model changes.
Example:
<divng-bind="name"data-lazy></div><!-- or --><divng-bind="name"lazy></div>
Demo
<sectionng-app><!-- Eager bind --><label>Enter name: <inputtype="text"ng-model="name"/></label><br/> Hello <spanng-bind="name">I am never displayed</span>!
<!-- Lazy bind with short-hand `lazy` --><buttonng-click="name1 = name">Sync</button><spanng-bind="name1"lazy>I am server content</span>!
</section>
Hello I am never displayed!
I am server content!
13.6 - ng-bind-html
Bind an HTML string to an element after the application has sanitized it.
Sets an element’s innerHTML to the evaluated string.
Expression that evaluates to an HTML string. null and undefined clear the
element.
Warning: This directive does not sanitize the string. Never bind raw user
input or untrusted server content. Sanitize it before assigning it to scope.
13.7 - ng-bind-template
Bind interpolated template text to an element.
Binds a template string that may contain multiple {{ }} interpolation
expressions. Useful on attributes or when you need mixed text and expressions in
the element’s text content.
<spanng-bind-template="{{ remaining }} of {{ total }} tasks remain"></span>
ng-bind-template
Type:string
Required: yes
A string literal containing one or more {{ expression }} interpolations. The
entire string is interpolated and set as textContent.
13.8 - ng-blur
Handler for blur event
Description
The ng-blur directive allows you to specify custom behavior when an element
loses focus.
Description: Expression to evaluate upon
blur
event.
FocusEvent
object is available as $event.
Example:
<divng-blur="$ctrl.handleBlur($event)"></div>
Demo
<sectionng-app><inputtype="text"ng-blur="count++"ng-init="count = 0"placeholder="Click or tab away from me"/> Lost focus {{ count }} times
</section>
Lost focus {{ count }} times
13.9 - ng-channel
Subscribe a template to a messaging service topic
Description
Updates element’s content by subscribing to events published on a named channel
using $eventBus.
If the element does not contain any child elements or templates, the
directive will replace the element’s inner HTML with the published value.
If the element does contain a template and the published value is an
object, the directive will merge the object’s key-value pairs into the
current scope, allowing Angular expressions like to be evaluated and
rendered.
The directive automatically unsubscribes from the event channel when the scope
is destroyed.
Parameters
ng-channel
Type:string
Description: The name of the channel to subscribe to using $eventBus.
Dynamically bind one or more CSS classes using expressions.
Description
The ng-class directive allows dynamically setting CSS classes on an HTML
element by binding to an expression. The directive supports the following
expression types:
String — space-delimited class names.
Object — keys as class names and values as booleans. Truthy values add the
class.
Array — containing strings and/or objects as described above.
For complex view state, bind a precomputed class map instead of embedding a long
object expression in the template:
Important: Avoid using interpolation ({{ ... }}) in the value of the
class attribute together with ng-class. See
template and interpolation guidance
for details.
Animations
If data-animate attribute is present, the following animations will be applied
to the element:
The ng-cloak directive is used to prevent the HTML template from being briefly
displayed by the browser in its raw (uncompiled) form while your application is
loading. Use this directive to avoid the undesirable flicker effect caused by
the HTML template display.
The directive can be applied to the <body> element, but the preferred usage is
to apply multiple ng-cloak directives to small portions of the page to permit
progressive rendering of the browser view.
ng-cloak works in cooperation with the following CSS rule:
The ng-el directive allows you to store a reference to a DOM element in the
current scope, making it accessible elsewhere in your template or from your
controller. The reference is automatically removed if the element is removed
from the DOM.
Use ng-el for the common case where you want the native element itself:
<sectionng-controller="BoardController as $ctrl"><canvasng-el="$ctrl.boardEl"></canvas></section>
functionBoardController(){this.boardEl=null;}
Use a bare name for simple scope shorthand:
<canvasng-el="boardEl"></canvas>
Use a full assignable expression for controller-as or object-path refs:
For simple DOM element references, ng-el is the clearer API.
Parameters
ng-el
Type:string (optional)
Description: Name of the key under which the element will be stored in
scope, or an assignable expression such as $ctrl.boardEl. Bare names are
treated as shorthand keys. If omitted, the element’s id attribute will be
used.
<sectionng-app><divng-el="$chesireCat"></div><divng-elid="response"></div><buttonclass="btn"ng-el="$button"ng-click="
$chesireCat.innerHTML = '🐱';
response.innerHTML='That depends a good deal on where you want to get to.';
$button.hidden = true"> Which way I ought to go?
</button></section>
13.17 - ng-focus
Handler for focus event
Description
The ng-focus directive allows you to specify custom behavior when an element
is focused.
Description: Expression to evaluate upon
focus
event.
FocusEvent
object is available as $event.
Example:
<divng-focus="$ctrl.greet($event)"></div>
Demo
<sectionng-app><inputtype="text"ng-focus="count++"ng-init="count = 0"placeholder="Click or tab into me"/> Focused {{ count }} times
</section>
Focused {{ count }} times
13.18 - ng-get
Initiates a GET request
Description
The ng-get directive allows you to fetch content via $http service from a
remote URL and insert, replace, or manipulate it into the DOM. For DOM
manipulation to work, the response must be HTML content. If the server endpoint
returns JSON, the response is available to on-success as $res. JSON never
mutates the current scope implicitly, and the swap strategy is ignored.
Additional options for request and response handling can be modified with
attributes provided below.
Parameters
ng-get
Type:string
Description: A URL to issue a GET request to
Example:
<divng-get="/example">get</div>
Modifiers
data-trigger
Type:string
Description: Specifies the DOM event for triggering a request (default is
click). For a complete list, see
UI Events. To
eagerly execute a request without user interaction, use the “load” event,
which is triggered syntheticaly on any element by the directive linking
function. This is in contract to the native
load,
which executes lazily only for window object and certain resource elements.
Description: Triggers a request whenever its value changes. This attribute
can be used with interpolation (e.g., {{ expression }}) to observe reactive
changes in the scope.
Example:
<divng-get="/example"latch="{{ latch }}"ng-mouseover="latch = !latch"> Get
</div>
Toggles the CSS display property of an element without removing it from the DOM. The element’s scope is always alive, regardless of visibility.
<divng-hide="isLoading">Content here</div>
ng-show
Type:expression
Required: yes
When truthy, the element is visible (removes the ng-hide CSS class). When falsy, the element is hidden.
ng-hide
Type:expression
Required: yes
Inverse of ng-show. When truthy, the element is hidden.
Animation hooks:.ng-hide-add / .ng-hide-add-active when hiding, .ng-hide-remove / .ng-hide-remove-active when showing.
ng-if vs ng-show/hide
Aspect
ng-if
ng-show / ng-hide
DOM presence
Removed when false
Always in DOM
Scope lifetime
Destroyed when false
Always alive
Child watchers
Removed when false
Always active
Initial render cost
Only when true
Always rendered
Animation events
ng-enter / ng-leave
ng-hide-add / ng-hide-remove
Use ng-if when the content is expensive to render or when you want to prevent hidden content from making network requests. Use ng-show/hide when toggling frequently and you need instant re-display without re-initiali
13.20 - ng-if
Conditionally add or remove an element from the DOM.
Adds or removes an element from the DOM based on the truthiness of an
expression. When the element is removed, its scope and all child scopes are
destroyed. When re-added, a fresh scope is created.
When truthy, the element is rendered. When falsy, the element and its scope are
destroyed and removed from the DOM.
Animation hooks:.ng-enter / .ng-enter-active when added, .ng-leave /
.ng-leave-active when removed.
Note:ng-if creates a child scope. If you bind ng-model inside an
ng-if, write to an object property (obj.field) rather than a primitive to
avoid scope shadowing issues.
13.21 - ng-include
Include a template
Description
13.22 - ng-inject
Inject dependencies into scope.
Description
The ng-inject directive injects registered injectables (services, factories,
etc.) into the current scope for direct access within templates or expressions.
This allows access to application state without having to create intermediary
controllers.
When applied to an element, the directive reads a semicolon-separated list of
injectables’ names from the ng-inject attribute and attempts to retrieve them
from the $injector. Each resolved injectable is attached to the current scope
under its corresponding name.
Parameters
ng-inject
Type:string
Restrict:A
Description: A semicolon-separated list of injectable’ names to attach to current scope.
Description: Expression to evaluate upon
keydown
event.
KeyboardEvent
object is available as $event.
Example:
<divng-keydown="$ctrl.greet($event)"></div>
Demo
<sectionng-app><inputtype="text"ng-keydown="count++"ng-init="count = 0"placeholder="Click here, then press down a key."/> Keydown {{ count }} times
</section>
Keydown {{ count }} times
13.24 - ng-keyup
Handler for keyup event
Description
The ng-keyup directive allows you to specify custom behavior when releasing
keys, regardless of whether they produce a character value.
Description: Expression to evaluate upon
keyup
event.
KeyboardEvent
object is available as $event.
Example:
<divng-keyup="$ctrl.greet($event)"></div>
Demo
<sectionng-app><inputtype="text"ng-keyup="count++"ng-init="count = 0"placeholder="Click here, then press down a key."/> Keyup {{ count }} times
</section>
Keyup {{ count }} times
13.25 - ng-load
Handler for load event
Description
The ng-load directive allows you to specify custom behavior for elements that
trigger
load
event.
Note: there is no guarantee that the browser will bind ng-load directive
before loading its resource. Demo below is using a large image to showcase
itself.
<sectionng-app><imgng-load="res = 'Large image loaded'"width="150px"src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Centroamerica_prehispanica_siglo_XVI.svg/1920px-Centroamerica_prehispanica_siglo_XVI.svg.png"/> {{ res }}
</section>
{{ res }}
13.26 - ng-model
Two-way data binding for form controls.
Binds the value of <input>, <textarea>, <select>, and custom controls to a scope expression.
<textareang-model="user.bio"></textarea><selectng-model="user.role"ng-options="r for r in roles"></select><inputtype="checkbox"ng-model="user.active">
ng-model
Type:expression
Required: yes
An assignable AngularTS expression. When the user changes the input, the expression is assigned the new value. When the scope value changes, the input is updated.
Configure update timing and validation behavior for ng-model.
Configures how and when ng-model reads and writes its value.
updateOn
Type:string
Space-separated list of DOM events that trigger a model update. Default is
'default' (uses the element’s natural update event). Common values: 'blur',
'change', 'keyup', 'default'.
<!-- Only update on blur --><inputng-model="name"ng-model-options="{ updateOn: 'blur' }"/><!-- Update on both blur and custom event --><inputng-model="name"ng-model-options="{ updateOn: 'blur myEvent' }"/>
debounce
Type:number | object
Milliseconds to wait after the last change before updating the model. Can be a
number (applies to all events) or an object mapping event names to delays.
<!-- 300ms debounce for all events --><inputng-model="search"ng-model-options="{ debounce: 300 }"/><!-- Different debounce per event --><inputng-model="search"ng-model-options="{ debounce: { default: 300, blur: 0 } }"/>
allowInvalid
Type:boolean
When true, the model is updated even when validators fail. Default is false
(model is set to undefined on invalid input).
`time
13.28 - ng-mousedown
Handler for mousedown event
Description
The ng-mousedown directive allows you to specify custom behavior when a mouse
is pressed over an element.
Description: Expression to evaluate upon
mouseup
event.
MouseEvent
object is available as $event.
Example:
<divng-mouseup="$ctrl.greet($event)"></div>
Demo
<sectionng-app><divng-init="count = 0"ng-mouseup="count++">Mouse Up</div> Mouse Up {{ count }} times
</section>
Mouse Up
Mouse Up {{ count }} times
13.35 - ng-non-bindable
Stops compilation for element
Description
The ng-non-bindable directive tells the framework not to compile or bind the
contents of the current DOM element, including directives on the element itself
that have a lower priority than ngNonBindable. This is useful if the element
contains what appears to be directives and bindings but which should be ignored.
This could be the case if you have a site that displays snippets of code, for
instance.
ng-non-bindable
Type: N/A
Description: Stops compilation process for element
The ng-pointer-capture directive captures a pointer stream that starts on the
element and releases it when the pointer ends or is cancelled. This is useful
for board, canvas, and game-style interfaces where pointermove and pointerup
should continue reaching the same element even when the pointer leaves its
bounds.
ng-pointer-capture does not implement dragging behavior. It only manages the
browser pointer capture lifecycle. Register native pointer listeners from an
application directive for drag behavior.
Parameters
ng-pointer-capture
Type: boolean attribute
Restrict:A
Element: ANY
Priority:1
Description: Calls setPointerCapture($event.pointerId) on pointerdown,
releases capture on pointerup and pointercancel, forgets browser-released
pointers on lostpointercapture, and releases active captures when the scope
is destroyed.
Example:
<divng-pointer-capturepointer-drag></div>
Demo
<style>.pointer-capture-demo{display:grid;gap:0.75rem;max-width:30rem;}.pointer-capture-demo__board{position:relative;aspect-ratio:16/9;overflow:hidden;border:1pxsolid#cbd5e1;background:linear-gradient(90deg,rgba(14,165,233,0.12)1px,transparent1px),linear-gradient(rgba(14,165,233,0.12)1px,transparent1px),#f8fafc;background-size:2rem2rem;touch-action:none;user-select:none;}.pointer-capture-demo__marker{position:absolute;left:0;top:0;width:1.5rem;height:1.5rem;border:2pxsolid#fff;border-radius:999px;background:#2563eb;box-shadow:00.5rem1remrgba(15,23,42,0.22);will-change:transform;}.pointer-capture-demo__status{margin:0;color:#475569;}</style><sectionng-app="pointerCaptureDemo"class="pointer-capture-demo"><divng-controller="PointerCaptureDemo as $ctrl"><divclass="pointer-capture-demo__board"ng-pointer-capturepointer-drag><divclass="pointer-capture-demo__marker"ng-style="$ctrl.markerStyle"></div></div><pclass="pointer-capture-demo__status">{{ $ctrl.status }}</p></div></section><script>window.angular.createModule('pointerCaptureDemo',[]).controller('PointerCaptureDemo',function(){this.dragging=false;this.status='Drag the marker across the board';this.markerStyle={transform:'translate(32px, 32px)',};this.positionFromEvent=function(event){constrect=event.currentTarget.getBoundingClientRect();constradius=12;constx=Math.max(radius,Math.min(event.clientX-rect.left,rect.width-radius),);consty=Math.max(radius,Math.min(event.clientY-rect.top,rect.height-radius),);this.markerStyle={transform:'translate('+(x-radius)+'px, '+(y-radius)+'px)',};};this.start=function(event){this.dragging=true;this.status='Pointer captured';this.positionFromEvent(event);};this.move=function(event){if(!this.dragging){return;}this.status='Dragging pointer '+event.pointerId;this.positionFromEvent(event);};this.end=function(event){if(this.dragging){this.positionFromEvent(event);}this.dragging=false;this.status='Pointer released';};this.cancel=function(){this.dragging=false;this.status='Pointer cancelled';};}).directive('pointerDrag',function(){return{link(scope,element){constcontroller=scope.$ctrl;constlisteners={pointerdown:(event)=>controller.start(event),pointermove:(event)=>controller.move(event),pointerup:(event)=>controller.end(event),pointercancel:()=>controller.cancel(),};for(const[name,listener]ofObject.entries(listeners)){element.addEventListener(name,listener);}scope.on('$destroy',()=>{for(const[name,listener]ofObject.entries(listeners)){element.removeEventListener(name,listener);}});},};});</script>
{{ $ctrl.status }}
13.37 - ng-post
Initiates a POST request
Description
The ng-post directive allows you to send data via $http service to a remote
URL and insert, replace, or manipulate the server’s response into the DOM. The
directive assumes a response will be HTML content. If the server endpoint
returns JSON, the response is available to on-success as $res and requires
explicit assignment. The swap strategy is ignored for JSON. Unlike its sister
ng-get directive, ng-post assumes it is attached to a form, input,
textarea, or select element, which act as the source of data for the request
payload:
In case of error, the directive displays a string error in place of the success
result. JSON errors are available to on-error as $res. The behavior can be
combined with other directivs to create complex form-handling strategies. Below
is a form that dissappears in case of success or adds error state in case of
validation errors.
Additional options for request and response handling can be modified with
attributes provided below.
Parameters
ng-post
Type:string
Description: A URL to issue a GET request to
Example:
<divng-post="/example">post</div>
Modifiers
data-enctype
Type:string
Description: Specifies the content type of form. Defaults to
application/json. To send regular URL-encoded data form, use
application/x-www-form-urlencoded.
Description: Specifies the DOM event for triggering a request (default is
click). For a complete list, see
UI Events. To
eagerly execute a request without user interaction, use the “load” event,
which is triggered syntheticaly on any element by the directive linking
function. This is in contract to the native
load,
which executes lazily only for window object and certain resource elements.
Description: Triggers a request whenever its value changes. This attribute
can be used with interpolation (e.g., {{ expression }}) to observe reactive
changes in the scope.
Example:
<divng-post="/example"latch="{{ latch }}"ng-mouseover="latch = !latch"> Get
</div>
Toggles the CSS display property of an element without removing it from the DOM. The element’s scope is always alive, regardless of visibility.
<divng-hide="isLoading">Content here</div>
ng-show
Type:expression
Required: yes
When truthy, the element is visible (removes the ng-hide CSS class). When falsy, the element is hidden.
ng-hide
Type:expression
Required: yes
Inverse of ng-show. When truthy, the element is hidden.
Animation hooks:.ng-hide-add / .ng-hide-add-active when hiding, .ng-hide-remove / .ng-hide-remove-active when showing.
ng-if vs ng-show/hide
Aspect
ng-if
ng-show / ng-hide
DOM presence
Removed when false
Always in DOM
Scope lifetime
Destroyed when false
Always alive
Child watchers
Removed when false
Always active
Initial render cost
Only when true
Always rendered
Animation events
ng-enter / ng-leave
ng-hide-add / ng-hide-remove
Use ng-if when the content is expensive to render or when you want to prevent hidden content from making network requests. Use ng-show/hide when toggling frequently and you need instant re-display without re-initiali
13.39 - ng-state
Bind state declaration data in templates.
Declarative state navigation where both the state name and params come from
scope expressions.
<ang-state="'orders.detail'"ng-state-params="{ id: order.id }"data-state-activedata-state-exact> Order {{order.id}}
</a>
Use data-state-active when the link should track whether its target state or a
child state is active. Add data-state-exact when only the exact target state
should count as current. AngularTS writes data-state-current="true" or
data-state-current="false" so styling can stay declarative.
ng-state also consumes $aria defaults. A non-native route link such as a
div receives role="link", tabindex="0", keyboard activation, and managed
aria-current when data-state-active or data-state-exact is present.
Authored role, tabindex, and aria-current values are preserved, and
ng-aria-disable opts an element out locally.
The ng-state value is always an expression. The quoted literal route name
('orders.detail') is static enough for TypeScript examples and documentation
checks to map it back to a known route. Use ng-state="link.state" when the
route name itself is dynamic. An unquoted ng-state="orders" reads the
orders scope property; it does not name the orders route. Interpolation is
not part of the ng-state contract.
Expression that evaluates to a state name string. Use a quoted string literal
when the template always targets one route and a direct scope expression when
the target is dynamic.
ng-state-params
Type:expression
Expression that evaluates to the params object for the state.
$scope.currentParams={userId:42};
13.40 - ng-switch
Switch between exclusive template blocks.
Conditionally renders one of several templates based on the value of an
expression. Similar to a JavaScript switch statement.
Animation hooks:.ng-enter / .ng-leave on matched/unmatched elements.
13.41 - ng-view
Render the active routed view.
Marks the element where the router renders the active state’s template. When the active state changes, the content inside ng-view is replaced with the new state’s template, compiled into a new scope.
<ng-view></ng-view><!-- As an attribute --><divng-view></div><!-- Named view (for multiple named views in one state) --><divng-view="sidebar"></div>
ng-view
Type:string
Optional view name. When omitted, this outlet renders the unnamed (default) view of the active state. When specified, renders the named view from the state’s views configuration.
States define their templates in $stateRegistry:
name:'home',url:'/home',template:'<h1>Home</h1>'});// Named views:
$stateRegistry.register({name:'dashboard',url:'/dashboard',views:{'':{template:'<p>Main content</p>'},'sidebar':{template:'<nav>Sidebar</nav>'}}});
13.42 - ng-window-* and ng-document-*
Handler for window and document events
Description
The ng-window-* and ng-document-* directives allow you to specify custom
behavior for events dispatched from the Window or Document object. The event
name is defined by including it in the placeholder of the directive name.
Example: ng-window-online binds to the window online event, and
ng-document-pointerup binds to the document pointerup event.
Listeners are removed automatically when the owning scope is destroyed.
<sectionng-app> In Chrome DevTools: Open <b>Network tab</b> → Select
<b>Throttling</b> dropdown -> Click <b>Offline</b> checkbox
<divng-window-online="online = true">Connected: {{ online }}</div><divng-window-offline="offline = true">Disconnected: {{ offline }}</div><buttonng-document-click="lastDocumentClick = $event.type"> Listen for next document click
</button><div>Document event: {{ lastDocumentClick || 'none' }}</div></section>
In Chrome DevTools: Open Network tab → Select
Throttling dropdown -> Click Offline checkbox
Connected: {{ online }}
Disconnected: {{ offline }}
Document event: {{ lastDocumentClick || 'none' }}
14 - Service reference
Look up injectable AngularTS services and their runtime responsibilities.
Service names beginning with $ are framework-provided injection tokens. Read
the service guides first if services or
dependency injection are new to you.
14.1 - $anchorScroll
Scroll to document anchors and coordinate hash-driven scrolling.
$anchorScroll scrolls to the element identified by a hash or, when omitted,
to the current value of
$location.getHash(),
according to the rules specified in the
HTML spec.
It also watches the URL hash and automatically scrolls to any matched anchor
whenever it is changed by $location.
When automatic scrolling is disabled, call $anchorScroll(...) directly after
coordinating routing, animation, or focus.
Vertical Offset
Use the yOffset property to specify a fixed or dynamic vertical scroll offset.
This is useful when the application has sticky headers.
Example
<style>.demo2{height:100px;overflow:auto;}.anchor{border:2pxdashedDarkOrchid;padding:10px10px400px10px;}.fixed-header{background-color:rgba(0,0,0,0.2);height:50px;}.fixed-header>a{display:inline-block;margin:5px15px;}</style><sectionid="demo2"class="demo2"><divng-controller="headerCtrl"class="fixed-header"><buttonclass="btn"ng-click="gotoAnchor(x)"ng-repeat="x in [1,2,3,4,5]"> Go to anchor {{x}}
</button></div><divid="anchor{{x}}"class="anchor"ng-repeat="x in [1,2,3,4,5]"> Anchor {{x}} of 5
</div></section>
window.angular.createModule('demo2',[]).run(['$anchorScroll',function($anchorScroll){$anchorScroll.yOffset=100;// always scroll by 50 extra pixels
},]).controller('headerCtrl',['$anchorScroll','$location','$scope',function($anchorScroll,$location,$scope){$scope.gotoAnchor=function(x){window.$locationTest=$location;constnewHash='anchor'+x;if($location.getHash()!==newHash){// Pass `newHash` to setHash() and
// $anchorScroll will automatically scroll to it
$location.setHash('anchor'+x);}else{// call $anchorScroll() explicitly,
// since getHash() still returns the same value
$anchorScroll();}};},]);window.angular.bootstrap(document.getElementById('demo2'),['demo2']);
$eventBus allows communication between an
Angular application instance and the outside context, which can be a different
module, a non-Angular application, a third-party party library, or even a WASM
application. Additionally, $eventBus can be used to communicate directly with
a template, using ng-channel directive.
$eventBus should not be used for communicating between Angular’s own
primitives.
For sharing application state: use custom Services and Factories that
encapsulate your business logic and manage your model.
For intercomponent communication: use $scope.on(), $scope.emit(),
and $scope.broadcast() methods.
The example below demonstrates communication between the global window context
and a controller. Note: Ensure topic clean-up after the $scope is
destroyed
Example
<sectionng-app="demo"><divng-controller="DemoCtrl as $ctrl"> Milliseconds elapsed since the epoch <b>{{ $ctrl.ms }}</b></div></section><!--We are using a regular onclick and `angular` global --><buttonclass="btn btn-dark"onclick="angular.eventBus.publish('demo', Date.now())"> Publish time
</button>
window.angular.createModule('demo',[]).controller('DemoCtrl',class{static$inject=['$eventBus','$scope'];constructor($eventBus,$scope){constunsubscribe=$eventBus.subscribe('demo',(val)=>{// `this.ms = val` will not work because `this` is not a proxy
// to trigger change detection, we access controller as a scope property
$scope.$ctrl.ms=val;});$scope.on('$destroy',unsubscribe);}},);
Demo
Milliseconds elapsed since the epoch {{ $ctrl.ms }}
$exceptionHandler is the central hook for exceptions that escape detached
AngularTS-managed work. This includes DOM and transport events, scheduled tasks,
subscriptions, expression evaluation, and lifecycle callbacks whose original
caller can no longer receive a thrown value or rejected promise.
The decision is: can the initiating caller still receive the failure? If yes,
throw synchronously or reject the returned promise. Use $exceptionHandler only
after that caller boundary no longer exists.
Synchronous API validation throws directly. Promise-returning operations reject
their returned promise. Expected transport, workflow, routing, and domain
failures remain in their typed operation channels.
By default, it rethrows exceptions that occur during AngularTS-managed
execution. This fail-fast behavior ensures errors are visible immediately in
development and in unit tests.
IMPORTANT: In AngularJS, $exceptionHandler only caught errors in
expressions and logged them to the console, using a type signature of
$exceptionHandler(exception, [cause]).
AngularTS treats $exceptionHandler as a single error sink and fails eagerly.
It preserves the thrown value unchanged. Applications should throw
Error
objects with a cause when reporting requires structured context.
Custom handlers should rethrow after reporting. AngularTS treats
$exceptionHandler as fail-fast, and swallowing errors can hide broken
application state.
The $http service is AngularTS’s built-in HTTP client. Use this page for the
behavioral model and common examples. For exact method signatures, parameters,
return types, and interfaces, use the generated TypeDoc reference:
Plain objects are serialized as JSON by the default request transform. File,
Blob, and FormData values are sent as-is so the browser can set the correct
transport headers.
Query parameters are serialized by $httpParamSerializer. Arrays produce
repeated keys, objects are JSON encoded, and null, undefined, and functions
are omitted.
Successful 2xx responses resolve with an HttpResponse<T>. Non-2xx responses,
timeouts, aborts, and network errors reject with the same response shape, so
error handlers can inspect status, statusText, headers, config, and
xhrStatus.
$http.get<User>('/api/users/99').catch((error)=>{if(error.xhrStatus==='timeout'){message='Request timed out.';}elseif(error.status===404){message='User not found.';}});
Defaults
Configure defaults before bootstrap with module.config({ $http: ... }), or
mutate runtime defaults through $http.defaults.
Compile interpolation text into reusable expression functions.
14.9 - $location
Normalize and update browser URLs across HTML5 and hashbang modes.
$location parses the browser URL into path, search, hash, and state pieces.
Changes made through $location update the browser address bar, and browser
navigation updates the service.
$locationChangeStart is broadcast on $rootScope before the URL changes. Call
event.preventDefault() from a listener to cancel the navigation.
$locationChangeSuccess is broadcast after the URL changes. In HTML5 mode,
listeners may also receive the new and old history state values when the browser
supports the History API.
Default implementation of
LogService safely writes the
message into the browser’s
console (if
present). The main purpose of this service is to simplify debugging and
troubleshooting, while allowing the developer to modify the defaults for live
environments.
debug controls whether $log.debug(...) writes to the console. The default is
false.
Beacon delivery
Selected log levels can also be delivered through the browser’s Beacon API.
Beacon delivery is disabled unless an endpoint is configured and defaults to the
error level when levels is omitted.
The default serializer sends an application/jsonBlob containing
timestamp, level, and args. Applications that need redaction or additional
metadata can register and select a serializer:
Beacon queueing has no response callback or delivery acknowledgement. AngularTS
does not retry or persist rejected entries. Failures produce a local warning by
default; set failure: "ignore" to suppress it.
Beacon delivery automatically participates in $security; there is no separate
logging security switch. AngularTS checks the endpoint and configured request
credentials before serializing an entry. A denied remote delivery never
suppresses the local log call, and repeated policy denials produce at most one
local warning.
Beacon requests cannot carry application-defined Authorization headers, so
Bearer and Basic credential policies deny Beacon delivery. Use a cookie endpoint
for authenticated Beacon logging. If the endpoint requires custom headers, use
$http or fetch() with keepalive: true instead. Redaction and payload
shaping remain the responsibility of the configured serializer.
You can also override any $log service method with module.decorator(). Below
is a simple example that overrides default $log.error to log errors to both
console and a backend endpoint.
If your application is already heavily reliant on default
console logging or
you are using a third-party library where logging cannot be overriden, you can
still take advantage of Angular’s services by modifying the globals at runtime.
Below is an example that overrides default console.error to logs errors to
both console and a backend endpoint.
Example*
angular.getModule('demo').run(["$http",($http)=>{/**
* Decorate console.error to log error messages to the server.
*/const$delegate=window.console.error;window.console.error=(...args)=>{try{consterrorMessage=args.map((arg)=>(arginstanceofError?arg.stack:JSON.stringify(arg))).join(' ');$delegate.apply(console,args);$http.post('/api/log/error',{message:errorMessage});}catch(e){console.warn(e);}};/**
* Detect errors thrown outside Angular and report them to the server.
*/window.addEventListener('error',(e)=>{window.console.error(e.error||e.message,e);});/**
* Optionally: Capture unhandled promise rejections
*/window.addEventListener('unhandledrejection',(e)=>{window.console.error(e.reason||e);});}]);
Combining both $log decoration and console.log overriding provides a robust
and flexible error reporting strategy that can adapt to your use-case.
* array notation and HTTP error handling omitted for brevity
14.11 - $machine
Reactive state machines with declarative transitions
$machine
$machine creates a small reactive state machine for UI and application flows.
It is more than a regular service or factory because the returned machine is
designed to be wrapped by AngularTS scope proxies: state and data update
templates naturally, and send() batches transition work when a scope proxy is
observing the machine.
Use ordinary services for IO, persistence, and shared domain logic. Use
$machine when the important part is the allowed flow between states.
Use $workflow when the flow also
needs command boundaries, diagnostics, snapshots, restore, retry, or repeat.
<sectionng-controller="TapGameCtrl as $ctrl"><p>{{$ctrl.game.state}}: {{$ctrl.game.data.score}} / 3</p><buttonng-if="$ctrl.game.matches('ready')"ng-click="$ctrl.game.send('start')"> Start
</button><buttonng-if="$ctrl.game.matches('playing')"ng-click="$ctrl.game.send('tap')"> Tap
</button><buttonng-if="$ctrl.game.matches('won')"ng-click="$ctrl.game.send('restart')"> Play again
</button></section>
state identifies the machine’s active state. State-tree transitions move to
the explicit to state, while update mutates reactive game data. A
to(context) resolver handles transitions whose target depends on runtime data.
Ownership Patterns
Create machines with $machine(config) and assign the returned object to the
owner that should keep the flow alive.
Controller-owned machines are enough for local UI flow data:
This is useful when defaults depend on environment, user session data, or
persisted configuration.
Named machines are regular DI services. The injector creates one machine
instance and reuses it for that injectable name.
Template API
The machine exposes a small template-friendly API:
<buttonng-disabled="!$ctrl.session.matches('setup')">Random</button><divng-show="$ctrl.session.matches('waiting')">Waiting for opponent...</div><span>{{ $ctrl.session.data.roomId }}</span>
Runtime API
session.state;// current state string
session.data;// reactive data object
session.matches('setup');// true when current state is setup
session.can('join',payload);// true when current state can run join
session.send('join',payload);// structured transition result
session.snapshot();// structured clone of { state, data }
session.restore(snapshot);
send(type, payload) returns a structured result with ok, status, type,
from, to, and an optional denial reason. ok is true when a transition
entry exists for the current state, its optional guard passes, and its update
hook runs when provided. Missing transitions and blocked guarded transitions
return ok: false with a specific status and do not throw. Use $workflow when
failures need diagnostics, retry, or recovery.
ok is the broad branch: successful results have status transitioned or
updated; failed results have a denial, invalid-event, or missing-transition
status. Machine results intentionally do not carry diagnostics. The status is
the decision, while workflows own durable failure evidence.
State-tree transition shape:
to names the next state or resolves it from readonly transition input
omit to for a same-state data update
update({ data, payload }) mutates reactive data
guard({ data, payload }) blocks the transition when it returns false
denied({ data, payload }) may record local denial state
Routing is explicit. Static to is the normal path. Use a synchronous,
side-effect-free to(context) resolver when the next state depends on runtime
data, such as a game move ending in win, draw, or continued play. AngularTS
resolves one target before guard and policy evaluation; hooks and update()
receive that same readonly target. Return values from update() are ignored.
can(type, payload) checks the transition table, target resolver, policy, and
any optional guard for the current state. It does not run update or denied.
Target resolvers and guards should be side-effect-free because templates may
call can() repeatedly.
Status values are transitioned, updated, missing-transition,
guard-denied, policy-denied, and invalid-event. When policy denies a
transition, the result includes the policy decision’s optional reason.
Denial paths are narrow. guard-denied only means the transition guard returned
false; transition-local denied() runs only for that guard-denial case.
Missing transitions and invalid event names do not run before, update,
after, denied, enter hooks, exit hooks, or global transition hooks.
policy-denied is reserved for framework policy gates. It does not run
denied, mutation hooks, state hooks, global transition hooks, or legacy
transition targets.
Transition Policy
Use policy when the application has a cross-cutting synchronous gate that
should apply to every transition in a machine:
Policy contexts include operation: "machine.transition", machineId, type,
from, to, event-narrowed payload, readonly data, an observational
machine view, and optional meta. The policy and guard machine view exposes
state, data, can(), matches(), and snapshot() but cannot dispatch or
restore live state. Policies must be synchronous so can() and send() stay
synchronous. Put async authorization, retries, persistence, and recovery in
$workflow or another service and then project the result back through a
machine event.
State-tree transitions with static to give policy the strongest pre-execution
context. Legacy callback transitions do not run their target just to discover a
future state, so policy sees to as the current state there.
Guarded Transitions
Use guard when a transition has a cheap synchronous condition that templates
or controllers should be able to ask about through can():
constsession=$machine({initial:'setup',data:{roomId:'',inviteCode:'',},states:{setup:{on:{join:{to:'waiting',guard({data,payload}){return!!data.inviteCode&&payload.roomId!=='';},update({data,payload}){data.roomId=payload.roomId;},denied({data}){data.roomId='';},},},},waiting:{},},});session.can('join',{roomId:'abc'});// evaluates guard only
session.send('join',{roomId:'abc'});// runs update when guard passes
Guards and updates are synchronous. Put async work, retries, diagnostics, and
command recovery in $workflow, then
use a machine transition to project the resulting state/data into the UI.
Example: Session Wizard
A machine is also useful for local wizard flow where the UI should only expose
legal next actions:
Static to keeps the flow inspectable: next, back, and submit declare
their route before mutation code runs. The same-state accept event omits to
because it only updates wizard data.
Example: Tic Tac Toe
A machine is useful when user actions must follow a small set of legal states.
This example keeps a tic tac toe game in playing until a move produces a win
or draw. Once the machine reaches xWon, oWon, or draw, there is no move
transition for that state, so further moves return false.
The transition owns both validation and projection. Its to resolver predicts
the next state without mutation; a valid move then updates data.board and
whose turn is next. An invalid move records lastError in denied, leaving
state unchanged. Machine transitions do not roll back data mutations when
hooks throw.
Persist With Transition Hooks
Transition hooks are a convenient place to persist the game after each handled
move. The hook runs after state has been updated, so the snapshot contains the
terminal state when the move ends the game.
restore() does not run hooks, so loading the saved game will not immediately
write another snapshot. The next successful send() call will persist the
updated state and data.
Snapshots
Use snapshot() to capture durable state and data. A snapshot is a structured
clone of { state, data }:
A snapshot contains only state and data. It has no version or machine id,
and it does not include transitions or hooks; those still come from the machine
config. Use $workflow or a workflow supervisor when persisted recovery needs
versioning, diagnostics, history, retry, or migration.
The clone uses the browser’s structuredClone() behavior. Values such as
objects, arrays, Dates, Maps, Sets, typed arrays, and cycles can be cloned.
Functions, DOM nodes, and other non-cloneable values will throw the native
structured clone error.
JSON persistence is a narrower contract. Use JSON.stringify(snapshot) only
when data is JSON-compatible, or add your own encode/decode layer before
storing the snapshot.
Use restore(snapshot) to recover an existing machine:
restore() mutates the existing data object in place so scope proxies and
templates keep their identity. Keys missing from the snapshot are removed,
nested plain objects are merged in place, and non-plain values like arrays are
replaced with cloned snapshot values. Restore accepts persisted input as
unknown, validates its shape, and rejects a state that is not declared in
states. Restore does not run transition hooks.
The configured initial state and every resolved transition target must also
exist in states. Invalid built-in machine states fail at their boundary
instead of leaving the machine in an unreachable state.
Hooks
Use hooks.enter and hooks.exit for state-specific effects. Use
hooks.transition for logging or cross-cutting work that should run after any
handled transition, including same-state transitions:
Hook context contains type, from, to, payload, data, and machine.
exit runs before state changes, enter runs after state changes, and
transition runs after state-specific hooks. Missing transitions do not run
hooks.
Hooks run synchronously inside the same send() batch. A hook may call
machine.send() to run another valid transition; nested sends are batched with
the outer transition. If a transition or hook throws, the error is rethrown,
prior data/state mutations are kept, and AngularTS still schedules bound scopes
for the keys touched by the attempted transition.
Scope Ownership
$machine(config) does not tie the machine to the lifetime of any one scope.
The machine stores durable state and data internally. When an AngularTS scope
proxy wraps the machine, the machine registers with that proxy and uses its
scheduler for reactive template updates and batched send() calls.
That means a machine can be created once, assigned to a component, survive that
component being destroyed, and later be assigned to another scope.
Prefer $machine(config) plus assignment to the controller, model, service,
workflow, or runtime adapter that owns the machine. Named machines registered
with module.machine() follow the same rule: they bind when a scope proxy
observes them.
Machine definitions are usable without generic parameters. Event names are
inferred from the on maps and state names are inferred from states. Payloads
remain unknown until an explicit event map supplies their types.
Add an event map when you want strict checked event names and payloads:
The ordinary authoring path does not require extraction helpers. Package authors
can import MachineDataOf, MachineEventsOf, MachineEventNamesOf, and
MachineStatesOf directly from @angular-wave/angular.ts/services/machine when
an adapter needs to derive types from a reusable definition.
14.12 - $parse
URL normalization for HTML5/hashbang modes
14.13 - $rest
Typed REST resource client
The $rest service creates typed resource clients on top of a REST backend. Use
this page for the usage model and examples. Exact class members, method
signatures, return types, and configuration interfaces live in TypeDoc:
get(), create(), and update() return null when the backend returns no
entity. delete() resolves with no value. All backend failures reject instead
of being converted into fallback values.
Entity Mapping
Pass an entity class when raw JSON should be converted into richer objects.
When an entity class is supplied, response objects are passed through
new entityClass(data) before they are returned.
Request Options
The third factory argument is merged into backend requests. With the default
HTTP backend, use it for headers, credentials, cache options, transforms, or
custom param serialization.
Pass backend when a resource should use a custom backend. The backend receives
a normalized RestRequest with
expanded URLs, params, request data, collection URL, and resource id. This is
the extension point for tests, local persistence, IndexedDB, the browser Cache
API, or composed network/cache behavior.
createRestCacheKey() is used internally by
CachedRestBackend; it is
not part of the top-level AngularTS namespace. Cache stores receive the final
key string through the
RestCacheStore methods and
should treat it as opaque.
Request Bodies
When the default HTTP backend is used, request data is serialized by $http.
Scope proxies are deproxied before JSON serialization, so proxy helper
properties such as _target, _handler, and _proxy are not sent to the
server. AngularTS-generated repeat identity is stored in internal metadata, not
on the object, so it is not included in write payloads either.
Explicit application-owned fields are still normal data. If your model defines a
property such as $hashKey, $rest does not remove it.
URI Templates
Resource URLs support RFC 6570 URI templates. Variables wrapped in braces are
expanded from the params passed to methods such as list().
The CRUD demo at /src/services/rest/rest-crud-demo.html talks to the Go demo
backend through /api/tasks. It uses ng-repeat for rows, $rest for CRUD
operations, and a cache strategy toggle for network-first, cache-first, and
stale-while-revalidate.
Cross-cutting security policy gate for navigation and request-sensitive operations.
$security centralizes framework security policy decisions so apps can keep
cross-cutting concerns out of every service call. The runtime-composed service
allows by default and applies typed configuration from
app.config({ $security: ... }).
Services consume this policy before sensitive side effects:
$http calls check() once and applies credentials from its allow decision.
$log synchronously gates Beacon delivery before serializing an entry.
Router transitions call check() before state changes proceed.
When no fallback is configured, the fallback decision is allow.
fallback controls unspecified behavior ("allow" | "deny"). Redirects are
declared on route policies where a target is required.
Configuring a credential enables its request transport scheme. It does not
declare that the current user is authenticated. isAuthenticated supplies that
application state as a boolean or contextual async function. The optional
credentials.order changes the default bearer, basic, then cookie precedence
without requiring a second list of enabled schemes.
allowInsecureOrigins is an explicit allowlist for HTTP origins that may carry
sensitive credentials. All other insecure origins remain blocked.
Malformed origin strings are ignored, preserving fail-closed credential
transport. Invalid policy enums and configuration shapes fail during setup.
credentials.bearer attaches a bearer token to allowed $http requests.
Caller-provided Authorization headers are recognized automatically.
credentials.basic accepts username and password value sources and encodes
the resulting credentials for the Authorization header.
credentials.cookie tells request services to use browser-managed cookies by
enabling credentialed requests instead of manually writing a Cookie header.
Request policy contexts identify their framework transport. HTTP, Beacon,
Worker, and Service Worker creation all pass through the same security gate.
Beacon participates automatically, but its Web Platform API cannot apply custom
headers. Bearer and Basic policies therefore deny Beacon delivery, while an
enabled cookie session permits it. Security evaluation remains synchronous
inside the Beacon transport so lifecycle delivery is never delayed by a Promise.
permissions receives each required permission and the current navigation
context. It may return a boolean or a Promise. A static permission array is a
shorthand for applications that do not need contextual authorization.
Route Policy
States can declare inherited navigation policy. The router merges policy from
the target state path and passes the effective policy to $security before
resolves, controllers, or views run.
authenticated: true is satisfied only when the configured isAuthenticated
evaluator resolves true. Credential transport and authentication state remain
separate.
Required permissions are resolved by the configured permissions evaluator. If
a required permission is missing, navigation is denied or redirected to the
route policy’s redirectTo target.
Non-security route policies (loading, error, fallbackTo, retry) run in
the same transition pipeline but do not replace authentication/authorization.
They are complementary: $security decides whether navigation is allowed for
the current principal, and route policies decide how the transition is rendered
or recovered when that navigation remains allowed.
Use public: true on a child state to explicitly clear inherited navigation
requirements. A public declaration cannot also specify authenticated,
permissions, redirectTo, or reason because those fields would be
contradictory or ignored.
Security policy has no public provider. Use app.config({ $security: ... });
route matching remains exclusively in the router state tree.
Service worker lifecycle, update, and message facade
$serviceWorker wraps browser-owned service worker registration, readiness,
update observation, controller changes, and page-to-worker messaging.
Use it when the browser owns the page-control lifecycle. Use $worker for
page-owned background computation. Use $rest and RestBackend for request and
cache composition. Use $workflow for command execution and recovery; a service
worker can be an activity boundary, not the workflow engine.
Or configure the script without automatic registration and start it explicitly:
angular.createModule('app',[]).serviceWorker('/sw.js',{scope:'/',updateViaCache:'none',});$serviceWorker.register();// Override browser registration options without repeating the configured URL.
$serviceWorker.register({scope:'/feature/'});$serviceWorker.ready().then(()=>{status=$serviceWorker.status;});
The default policy is observation only. AngularTS does not reload the page,
activate a waiting worker, or pick a cache strategy for the app.
Destroying an AngularTS runtime removes its service-worker event listeners and
reactive bindings. It does not call unregister(); browser registration remains
active until the application explicitly unregisters it.
registrationState and updateState are readonly framework-owned views. Their
properties update reactively as the browser registration changes.
If the user opts in, send your own protocol message to the waiting worker. Keep
reload separate so the app can save state, finish work, or defer navigation.
The service-worker message handler responds through event.ports[0]. AngularTS
does not add request ids or prescribe an application message envelope.
Security
Registration participates in $security automatically. Bearer and Basic
credential policies deny registration because the Web Platform registration API
cannot attach application-defined Authorization headers. Cookie credentials
remain browser-managed.
REST And Cache
Do not make $rest depend on service workers. REST caching belongs behind
RestBackend and RestCacheStore; a service-worker-backed REST cache should be
an explicit backend adapter when the app needs that boundary.
Unsupported Browsers
When navigator.serviceWorker is unavailable, $serviceWorker.supported is
false, status is unsupported, and async lifecycle methods reject with a
stable service-worker error. Applications can render a non-offline experience
without special provider wiring.
The first time a template is used, it is loaded in the template cache for quick
retrieval. You can load templates directly into the cache in a script tag by
using $templateRequest or by consuming the $templateCache service directly.
Note: the script tag containing the template does not need to be included in the
head of the document, but it must be a descendent of the $rootElement (e.g.
element with ng-app attribute), otherwise the template will be ignored.
Adding via the $templateCache service:
Example
constmyApp=angular.createModule('myApp',[]).run(["$templateCache",($templateCache)=>{$templateCache.set('templateId.html','This is the content of the template');}]);
To retrieve the template, simply use it in your component:
$templateRequest assumes template URLs are trusted by the caller. Security
policy for resource URLs belongs to $compile.resourceUrlPolicy when templates
are loaded through compiler bindings such as ng-include.
14.20 - $url
URL management for state transitions
14.21 - Binding policies
Destination-specific HTML, URL, and script binding policies
Binding policies
AngularTS uses synchronous callbacks configured through $compile to sanitize
or validate values at their DOM destination. The former $sce and
$sceDelegate services and their configuration keys have been removed.
Configuration
Each callback receives a string and must return a string or throw to reject it.
Omitted callbacks retain the framework defaults.
Supply your application’s sanitizer implementation, such as DOMPurify, before
using this configuration. A policy callback is responsible for the values it
approves. Policies are configured before bootstrap and are not injectable
services.
Native TrustedHTML, TrustedScript, and TrustedScriptURL values are
supported at their corresponding destinations. The framework does not create
browser Trusted Types policies for the application.
Declarative asynchronous operation lifecycles with reactive state, cancellation, diagnostics, snapshots, and recovery.
Workflow
$workflow runs asynchronous operations through declarative lifecycles. A
definition states where a command may run and what state and data changes occur
while it is pending, after success, and after failure. Application code only
requests the operation.
Use $machine for synchronous event-driven state. Use $workflow when an
operation can wait, fail, time out, be cancelled, or require recovery.
Lifecycle updates are the writable boundary for workflow data. The data passed
to execute is readonly, including nested plain objects, arrays, maps, and
sets. This prevents a failed command from leaving untracked imperative changes.
Immediate Commands
An operation that only changes workflow state can omit execute:
External approvals, WebSocket messages, user actions, and repair requests enter
a workflow as commands. Workflows do not expose a separate event or send()
API.
Successful results have status completed. Failed results use failed,
cancelled, timeout, or rejected.
rejected means execution did not fail unexpectedly. The command was unknown,
was not allowed from the current state, violated its concurrency declaration,
or called reject() with a controlled diagnostic.
Controlled Rejection
Use reject() for an expected business decision:
execute({input,reject}){if(!input.acceptedTerms){returnreject({code:'checkout.termsRequired',message:'Terms must be accepted.',recoverable:true,});}returncheckoutApi.submit(input);}
The framework records the diagnostic and applies the declared failure
lifecycle.
Cancellation And Timeout
Commands receive an AbortSignal and may register cleanup callbacks:
reject is the default and rejects another run of the same command.
queue executes runs of the same command in request order.
parallel allows overlapping execution when the declared source states also
permit it.
retry is the number of automatic retries after unexpected execution
failure.
Callers cannot override these properties through run(). Operational behavior
therefore remains part of the workflow definition.
Diagnostics And History
workflow.diagnostics contains normalized, serializable evidence from failed
operations. workflow.history records command start, completion, and final
failure.
workflow.diagnostics;workflow.history;
Use diagnosticLimit and historyLimit to bound retained entries:
autoPersist saves after every settled command. autoRecover restores the
latest snapshot and reruns the latest recoverable failed command for each
workflow.
Worker Runtime
The optional workflow worker runtime transports run, snapshot, and
restore. It does not define another orchestration protocol:
The runtime intentionally has no send(), manual lifecycle transition, or
per-run orchestration options.
15 - Filters
Transform displayed values in templates without changing the stored value.
A filter formats a value for display. For example, a filter can sort a list or
convert an object to JSON. Filters do not replace methods that change
application state.
15.1 - Async
Render a promise result when it settles.
The async filter returns undefined while a promise-like value is pending.
When it settles, AngularTS schedules change detection and returns the resolved
value. A rejection is exposed as the settled value so the view can decide how to
present it.
<p>{{ profilePromise | async }}</p>
Keep the same promise instance between render passes. Creating a promise in the
template starts new work repeatedly; create it in a controller or model instead.
15.2 - Filter collections
Select collection items with a string, object, or predicate.
/**
@ngdoc filter
@name filter
@kind function
@description
Selects a subset of items from array and returns it as a new array.
@param {Array} array The source array.
Note: If the array contains objects that reference themselves, filtering
is not possible.
@param {string|Object|function()} expression The predicate to be used for
selecting items from
array.
Can be one of:
string: The string is used for matching against the contents of the
array. All strings or
objects with string properties in `array` that match this string will be returned. This also
applies to nested object properties.
The predicate can be negated by prefixing the string with `!`.
Object: A pattern object can be used to filter specific properties on
objects contained
by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
which have property `name` containing "M" and property `phone` containing "1". A special
property name (`$` by default) can be used (e.g. as in `{$: "text"}`) to accept a match
against any property of the object or its nested object properties. That's equivalent to the
simple substring match with a `string` as described above. The special property name can be
overwritten, using the `anyPropertyKey` parameter.
The predicate can be negated by prefixing the string with `!`.
For example `{name: "!M"}` predicate will return an array of items which have property `name`
not containing "M".
Note that a named property will match properties on the same level only, while the special
`$` property will match properties on the same level or deeper. E.g. an array item like
`{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
**will** be matched by `{$: 'John'}`.
function(value, index, array): A predicate function can be used to write
arbitrary filters.
The function is called for each element of the array, with the element, its index, and
the entire array itself as arguments.
The final result is an array of those elements that the predicate returned true for.
@param {function(actual, expected)|true|false} [comparator] Comparator which
is used in
determining if values retrieved using `expression` (when it is not a function) should be
considered a match based on the expected value (from the filter expression) and actual
value (from the object in the array).
Can be one of:
function(actual, expected):
The function will be given the object value and the predicate value to compare and
should return true if both values should be considered equal.
true: A shorthand for strict value comparison.
This is essentially strict comparison of expected and actual.
false: A short hand for a function which will look for a substring match
in a case
insensitive way. Primitive values are converted to strings. Objects are not compared against
primitives, unless they have a custom `toString` method (e.g. `Date` objects).
Defaults to false.
@param {string} [anyPropertyKey] The special property name that matches
against any property.
By default `$`.
*/
15.3 - Collection filters
Read keys, values, and entries from objects and keyed collections.
The collection filters accept objects, Map, and other values that expose the
corresponding iterable method.
<ling-repeat="name in team | keys">{{ name }}</li><ling-repeat="member in team | values">{{ member }}</li><ling-repeat="entry in team | entries">{{ entry.key }}: {{ entry.value }}</li>
null, undefined, primitives, and unsupported inputs produce an empty array.
Use entries when both the key and value are needed.
15.4 - JSON
Render a value as formatted JSON for inspection and debugging.
JSON filter
Description
Allows you to convert a JavaScript object into a JSON string.
This filter is mostly useful for debugging. When using the double curly
{{value}} notation, the binding is automatically converted to JSON.
Parameters
object{*}: Any JavaScript object (including arrays and primitive types)
to filter.
spacing{number=}: The number of spaces to use per indentation, defaults
to 2.
Returns
{string}: JSON string.
15.5 - Date
Format dates with the browser’s locale-aware formatter.
The date filter accepts a Date, timestamp, or date string. It delegates to
Intl.DateTimeFormat, so locale and formatting options follow the browser
standard.
<time>{{ task.dueAt | date:'en-GB' }}</time>
A missing or invalid input produces an empty string. Store real dates or
unambiguous timestamps in application state.
15.6 - Limit to
Return a bounded part of an array, string, or number.
Limit to filter
Description
Creates a new array or string containing only a specified number of elements.
The elements are taken from either the beginning or the end of the source array,
string, or number, as specified by the value and sign (positive or negative) of
limit. Other array-like objects are also supported (e.g., array subclasses,
NodeLists, JQLite/jQuery collections, etc.). If a number is used as input, it is
converted to a string.
Parameters
input{Array|ArrayLike|string|number}: Array/array-like, string, or
number to be limited.
limit{string|number}: The length of the returned array or string.
If the limit number is positive, limit number of items from the
beginning of the source array/string are copied.
If the number is negative, limit number of items from the end of the
source array/string are copied.
The limit will be trimmed if it exceeds array.length.
If limit is undefined, the input will be returned unchanged.
begin{(string|number)=}: Index at which to begin limitation. As a
negative index, begin indicates an offset from the end of input. Defaults
to 0.
Returns
{Array|string}: A new sub-array or substring of length limit or less if
the input had less than limit elements.
15.7 - Number, currency, and percent
Format numeric values with locale-aware browser formatters.
AngularTS provides three filters backed by Intl.NumberFormat:
number formats a finite number.
currency uses currency style and defaults to USD.
percent uses percentage style.
<span>{{ total | number:'en-US' }}</span><span>{{ total | currency:'en-US' }}</span><span>{{ completion | percent:'en-US' }}</span>
Numeric strings are accepted. Missing, empty, infinite, and nonnumeric values
produce an empty string. Supply an explicit locale and currency for user-facing
output.
15.8 - Order by
Order a collection by one or more computed values.
Order by filter
Description
Returns an array containing the items from the specified collection, ordered
by a comparator function based on the values computed using the expression
predicate.
For example, [{id: 'foo'}, {id: 'bar'}] | orderBy:'id' would result in
[{id: 'bar'}, {id: 'foo'}].
The collection can be an Array or array-like object (e.g., NodeList, jQuery
object, TypedArray, String, etc).
The expression can be a single predicate or a list of predicates, each serving
as a tie-breaker for the preceding one. The expression is evaluated against
each item and the output is used for comparing with other items.
You can change the sorting order by setting reverse to true. By default,
items are sorted in ascending order.
The comparison is done using the comparator function. If none is specified, a
default, built-in comparator is used (see below for details - in a nutshell, it
compares numbers numerically and strings alphabetically).
Under the Hood
Ordering the specified collection happens in two phases:
All items are passed through the predicate (or predicates), and the returned
values are saved along with their type (string, number, etc). For
example, an item {label: 'foo'}, passed through a predicate that extracts
the value of the label property, would be transformed to:
{value:'foo',type:'string',index:...}
Note: null values use ’null’ as their type. 2. The comparator function is used
to sort the items, based on the derived values, types, and indices.
If you use a custom comparator, it will be called with pairs of objects of the
form {value: …, type: ‘…’, index: …} and is expected to return 0 if the
objects are equal (as far as the comparator is concerned), -1 if the 1st one
should be ranked higher than the second, or 1 otherwise.
To ensure that the sorting will be deterministic across platforms, if none of
the specified predicates can distinguish between two items, orderBy will
automatically introduce a dummy predicate that returns the item’s index as
value. (If you are using a custom comparator, make sure it can handle this
predicate as well.)
If a custom comparator still can’t distinguish between two items, then they will
be sorted based on their index using the built-in comparator.
Finally, in an attempt to simplify things, if a predicate returns an object as
the extracted value for an item, orderBy will try to convert that object to a
primitive value before passing it to the comparator. The following rules govern
the conversion:
If the object has a valueOf() method that returns a primitive, its return value
will be used instead. (If the object has a valueOf() method that returns another
object, then the returned object will be used in later steps.) If the object has
a custom toString() method (i.e., not the one inherited from Object) that
returns a primitive, its return value will be used instead. (If the object has a
toString() method that returns another object, then the returned object will be
used in later steps.) No conversion; the object itself is used. The Default
Comparator The default, built-in comparator should be sufficient for most use
cases. In short, it compares numbers numerically, strings alphabetically (and
case-insensitively), for objects falls back to using their index in the original
collection, sorts values of different types by type, and puts undefined and null
values at the end of the sorted list.
More specifically, it follows these steps to determine the relative order of
items:
If the compared values are of different types: If one of the values is
undefined, consider it “greater than” the other. Else if one of the values is
null, consider it “greater than” the other. Else compare the types themselves
alphabetically. If both values are of type string, compare them alphabetically
in a case- and locale-insensitive way. If both values are objects, compare their
indices instead. Otherwise, return: 0, if the values are equal (by strict
equality comparison, i.e., using ===). -1, if the 1st value is “less than” the
2nd value (compared using the < operator). 1, otherwise. Note: If you notice
numbers not being sorted as expected, make sure they are actually being saved as
numbers and not strings. Note: For sorting, null and undefined are considered
“greater than” any other value (with undefined “greater than” null). This
effectively means that null and undefined values end up at the end of a list
sorted in ascending order. Note: null values use ’null’ as their type to be able
to distinguish them from objects.
Parameters collection {Array|ArrayLike}: The collection (array or array-like
object) to sort.
expression {(Function|string|Array.<Function|string>)=}: A predicate (or list of
predicates) to be used by the comparator to determine the order of elements.
Can be one of:
Function: A getter function. This function will be called with each item as an
argument and the return value will be used for sorting. string: An AngularTS
expression. This expression will be evaluated against each item and the result
will be used for sorting. For example, use ’label’ to sort by a property called
label or ’label.substring(0, 3)’ to sort by the first 3 characters of the label
property. (The result of a constant expression is interpreted as a property name
to be used for comparison. For example, use ‘“special name”’ (note the extra
pair of quotes) to sort by a property called special name.) An expression can be
optionally prefixed with + or - to control the sorting direction, ascending or
descending. For example, ‘+label’ or ‘-label’. If no property is provided,
(e.g., ‘+’ or ‘-’), the collection element itself is used in comparisons. Array:
An array of function and/or string predicates. If a predicate cannot determine
the relative order of two items, the next predicate is used as a tie-breaker.
Note: If the predicate is missing or empty, then it defaults to ‘+’.
reverse {boolean=}: If true, reverse the sorting order.
comparator {(Function)=}: The comparator function used to determine the relative
order of value pairs. If omitted, the built-in comparator will be used.
Returns {Array}: The sorted array.
15.9 - Relative time
Format relative dates such as tomorrow or three days ago.
The relativeTime filter delegates to Intl.RelativeTimeFormat. Its input is
an offset, not a timestamp. The unit defaults to day.
Use AngularTS from JVM, JavaScript-targeting, native, and WebAssembly languages.
AngularTS modules, injection, components, directives, and scopes are available
from several languages. Choose the language that already builds the part of your
application that runs on the page.
Begin with the integration model, then open the setup guide for your language.
Java, ClojureScript, and Scala.js are available from Maven Central. Dart is on
pub.dev, Gleam is on Hex, and Closure externs ship in the npm package. Kotlin
and the WebAssembly bindings are currently distributed as source with complete
examples.
Choose a language binding and understand how it works with the AngularTS runtime.
What stays the same
An integration changes the language used to register AngularTS features. It
does not introduce a second module system or component lifecycle. Modules,
injection, components, directives, scopes, templates, and services behave the
same way.
Choose by language
Use the binding for the language that produces your client-side code:
Closure JavaScript uses the extern file from the npm package.
ClojureScript, Java/J2CL, and Scala.js use Maven Central artifacts.
Dart uses pub.dev.
Gleam uses Hex.
Kotlin and the WebAssembly languages currently use source dependencies.
Every integration still needs @angular-wave/angular.ts on the page. Load it
once and keep its version equal to the language binding version.
Start from a complete project
Open the guide for your language, install its required compiler, and begin with
the linked todo project. The
example shows the dependency, entry point, page, build output, and AngularTS
startup together.
17.2 - Integration example projects
Find a complete todo project for each AngularTS language integration.
Use these projects when starting an application. Each one keeps the dependency,
compiler configuration, AngularTS registration, page, and build output in one
place.
Integration
Example
Closure Compiler
integrations/closure/demo
ClojureScript
integrations/closure/clojurescript/demo
Java/J2CL
integrations/closure/java/demo
Kotlin/JS
integrations/kotlin/examples/basic_app
Scala.js
integrations/scala/examples/basic_app
Dart
integrations/dart/example/basic_app
Gleam
integrations/gleam/examples/basic_app
HarmonyOS
integrations/harmonyos/entry
AssemblyScript
integrations/wasm/assemblyscript/examples/todo
C
integrations/wasm/c/examples/todo
C++
integrations/wasm/cpp/examples/todo
C#/.NET
integrations/wasm/csharp/examples/todo
Go
integrations/wasm/go/examples/basic_app
Rust
integrations/wasm/rust/examples/basic_app
Zig
integrations/wasm/zig/examples/todo
Copy the project structure that matches your language, then replace the todo
model and markup one feature at a time.
17.3 - Closure Compiler
Compile AngularTS code with Closure ADVANCED optimization while preserving runtime API names through the packaged extern file.
Closure Compiler optimizes your JavaScript. AngularTS stays outside that output,
and its extern file tells Closure which public names must not be renamed.
Install
Install @angular-wave/angular.ts and Closure Compiler in the application.
Pass dist/externs/angular.js from the npm package to Closure, and compile the
production output with ADVANCED optimization.
Load dist/angular-ts.umd.min.js before the compiled application file:
Treat the packaged extern as part of the AngularTS version; do not edit it.
Promote Closure warnings to errors.
Keep AngularTS external so the page loads only one runtime.
Use documented public APIs so Closure can preserve every accessed member.
Test the ADVANCED output, not only uncompiled source.
Complete example
Use integrations/closure/demo as a complete todo project, or browse all
integration examples.
17.4 - ClojureScript
Create a Shadow CLJS application with the Maven facade, packaged externs, explicit injection, and the AngularTS browser runtime.
The angular-ts-cljs Maven artifact provides the angular-ts.core API and the
Closure externs needed by an optimized ClojureScript build. Keep it on the same
version as the AngularTS runtime loaded by the page.
Before you start
Install Java 21, Node.js, and Shadow CLJS. Create a project with
src/main/todo/app.cljs, shadow-cljs.edn, and an HTML entry page.
Configure Shadow CLJS
Add the Maven artifact and tell Closure where the packaged extern file is:
Keep :warnings-as-errors true; extern or type-tag drift should stop the build
instead of producing renamed runtime calls.
Register the application
Use angular-ts.core for fluent module registration. Dependency names stay
explicit because optimized ClojureScript function names are not injectable
names.
Use JavaScript objects and arrays at the framework boundary. Keep persistent
Clojure collections inside domain code and convert them once when assigning a
template-visible value.
Load and start the application
Load AngularTS before the compiled module and bootstrap after both are ready:
<mainid="app"ng-controller="TodoCtrl as ctrl"></main><scriptsrc="angular-ts.umd.js"></script><scriptsrc="/js/app.js"></script><script>angular.bootstrap(document.getElementById('app'),['todo']);</script>
Build with npx shadow-cljs release app. Serve public over HTTP.
Production practices
Prefer the fluent facade; use the lower-level namespace only when no helper
exists.
Keep *warn-on-infer* enabled and fix every inference warning.
Use explicit injection vectors for controllers, services, directives, and
configuration blocks.
Load one AngularTS runtime even when several ClojureScript modules share the
page.
Compile and test with Closure ADVANCED optimization before release.
Complete example
Use integrations/closure/clojurescript/demo as a complete Shadow CLJS todo
project, or browse all integration examples.
17.5 - Java and J2CL
Create an AngularTS application in Java with the Maven bindings, annotation processor, J2CL compiler, and AngularTS runtime.
J2CL compiles Java to JavaScript. The angular-ts-java artifact supplies typed
JsInterop bindings and an annotation processor; @angular-wave/angular.ts
supplies the browser runtime. Keep both on the same version.
The Maven artifact includes the Java sources and AngularTS externs required by
J2CL. Your application only generates and passes its own template extern.
Before you start
Install JDK 21, Maven, and Node.js. Create a Maven project with src/main/java
for Java sources and a directory for the page that loads the compiled output.
Add the Java bindings
Add the version and regular dependency to pom.xml:
The dependency supplies its runtime extern automatically. The processor
generates the application Closure entry point and template extern. Configure
the J2CL Maven plugin with goog:angular.ts.generated.entrypoint and
META-INF/externs/angular-ts-template.externs.js. The complete tested plugin
configuration is in integrations/closure/java/demo/pom.xml.
Register the application
Mark the startup method with @AngularEntryPoint. Mark only Java types that
templates access with @AngularTemplateApi.
@AngularEntryPoint
public static void start() {
Angular.createModule("todo", new String[0])
.controller("TodoCtrl", (TodoFactory) TodoController::new);
}
@JsFunction
interface TodoFactory {
TodoController create();
}
The maintained todo demonstrates public fields, template methods, directives,
and array replacement in integrations/closure/java/demo/src/main/java.
Load and start the application
Load AngularTS first, then the J2CL bundle, then bootstrap the registered module:
<mainid="app"ng-controller="TodoCtrl as ctrl"></main><scriptsrc="angular-ts.umd.js"></script><scriptsrc="todo.js"></script><script>angular.bootstrap(document.getElementById('app'),['todo']);</script>
Build with mvn package. Serve the page over HTTP; do not open it through a
file: URL.
Production practices
Use the published artifact as a dependency; never copy generated bindings
into the application.
Keep Closure ADVANCED optimization enabled and keep template extern
generation in the build.
Expose the smallest possible template API. Unannotated Java implementation
details remain optimizable.
Replace Java arrays when their length changes so AngularTS observes the new
collection identity.
Test the optimized J2CL output against the matching AngularTS bundle.
Complete example
Use integrations/closure/java/demo as a complete Maven and J2CL todo project,
or browse all integration examples.
17.6 - Kotlin/JS
Add the Kotlin source binding, register an AngularTS module, and build the Kotlin/JS output.
The Kotlin integration provides typed access to AngularTS modules, injection,
components, directives, services, and scopes.
Add the binding
The Kotlin binding is currently distributed as source. Add
integrations/kotlin as a Gradle project dependency and use
integrations/kotlin/examples/basic_app as the application structure. Enable a
Kotlin/JS IR browser target with an executable binary.
Load the matching @angular-wave/angular.ts runtime before the Kotlin/JS output.
Guidance
Use typed injection tokens and explicit dependency order.
Prefer Kotlin wrappers over dynamic JavaScript access.
Keep JavaScript conversion at the edge of the application.
Build and test the optimized Kotlin/JS output used in deployment.
Complete example
Use integrations/kotlin/examples/basic_app as a complete Gradle project.
integrations/kotlin/examples/web_components shows custom elements. See all
integration examples.
17.7 - Android
Run server-rendered AngularTS screens in Android navigation and add native controls where the web page needs Android behavior.
Angular Native keeps the server and HTML in charge of most screens. Android owns
navigation, system access, and any controls that need native behavior. You can
move one control or one destination to native UI without turning the whole
application into a client-rendered app.
Adding one of these packages is enough: Android registers it during application
startup without an application-side registration call. credentials provides
password and passkey operations through Android Credential Manager. media
provides Media3 playback without adding Media3 to the base shell. maps adds
the catalog map element; configure the Google Maps API key in the application
manifest as required by the Maps SDK.
Do not install all optional packages by default. Keeping them separate avoids
initializing SDKs and shipping dependencies that a screen does not use.
The app needs network access for server-rendered pages:
System back and predictive-back gestures work through AndroidX Navigation. The
navigation package opts into predictive back and makes the selected navigator
host Android’s primary navigation fragment, including when bottom tabs switch.
Do not add an Activity back callback for normal page history; add one only for
temporary UI that must consume Back, such as an unsaved-form confirmation.
Native navigation uses one Android back stack. Use navigation.push,
navigation.replace, navigation.pop, navigation.modal,
navigation.deep-link, or navigation.external. Push, replace, modal, and
deep-link accept default, none, slide, fade, cover, dive, or flip.
Android disables explicit motion when system animations are off.
A navigation command returns phase: "accepted" with a transaction ID. Treat
the matching navigation.change event as authoritative: it reports
phase: "completed" after Android commits the destination, or
phase: "cancelled" with reason: "interrupted", "destination-changed",
"failed", or "closed". Android back gestures emit one completed change
without replaying another navigation command.
AndroidX restores the route, route parameters, and back stack after process
recreation. Angular Native also restores an unconsumed navigation action or
modal result and delivers it once. It does not put response HTML or cached
snapshot HTML into Android saved state: the restored destination requests that
content again, avoiding Binder-size failures and stale page data.
Start with the complete
demo.
It shows the Application, AngularNativeActivity, path configuration,
destinations, bottom tabs, file handling, and bridge components together.
Add a native control to HTML
ng-native-component replaces the marked rectangle when the page runs inside
the Android shell. In a normal browser, the HTML fallback remains usable.
The model works in both directions. AngularTS sends model, required, disabled,
and validation state to Android. Android changes update the same ng-model and
blur marks it as touched.
File selection uses the same model binding. Android returns content URI records,
not filesystem paths:
The native button opens Android’s document picker. Cancelling leaves the model
unchanged. A successful selection assigns an array of
{ uri, name, size, type, persisted } records when multiple is true, or one
record when it is false.
Native list and grid children require a unique key. The key lets Android
reuse visible views and restore each item’s state while large collections
scroll.
Native tabs use each child’s key as their controlled string value; native
pager uses a zero-based integer value. Updating the model moves selection
without recreating keyed child views. A swipe or tab press emits change, and
scrollTo provides the same selection behavior for imperative native calls.
Native layout numbers use density-independent pixels. width and height set
an exact size; without them, containers fill the available width and either wrap
their content or fill the available height when the component is a scroll,
scaffold, tabs, or pager. Use minWidth, minHeight, and spacing for
constraints and gaps. horizontalAlignment accepts start, center, end, or
stretch; verticalAlignment accepts top, center, bottom, or stretch.
Layout updates keep keyed child views and reset omitted values to their
documented defaults.
Style native controls with CSS
Native elements use the page’s computed CSS. Keep behavior and accessibility in
HTML, then reuse classes instead of repeating presentation attributes. Angular
Native resolves CSS variables, inheritance, and the cascade before it sends
styles to Android.
The supported CSS projection covers foreground and background colors,
typography, dimensions and minimum sizes, padding, margins, gaps, borders,
radii, opacity, shadows, text alignment, object-fit, and accent-color. CSS
pixels map to Android density-independent pixels; font sizes map to scaled
pixels. Class and root-theme changes update mounted native views without
recreating keyed children.
Use the semantic tokens from Angular.css when the web and native versions of a
screen share a theme. Applications can override --background, --foreground,
--primary, --accent, --muted, spacing, radius, and typography tokens in
their own stylesheet. Android receives resolved values, so it does not need to
parse CSS or know which token library supplied them.
Use ng-native-event for native events and ng-native for methods or platform
capabilities. Calls are ignored when no native bridge is available, so keep a
normal HTML path for essential actions.
For current location, declare coarse or fine location permission in the Android
manifest, request it while the destination is visible, then call the location
capability:
<buttonng-native="geolocation.current"data-on-result="position = $result"data-on-error="locationError = $error.message"> Use my location
</button><png-if="position">{{ position.latitude }}, {{ position.longitude }}</p>
Call permissions.request first when geolocation.status reports that access
has not been granted. Its accuracy result is fine, coarse, or null, so a
coarse-only grant is reported as usable without claiming precise access.
geolocation.current rejects with denied, unavailable, or interrupted
instead of returning stale coordinates. Aborting the browser call cancels the
Android location request.
Use the system camera when a page needs a new image. Angular Native stores the
capture behind a content URI and deletes an unfinished capture:
The result is { uri, name, size, type }. Check camera.status before showing
the action on devices without a camera.
Optional native capabilities appear in the environment capability list only when
their artifact is installed. Use $native.supports(target, method) before
showing an action. Media sources must use https, content, or
android.resource; local file URLs are rejected. Credential and media work is
cancelled when its destination is removed. The map element preserves its camera
position, forwards Android lifecycle changes, and emits ready, cameraChange,
markerClick, and mapClick events.
Call connectivity.watch or lifecycle.watch to receive change events for
the current destination. Call the matching unwatch method when the page no
longer needs them. Removing the destination also stops both subscriptions.
Call window.status for the current window width, height, orientation,
safe-area insets, and width and height size classes. Use window.watch when a
layout must react while the app is resized, rotated, folded, or moved into split
screen. Its change event also includes displayFeatures; folds and hinges
report their orientation, state, separating behavior, and bounds in
density-independent pixels. Call window.unwatch when the destination no longer
needs updates.
Application libraries can add a native service by implementing
NativeCapabilityProvider.
The provider receives a destination-scoped
NativeCapabilityContext;
use its emit method instead of retaining the bridge or WebView. Targets,
methods, and event names must use lowercase kebab-case. Close listeners and
other resources from NativeCapability.close.
See the generated
native element catalog
for every element name, property, method, and event. The generated
native capability catalog
lists each service, artifact, availability rule, permission, method, and event.
Add an application component
Implement NativeElementProvider in the Android library that owns the UI. A
provider returns NativeElementDefinition values backed by a normal Android
View factory or composeNativeElement. Add the metadata compiler to that
library:
Use the same com.android.legacy-kapt version as the application’s Android
Gradle plugin. List every provided name in
@RegisterNativeElementProvider("task-card", "task-status"). The compiler
generates Java service-provider metadata, and Angular Native discovers the
provider when the shell starts. The application does not edit or call a central
component registry.
The published io.github.angular-wave:angular-native-custom-elements-sample
artifact contains the complete task-cardView provider and task-status
Compose provider. Use it as a working reference; keep application components in
the Android library that owns their UI and dependencies.
Compose factories receive current properties and a scope with context,
lifecycleOwner, savedStateOwner, and emit:
Keep component names in the library that implements them. Reject unknown input
in the definition, emit plain JSON event data, release listeners in dispose,
and save only transient UI state in saveState.
Security
Every destination has a random session token. Android accepts bridge calls only
from the destination’s original HTTP or HTTPS origin and rejects stale sessions.
Intent URLs are limited to http, https, mailto, tel, and geo. File
results use content URIs instead of filesystem paths.
Permission and file requests run only while their destination is visible. Calls
can be aborted with an AbortSignal; Android then suppresses queued work and
late replies. Destination removal rejects unfinished calls as interrupted.
Validate an Android change
make -C integrations/android release-check
make -C integrations/kotlin check
make generated-check
release-check recreates an isolated Maven repository, publishes every Android
artifact into it, and compiles and shrinks a clean consumer. AngularTS
coordinates resolve only from that repository, so a stale artifact in
mavenLocal() or Maven Central cannot hide an incomplete publication. It also
signs all nine publications with a disposable test key and verifies every
signature without requiring release credentials.
The root release workflow publishes every Android artifact to Maven Central,
waits for its public POM, and blocks the npm/GitHub release if one is missing.
Run the Pulse reference application
Pulse demonstrates a native photo feed, profiles, activity updates, native file
selection, and multipart uploads without application-specific Kotlin or Java
code. Start one Android emulator, then run:
make -C integrations/android sample-social-run
The command starts its local Node server, installs the sample, and opens it in
the emulator.
Pulse mounts one native scaffold containing its app bar, changing content, and
bottom navigation. Its path configuration sets toolbar_enabled to false
because the scaffold owns the app bar. Leave this property unset when the
fragment toolbar should remain visible. The sample demonstrates fade, slide,
cover, dive, and flip transitions on different routes.
The generic AngularNativeHostActivity reads its start URL and path
configuration from Android manifest metadata.
Native collections
Use list for one-dimensional data and grid for columns. Every child must
have a stable key, an element name, and optional props. Updating the
collection runs a keyed diff. Unchanged visible children keep their Android
view, state, event wiring, and accessibility state.
Set swipeEnabled to emit swipe with the item key, index, and logical start
or end direction. Set reorderEnabled to enable long-press drag and emit
move with the key and old and new indexes. Treat both as controlled events:
update the server or client model and send the resulting child order back to the
collection. list-item.selected is controlled in the same way.
pull-to-refresh emits refresh and keeps showing its progress indicator while
refreshing is true. Collection scroll position and mounted child state
round-trip through the normal native element save and restore lifecycle.
For datasets backed by Android Paging 3, use NativePagingAdapter from
angular-native-paging. Its loadState event reports refresh, prepend, append,
empty, retryable, and end-of-list state. Attach NativePagingLoadStateAdapter
as a footer when the screen needs localized loading, retry, and end labels. Call
the Paging adapter’s standard retry() and refresh() methods from those
actions.
When a physical device is available, run the additional hardware checks:
make -C integrations/android physical-check
You can also dispatch the Android Physical Device GitHub Actions workflow for
an exact commit. It records compatibility and performance evidence but does not
block a release. Portable CI, emulator-connected tests, package validation, and
clean Maven consumption remain mandatory.
The command rejects emulators, runs every connected test, then performs a keyed
update and scroll over 10,000 items. It captures AndroidX frame and memory
metrics and rejects updates that exceed the allocation cap. It also measures
1,000 JavaScript-to-Kotlin bridge calls and enforces bridge-latency and WebView
host-process memory ceilings. Versioned limits in
config/benchmark-budgets.json also fail the physical gate when startup, frame,
or memory metrics regress. The same device run generates a startup baseline
profile. Core and navigation AARs also ship narrow seed rules, so a clean
consumer receives useful startup guidance before the measured profile is
generated for the final application.
Dialogs and other overlays
Use dialog, bottom-sheet, drawer, menu, snackbar, and tooltip for
transient native UI. Each supports show, hide, and dismiss. Their events
report user actions such as confirm, cancel, selection, and snackbar actions.
Only one overlay can own a destination at a time; showing another dismisses the
current one and returns accessibility focus to its trigger.
Drawer, dialog, and bottom-sheet restore open after destination recreation.
Menu, snackbar, and tooltip restore closed because they represent a temporary
interaction rather than destination state. Disposing the destination dismisses
its overlay without retaining its Activity, Fragment, WebView, or scope. See the
native element catalog
for each element’s properties and event payloads.
17.8 - HarmonyOS
Run AngularTS pages in ArkWeb and place ArkUI controls over the page when a screen needs native HarmonyOS behavior.
Angular Native keeps HTML and the server in charge of the application. The
HarmonyOS shell owns navigation, system access, and controls that need ArkUI.
The same page continues to work in a normal browser because native elements
keep their HTML fallback.
All shared native elements and capabilities have HarmonyOS providers and
portable tests. The packages are not production releases until the official
API 26 build, device evidence, and public OHPM consumer checks pass.
Choose packages
Every application starts with @angular-wave/angular-native-harmony-core.
Add only the packages used by the application:
Package
Add it when
@angular-wave/angular-native-harmony-navigation
AngularTS routes should use ArkUI navigation and system back.
@angular-wave/angular-native-harmony-browser
External HTTP links should use the HarmonyOS resolver.
@angular-wave/angular-native-harmony-credentials
A page uses passkeys.
@angular-wave/angular-native-harmony-maps
A page contains the native map element.
@angular-wave/angular-native-harmony-media
A page controls native audio or video playback.
@angular-wave/angular-native-harmony-paging
A large keyed collection needs incremental native updates.
@angular-wave/angular-native-harmony-compiler
A library defines additional native elements for its applications.
Do not add every optional package. Separate packages keep platform SDKs and
initialization work out of applications that do not use them.
Prepare the project
Install Huawei’s official command-line tools and the HarmonyOS API 26 SDK. The
tool bundle must provide ohpm, hvigorw, and codelinter. Point AngularTS at
an existing installation:
The bootstrap target can install a local official archive without changing the
user’s shell. It deliberately requires a SHA-256 digest; do not run an
unverified SDK archive.
HARMONY_COMMAND_LINE_TOOLS_ARCHIVE="$HOME/Downloads/command-line-tools-linux.zip"\
HARMONY_COMMAND_LINE_TOOLS_SHA256="<verified digest>"\
make -C integrations/harmonyos bootstrap
Open integrations/harmonyos in DevEco Studio when signing or device selection
needs the IDE. The entry module is the packaged proof application. It loads
the same AngularTS distribution that a release ships, not a development server.
Add native UI to a page
Use AngularTS native elements in HTML. AngularTS resolves CSS,
accessibility data, model bindings, and events, then sends normalized values to
the HarmonyOS shell. ArkUI renders the native control on the same-layer surface.
Application code does not register built-in controls and does not contain
ArkTS component mappings.
Keep reusable colors, spacing, type sizes, and control defaults in CSS. Do not
repeat native properties on every element. The browser computes the styles;
HarmonyOS receives density-independent values and applies system font scale,
safe areas, right-to-left layout, dark mode, and reduced motion.
Use Map Kit
Maps are optional because Huawei Map Kit is not needed by most applications.
Add the maps package, then pass its same-layer controller factory to the shell.
The page still uses the shared native map element; it does not call ArkTS or
Map Kit directly.
The factory renders MapComponent, keeps markers and camera state in sync,
forwards map events, and handles the native map methods declared by the shared
contract. The kitchen sink uses this exact provider wiring.
Map Kit requires Huawei’s HarmonyOS API 26 SDK. The public OpenHarmony API 23
lane type-checks the provider against a declaration-only compatibility stub,
but deliberately excludes the maps HAR and the kitchen-sink HAP from its build.
Only the official API 26 release gate can prove that Map Kit package.
Run the sample apps
Use the kitchen sink to inspect every native element and its current contract.
The page is generated from the shared catalogs, so checks fail when a property,
event, method, or capability is missing.
make -C integrations/harmonyos/samples/kitchen-sink compile
make -C integrations/harmonyos/samples/pulse compile
127.0.0.1 works only when the device forwards that port. On a physical or
cloud device, pass a reachable HTTPS or LAN URL as the pulseUrl Want
parameter. The shell rejects credentials, fragments, invalid URLs, and
non-HTTP schemes.
Load server-rendered pages
Use HTTPS for pages loaded from a server. A phone’s localhost is the phone,
not the development machine. Use a reachable development hostname or an hdc
port reverse while testing locally. Do not compile a workstation IP address
into the application.
The AngularTS router remains the source of route intent. HarmonyOS mirrors
committed routes into NavPathStack; ArkUI must not invent a second application
history. External HTTP and HTTPS locations go through the system resolver.
Security
Every destination receives a random session token. The bridge accepts calls
only from that destination’s normalized origin and rejects duplicate request
identifiers, undeclared methods, malformed native trees, oversized messages,
and calls made after disposal. Capability errors sent to a page use stable
public codes rather than platform exception details.
Declare only permissions used by installed capabilities. Keep credentials and
private keys out of pages, bridge payloads, package metadata, and repository
files. See the
HarmonyOS security model
before adding a provider.
Validate a change
Portable checks do not require the HarmonyOS SDK:
make -C integrations/harmonyos check
Relevant pull requests and updates to master also run a pinned public
OpenHarmony API 23 compiler audit and package build. This catches ArkTS,
public API, and packaging regressions without treating that compatibility lane
as an API 26 release. It retains the exact commit-scoped packages for 14 days.
The API 23 build produces the proof and Pulse HAPs plus six compatible HARs;
credentials and maps remain API 26-only build gates because their Huawei kits
are not available in the public OpenHarmony SDK.
The release gate uses the official ArkTS compiler and Code Linter, builds every
HAR and HAP, and compiles an isolated application against the built HAR files:
make -C integrations/harmonyos release-check
Before publication, publication-check also requires complete Android/Harmony
contract parity and successful device or Huawei cloud evidence for the exact
commit. A green portable check is not a substitute for that evidence.
The retained release bundle contains the eight HARs, license, exact dependency
manifest, SPDX 2.3 SBOM, SLSA provenance statement, package manifest, and
SHA256SUMS. Evidence capture copies that exact directory. Publication rejects
missing metadata, stale package identity, altered HARs, altered metadata, and
unchecksummed files.
The native workflow accepts schema-v2 evidence only when the device run reports
no failed or skipped tests, names every contract from the current shared
catalogs exactly once, passes accessibility and performance checks, and
supplies JUnit output plus phone and tablet screenshots. This prevents stale
counts, an empty cloud job, or a single smoke test from being treated as
release evidence.
Publish and verify
OHPM requires an authenticated account, an encrypted private key, and an
interactive terminal. After the publication gate passes, submit all packages
in dependency order:
make -C integrations/harmonyos publish-release
OHPM reviews submissions before making them public. After approval, compile a
fresh application using exact versions from the registry:
make -C integrations/harmonyos verify-published
17.9 - Scala.js
Create an sbt Scala.js application with typed AngularTS models, controllers, a linked browser bundle, and the matching runtime.
The Scala artifact provides typed facades and idiomatic builders. Scala.js links
the application; @angular-wave/angular.ts remains the browser runtime. Keep
the Maven and npm versions equal.
Before you start
Install JDK 21, sbt, and Node.js. Start with project/plugins.sbt, build.sbt,
src/main/scala, and an HTML entry page.
Model tokens can be registered directly as controllers; no identity factory is
needed.
package todo
import angular.ts.*
import org.scalajs.dom.document
import scala.scalajs.js
final class TodoModel(var newTodo: String = "") extends js.Object
object App:
@main def main(): Unit =
val app = AngularTS.createModule("todo")
val model = AngularTS.token[Model[TodoModel]]("todoModel")
app
.model(model, () => new TodoModel())
.controller("TodoCtrl", model)
AngularTS.bootstrap(document.body, Seq(app.name))
Use classes extending js.Object for values that cross into AngularTS. Keep
ordinary Scala domain types behind that boundary.
Load the browser runtime
Load AngularTS before the linked Scala.js output:
<mainng-controller="TodoCtrl as ctrl"><inputng-model="ctrl.newTodo"/></main><scriptsrc="angular-ts.umd.js"></script><scriptsrc="main.js"></script>
Run sbt fastLinkJS while developing and sbt fullLinkJS for production. Point
the page at the generated main.js and serve it over HTTP.
Production practices
Prefer typed tokens, builders, and injection helpers over js.Dynamic.
Keep AngularTS models as the shared reactive boundary rather than mirroring
state into DOM-root scopes.
Replace Scala collections with js.Array only where the browser-facing model
requires JavaScript collection semantics.
Use fullLinkJS and test the linked output against the release runtime.
Keep the Scala.js artifact and AngularTS runtime on the same version.
Complete example
Use integrations/scala/examples/basic_app as a complete sbt todo project, or
browse all integration examples.
17.10 - Dart
Install angular_ts, create a typed Dart component, and load it with the matching AngularTS runtime.
The angular_ts package gives Dart code typed access to AngularTS modules,
injection, components, directives, services, and scopes.
Install
Add the facade and Web API package:
dart pub add angular_ts web
Compile the entry point:
dart compile js web/main.dart -o web/main.dart.js
Load @angular-wave/angular.ts before main.dart.js, keep both packages on the
same version, and serve the page over HTTP. The package
page contains a complete first component.
Guidance
Use typed tokens and inject0 through inject8 for dependencies.
Expose template-facing controllers with @JSExport() and
createJSInteropWrapper().
Keep JavaScript conversion at the edge of the application.
Test the compiled JavaScript in a real page.
Complete example
Use integrations/dart/example/basic_app as a complete todo project, or browse
all integration examples.
17.11 - Gleam
Install angular_ts, register an AngularTS module from Gleam, and compile the application to JavaScript.
The angular_ts package supplies typed Gleam functions for modules, dependency
injection, components, and application startup.
Install
gleam add angular_ts
Build with gleam build --target javascript. Load the matching
@angular-wave/angular.ts runtime before the generated application script and
serve the page over HTTP.
Guidance
Keep foreign JavaScript functions at the edge of the application.
Prefer typed package functions over untyped JavaScript values.
Keep the Gleam package and AngularTS runtime on the same version.
Test the generated JavaScript in a real page.
Complete example
Use integrations/gleam/examples/basic_app for a component, injected store, and
custom element, or browse all integration examples.
17.12 - AssemblyScript WebAssembly
Connect AssemblyScript logic to an AngularTS scope through the shared Wasm ABI.
AngularTS owns the page while AssemblyScript handles domain logic in a Wasm
module.
Add the binding
The binding is distributed as source. Import
integrations/wasm/assemblyscript/src/angular_ts.ts, compile the guest with
AssemblyScript’s incremental runtime and exported runtime helpers, then load the
result through app.wasm(...).
The host adapter binds the guest to a scope and releases it when that scope is
destroyed.
Guidance
Use typed fields for values exchanged with AngularTS.
Batch related writes into one update.
Copy transient data across the ABI instead of retaining host buffers.
Keep durable shared state in AngularTS models.
Complete example
Use integrations/wasm/assemblyscript/examples/todo, or browse all
integration examples.
17.13 - C WebAssembly
Connect C domain logic to an AngularTS scope with explicit memory ownership.
AngularTS owns the page while C handles domain logic in a Wasm module.
Add the binding
The binding is distributed as source. Add include/angular_ts_wasm.h and
src/angular_ts_wasm.c to the guest, include the typed field contract used by
the feature, and compile for wasm32-freestanding without an entry point.
Load the result through app.wasm(...) and bind it to the component scope from
the host adapter.
Guidance
Release every owned result and cancel watches before freeing callback state.
Connect C++ domain logic to an AngularTS scope with typed access and RAII cleanup.
AngularTS owns the page while C++ handles domain logic in a Wasm module.
Add the binding
The binding is distributed as source and is header-only. Add
integrations/wasm/cpp/include to the compiler search path, include
angular_ts/wasm.hpp, and compile for wasm32-wasi without an entry point.
Load the result through app.wasm(...) and bind it to the component scope from
the host adapter.
Guidance
Use the C++ scope, watch, and owned result wrappers instead of raw handles.
Connect C# domain logic to an AngularTS scope with .NET WebAssembly.
AngularTS owns the page while C# handles domain logic in a .NET Wasm module.
Add the binding
The binding is distributed as source. Install .NET 8 and the wasm-tools
workload, add a project reference to
integrations/wasm/csharp/src/AngularTs.Wasm.csproj, and publish the application
for browser-wasm.
Connect Go domain logic to an AngularTS scope with a js/wasm guest.
AngularTS owns the page while Go handles domain logic in a Wasm module.
Add the binding
The binding is distributed as source. Point go.mod at
integrations/wasm/go, generate the application’s AngularTS metadata, compile
for js/wasm, and place Go’s wasm_exec.js beside the output.
go generate ./...
GOOS=js GOARCH=wasm go build -o main.wasm .
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
Load the generated AngularTS bootstrap module from the page.
Guidance
Keep callback wrappers reachable while AngularTS owns them.
Release watches when their scope is destroyed.
Batch related scope updates.
Keep durable shared state in AngularTS models.
Complete example
Use integrations/wasm/go/examples/basic_app, or browse all integration
examples.
17.17 - Rust WebAssembly
Connect Rust domain logic and services to AngularTS with wasm-bindgen.
AngularTS owns the page while Rust handles domain logic and services in a Wasm
module.
Add the binding
The binding is distributed as source. Add integrations/wasm/rust/crates/angular-ts
as a path dependency, install the wasm32-unknown-unknown target and
wasm-bindgen-cli, then compile the guest and generate its Web loader.
Initialize the generated module before bootstrapping AngularTS.
Guidance
Use typed fields instead of raw paths.
Propagate contract and decode failures.
Keep watch guards for exactly as long as the subscription is needed.
Keep durable shared state in AngularTS models.
Complete examples
Use integrations/wasm/rust/examples/basic_app for application registration and
integrations/wasm/rust/examples/scope_bridge for direct scope exchange. See
all integration examples.
17.18 - Zig WebAssembly
Connect Zig domain logic to an AngularTS scope with typed fields and explicit ownership.
AngularTS owns the page while Zig handles domain logic in a Wasm module.
Add the binding
The binding is distributed as source. Add
integrations/wasm/zig/src/angular_ts.zig as an angular-ts module and compile
the guest for wasm32-freestanding without an entry point.
Load the result through app.wasm(...) and bind it to the component scope from
the host adapter.
Guidance
Call deinit() for owned decoded results and watch registrations.
Use allocator-aware decoding only when the application needs custom ownership.
Move existing AngularJS or earlier AngularTS applications to the current runtime through explicit, testable steps.
Start with Migrate from AngularJS.
Use the current API documentation as the source of truth rather than carrying
removed providers or aliases into new code.
Migrate one feature at a time and keep user-visible tests around that boundary.
18.1 - Migrate from AngularJS
Inventory an AngularJS application, replace removed platform abstractions, and move features to AngularTS incrementally.
Goal
Move an application without treating API familiarity as runtime equivalence.
Before you start
Add browser-level tests for the workflows that must survive migration. Record
all modules, providers, directives, template syntax, router states, and custom
decorators.
Migrate in boundaries
Start a small independent AngularTS application or island.
Move one leaf component and its direct service dependencies.
Replace removed abstractions with the documented native or module API.
Run behavior tests and inspect console errors.
Repeat for shared application code.
AngularTS preserves the HTML-first module, injection, controller, scope,
directive, filter, and two-way-binding mental models. Its runtime is different:
reactive proxies replace digest polling, native promises replace $q, native
timers replace timeout abstractions, and native DOM elements replace wrapper
objects.
Require explicit injection annotation during migration. Audit custom directives
for DOM-wrapper assumptions and code that manually starts digest work.
Update the runtime and generated language bindings with controlled, reversible steps.
Before changing the version
Record the current production version, read release and migration notes, and
make sure the existing application passes its checks. Archive generated
integration artifacts so regeneration differences are reviewable.
Upgrade
Update AngularTS and its lockfile entry.
Regenerate TypeScript declarations and every maintained language binding.
Run formatting, lint, type, documentation, and generated-file checks.
Run core and integration tests.
Build production assets and exercise critical browser paths.
Review bundle, CSP, storage, routing, and service-worker changes.
Keep the change reversible
Do not combine a framework upgrade with unrelated application rewrites. Deploy
an immutable artifact, retain the previous artifact, and understand whether
stored data or service-worker caches prevent a simple rollback.
Deprecations
Remove deprecated usage while replacement and old behavior can still be
compared. Do not wait for the release that removes the API.
Generated bindings must come from the authoritative TypeScript namespace. Never
patch stale parity output manually.