This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Documentation

Welcome to AngularTS documentation.

This section is a work in progress. Its content will be updated regularly but feel free to rely on AngularJS documentation in the meantime.


What is AngularTS?

AngularTS is buildless, type-safe and reactive JS framework for building stuctured 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

<section ng-app ng-cloak>
  <button class="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

1.1 - AngularTS: modern evolution of AngularJS

AngularTS preserves AngularJS’s HTML-first model and dependency injection while adding reactive change detection, TypeScript support, and native browser APIs.

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.

HTMX-inspired HTTP directives

ng-get, ng-post, ng-put, and ng-delete let you make HTTP requests declaratively from HTML attributes.

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

AngularTSReactVueAngular (v2+)
Build step requiredNoYesOptionalYes
CDN drop-inYesPartialYesNo
Two-way bindingYesNoYesYes
Dependency injectionYesNoNoYes
HTML-first templatesYesNo (JSX)YesYes
Bundle sizeSmall (UMD)MediumSmallLarge

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

Next steps

Installation

Add AngularTS to your project via CDN or npm.

Quickstart

Build a working counter and todo list in minutes.

1.2 - Install AngularTS in your project

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>
    <script src="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
  </head>
  <body>
    <div ng-app ng-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:

When loaded in a browser environment, 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:

const myModule: ng.NgModule = angular.module("myApp", []);

myModule.controller("MyController", function ($scope: ng.Scope) {
  $scope.message = "Hello, AngularTS";
});

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:

<div ng-app>
  {{ 1 + 1 }}
</div>

To connect a named module, set ng-app to the module name:

<div ng-app="myApp">
  <p ng-controller="GreetController">{{ greeting }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
<script>
  angular.module("myApp", []).controller("GreetController", 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.

Strict dependency injection

Add the strict-di attribute alongside ng-app to enable strict mode, which requires explicit dependency annotations and rejects minified code that relies on function parameter names:

  ...
</div>

Manual bootstrap with angular.bootstrap()

For full control over when and how your application starts, call angular.bootstrap() directly instead of using ng-app:

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
  </head>
  <body>
    <div id="app">
      <p ng-controller="WelcomeController">{{ greeting }}</p>
    </div>

    <script>
      angular
        .module("demo", [])
        .controller("WelcomeController", function ($scope) {
          $scope.greeting = "Welcome!";
        });

      angular.bootstrap(document.getElementById("app"), ["demo"]);
    </script>
  </body>
</html>

angular.bootstrap() accepts three arguments:

ArgumentTypeDescription
elementstring | HTMLElement | HTMLDocumentThe root element to bootstrap the application on
modulesArray<string | any>Module names or inline config functions to load
config{ strictDi: boolean }Optional configuration; defaults to { strictDi: false }

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.

Next steps

Quickstart

Build a working app with a counter and todo list.

Modules

Learn how the AngularTS module system organizes your application.

1.3 - 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>
    <meta charset="UTF-8" />
    <title>My AngularTS App</title>
    <script src="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.module() with a name and an empty dependency array:

const app = angular.module("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:

<body ng-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.

const app = angular.module("myApp", []);

app.controller("CounterController", function ($scope) {
  $scope.count = 0;

  $scope.increment = function () {
    $scope.count++;
  };

  $scope.decrement = function () {
    if ($scope.count > 0) {
      $scope.count--;
    }
  };
});

Attach the controller to a DOM element with ng-controller:

<body ng-app="myApp">
  <div ng-controller="CounterController">
    <p>Count: {{ count }}</p>
    <button ng-click="increment()">+</button>
    <button ng-click="decrement()">-</button>
  </div>
</body>

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>
    <script src="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
  </head>
  <body>
    <section ng-app ng-cloak>
      <button ng-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

The following example demonstrates a more complete application using a named module, a controller, two-way binding with ng-model, list rendering with ng-repeat, and conditional display with ng-show. Everything runs from a single HTML file.

<html>
  <head>
    <meta charset="UTF-8" />
    <title>Todo List</title>
    <script src="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular-ts.umd.min.js"></script>
    <style>
      .done { text-decoration: line-through; color: #999; }
    </style>
  </head>
  <body ng-app="todoApp">
    <div ng-controller="TodoController">
      <h1>Todo List</h1>

      <!-- Add new item -->
      <form ng-submit="addTodo()">
        <input
          ng-model="newTodo"
          placeholder="What needs doing?"
          required
        />
        <button type="submit">Add</button>
      </form>

      <!-- Remaining count -->
      <p ng-show="todos.length > 0">
        {{ remaining() }} of {{ todos.length }} remaining
      </p>

      <!-- List of todos -->
      <ul>
        <li ng-repeat="todo in todos">
          <input type="checkbox" ng-model="todo.done" />
          <span ng-class="{ done: todo.done }">{{ todo.text }}</span>
          <button ng-click="removeTodo($index)">Remove</button>
        </li>
      </ul>

      <!-- Clear completed -->
      <button ng-click="clearDone()" ng-show="todos.length > remaining()">
        Clear completed
      </button>
    </div>

    <script>
      angular.module("todoApp", []).controller("TodoController", function ($scope) {
        $scope.todos = [
          { text: "Learn AngularTS", done: true },
          { text: "Build something", done: false },
        ];

        $scope.newTodo = "";

        $scope.addTodo = function () {
          if ($scope.newTodo.trim()) {
            $scope.todos.push({ text: $scope.newTodo.trim(), done: false });
            $scope.newTodo = "";
          }
        };

        $scope.removeTodo = function (index) {
          $scope.todos.splice(index, 1);
        };

        $scope.remaining = function () {
          return $scope.todos.filter(function (t) {
            return !t.done;
          }).length;
        };

        $scope.clearDone = function () {
          $scope.todos = $scope.todos.filter(function (t) {
            return !t.done;
          });
        };
      });
    </script>
  </body>
</html>

This example uses:

DirectivePurpose
ng-modelTwo-way binding between the input value and $scope.newTodo
ng-submitCalls addTodo() when the form is submitted
ng-repeatRenders a <li> for each item in $scope.todos
ng-classAdds the done CSS class when todo.done is true
ng-showShows the element only when the expression is truthy
ng-clickCalls 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:


interface TodoItem {
  text: string;
  done: boolean;
}

interface TodoScope extends ng.Scope {
  todos: TodoItem[];
  newTodo: string;
  addTodo(): void;
  remaining(): number;
}

angular.module("todoApp", []).controller(
  "TodoController",
  function ($scope: TodoScope) {
    $scope.todos = [{ text: "Learn AngularTS", done: false }];
    $scope.newTodo = "";

    $scope.addTodo = function () {
      if ($scope.newTodo.trim()) {
        $scope.todos.push({ text: $scope.newTodo.trim(), done: false });
        $scope.newTodo = "";
      }
    };

    $scope.remaining = function () {
      return $scope.todos.filter((t) => !t.done).length;
    };
  }
);

Note: TypeScript declarations ship with the package under @types/. No separate @types/angular-wave__angular.ts package is needed.

Next steps

Core concepts

Understand how modules, dependency injection, and scopes work together.

Directives reference

Browse all 50+ built-in directives with examples.

Routing

Add state-based routing with nested views and URL matching.

Services

Use built-in services for HTTP, WebSockets, REST, and more.

2 - Core Concepts

2.1 - Angular Runtime

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.

Exact runtime contracts live in TypeDoc:

Create Modules

Use angular.module() to create or retrieve modules. Passing a dependency array creates a module; passing only the name retrieves one.

const app = angular.module("myApp", ["ng"]);

app.service("UserService", UserService);

app.config(($locationProvider) => {
  $locationProvider.hashPrefix("!");
});

const existing = angular.module("myApp");

Calling angular.module("name") without first creating that module throws the same nomod error as AngularJS.

Bootstrap Manually

angular.bootstrap() starts an application on a DOM element. It is the programmatic alternative to ng-app.

document.addEventListener("DOMContentLoaded", () => {
  angular.bootstrap(document.body, ["myApp"], {
    strictDi: true,
  });
});

The built-in ng module is prepended automatically. Use strictDi when code must be safe for minification.

Each element can host only one application. Bootstrapping an element that already has an injector throws ng:btstrpd.

Auto-Bootstrap

angular.init() scans an element or document for ng-app roots. The first root uses the current Angular instance. Additional roots are bootstrapped as sub-applications and stored in angular.subapps.

window.addEventListener("DOMContentLoaded", () => {
  angular.init(document);
});

You usually do not need to call this yourself for static pages because AngularTS runs auto-bootstrap when the script loads. Call it manually when dynamically adding new ng-app roots.

Create A Standalone Injector

Use angular.injector() when tests or non-DOM code need services without compiling an application root.

const injector = angular.injector(["ng", "myApp"], true);
const $http = injector.get("$http");

Publish A Standalone Custom Element

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.

import { defineAngularElement } from "@angular-wave/angular.ts/runtime/web-component";
import { ngClickDirective } from "@angular-wave/angular.ts/directives/events";

defineAngularElement("billing-summary", {
  ngModule: {
    directives: {
      ngClick: ngClickDirective,
    },
    services: {
      billingApi: BillingApi,
    },
  },
  component: {
    shadow: true,
    inputs: {
      accountId: String,
    },
    template: `
      <button ng-click="refresh()">
        {{ accountId }} / {{ status }}
      </button>
    `,
    connected({ dispatch, injector, scope }) {
      const api = injector.get("billingApi");

      scope.status = "ready";
      scope.refresh = () => {
        scope.status = api.status(scope.accountId);
        dispatch("billing-refresh", { status: scope.status });
      };
    },
  },
});

Consumers only need the bundled module and the native element:

<script type="module" src="/widgets/billing-summary.js"></script>
<billing-summary account-id="acct_123"></billing-summary>

Inputs are DOM attributes or properties. Outputs should be CustomEvents dispatched with the dispatch() helper from the component context.

Bridge External Code

angular.emit() and angular.call() evaluate expressions against an injectable service or a named scope. The input format is "<target>.<expression>".

angular.emit("UserService.logout()");

const count = await angular.call("cartScope.items.length");

Use these helpers for small integration boundaries 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.

$scope.$scopename = "dashboard";

const scope = angular.getScopeByName("dashboard");
scope?.refresh();

Inspect Compiled Elements

The runtime exposes DOM cache helpers for integration and debugging:

const el = document.querySelector("[ng-controller='MyCtrl']") as Element;

const ctrl = angular.getController(el);
const scope = angular.getScope(el);
const injector = angular.getInjector(el);

These helpers read metadata attached during compilation and bootstrap.

Use Injection Tokens

angular.$t exposes public injection token strings as a typed object. Prefer it when writing $inject arrays in TypeScript.

MyService.$inject = [angular.$t.$http, angular.$t.$rootScope];

2.2 - 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:

set(target, property, value, proxy): boolean {
  target[property] = createScope(value, this); // recursively proxy nested objects

  if (oldValue !== value) {
    const listeners = this._watchers.get(property); // O(1) Map lookup
    if (listeners) {
      this._scheduleListener(listeners); // queue a microtask
    }
  }

  return true;
}

The key points:

  1. 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.
  2. 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.
  3. 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
  };
}]);
  <button ng-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 mechanismDirty-checking digest loopES6 Proxy set trap
Watcher lookup costO(n) — all watchers checkedO(1) — direct Map lookup by property name
TriggerManual digest entry required for async codeAutomatic — Proxy intercepts every assignment
Update granularityAll watchers, all the timeOnly bindings for the changed property
Nested objectsShallow by default; $watchCollection needed for deepDeep — nested objects are automatically proxied
Async codeMust manually enter change detectionNo 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 {
  const scheduler = 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:

class BoardGridController {
  $afterRender() {
    this.realignShips();
  }
}

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:

import { afterRender } from '@angular-wave/angular.ts';

afterRender(() => {
  this.realignShips();
});

If font metrics are required, opt in per callback:

afterRender(() => {
  this.realignLabels();
}, { fonts: true });

What objects are tracked

Not all objects are proxied. The isNonScope function explicitly excludes:

  • Browser built-ins: Window, Document, Element, Node, Event, Promise, Map, Set, WeakMap, Date, RegExp, typed arrays, Blob, File, URL, and others.
  • 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;

  readonly apiBase = 'https://api.example.com';
}

// Or per-instance:
$scope.rawData = Object.assign(new SomeClass(), { $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.

2.3 - 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 subsequent get returns the same instance.


// Retrieve a service
const $http = injector.get('$http');

// Check existence without throwing
if (injector.has('myOptionalService')) {
  const svc = injector.get('myOptionalService');
}

// Invoke a function with injection
injector.invoke(['$rootScope', '$http', function ($rootScope, $http) {
  // both dependencies are injected automatically
}]);

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.

angular.module('myApp', [])
  .controller('UserCtrl', [
    '$scope', '$http', 'userService',
    function ($scope, $http, userService) {
      $scope.users = [];

      userService.getAll().then(({ data }) => {
        $scope.users = data;
      });
    },
  ]);

$inject property

class UserCtrl {
  static $inject = ['$scope', '$http', 'userService'];

  constructor(
    private $scope: ng.Scope,
    private $http: ng.HttpService,
    private userService: UserService,
  ) {
    $scope.users = [];
    userService.getAll().then(({ data }) => {
      $scope.users = data;
    });
  }
}

angular.module('myApp', []).controller('UserCtrl', UserCtrl);

Note: The $inject static property on a class is the TypeScript-idiomatic form of array annotation. When present, AngularTS reads it in preference to inspecting function argument names.

Strict DI mode

Passing { strictDi: true } to angular.bootstrap() (or adding the strict-di attribute to the ng-app element) disables implicit annotation. Every injectable must carry explicit annotations. This catches minification bugs at development time.

<div ng-app="myApp" strict-di></div>

Under strict DI, any function that reaches the injector without annotations throws immediately — even if the un-annotated function would have worked in development.

Injectable types

value

A fixed JavaScript value. Registered with $provide.value(). 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.

  private prefix = '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) => {
    const prefix = this.prefix;
    return {
      greet(name: string) {
        $log.info(`${prefix}, ${name}!`);
      },
    };
  }];
}

angular.module('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.

Decorating existing services

A decorator intercepts an existing service and can replace, wrap, or augment it. The original instance is available as $delegate.

  .config(['$provide', function ($provide) {
    $provide.decorator('$log', ['$delegate', function ($delegate) {
      const originalInfo = $delegate.info.bind($delegate);

      $delegate.info = function (...args: any[]) {
        originalInfo('[DECORATED]', ...args);
      };

      return $delegate;
    }]);
  }]);

Injecting into different artifact types

Controllers

Controllers receive $scope as their first dependency by convention, followed by any other services. Use array annotation or $inject.

app.controller('DashboardCtrl', [
  '$scope', '$http',
  function ($scope, $http) { /* ... */ },
]);

Directives

Directive factories are injected like any other factory. The returned directive definition object is not itself injected.

app.directive('myWidget', ['$http', 'dataService', function ($http, dataService) {
  return {
    restrict: 'E',
    link(scope) {
      dataService.load().then(data => { scope.data = data; });
    },
  };
}]);

Filters

Filter factories are injectable. The returned filter function itself is not — it receives only the value and optional arguments from the template.

app.filter('truncate', ['$log', function ($log) {
  return function (text: string, limit = 80) {
    $log.info('truncating');
    return text.length > limit ? text.slice(0, limit) + '…' : text;
  };
}]);

Config and run blocks

Config blocks can inject providers and constants only. Run blocks have access to all services.

// Config — providers only
app.config(['$httpProvider', function ($httpProvider) {
  $httpProvider.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

MethodDescription
$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.

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

Creating and retrieving modules

Use angular.module() to both create and look up modules. The presence of the second argument — the requires array — is what distinguishes creation from retrieval.

TypeScript

// Create a new module with no dependencies
const app = angular.module('myApp', []);

// Retrieve an already-registered module
const same = angular.module('myApp');

JavaScript

// Create a new module
var app = angular.module('myApp', []);

// Retrieve it elsewhere (e.g., in another file)
var same = angular.module('myApp');

Warning: Calling angular.module('myApp', []) a second time replaces the existing module. Always pass the requires array only once — at the point of creation — and omit it everywhere else.

Module registration methods

The NgModule instance returned by angular.module() 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(fn)

Runs during the config phase, before any services are instantiated. Only providers and constants can be injected.

.run(fn)

Runs after the injector has been created. Services are available. Use this for one-time initialization logic.

.state(definition) / .state(name, definition)

Registers a router state through $stateProvider during the config phase. This is equivalent to using .config(['$stateProvider', ...]), but keeps route declarations in the same fluent module chain as components and services. The method records only a provider-token invocation; it does not import router implementation code into custom runtimes unless that runtime actually includes the router provider and loads a module that calls .state().

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.

const utilModule = angular.module('util', []);

utilModule.constant('API_BASE', 'https://api.example.com');

utilModule.factory('logger', ['$log', function ($log) {
  return {
    info(msg: string) { $log.info('[app]', msg); },
    warn(msg: string) { $log.warn('[app]', msg); },
  };
}]);
class UserService {
  static $inject = ['$http', 'API_BASE', 'logger'];

  constructor(
    private $http: ng.HttpService,
    private base: string,
    private logger: { info: (m: string) => void },
  ) {}

  getAll() {
    this.logger.info('Fetching users');
    return this.$http.get(`${this.base}/users`);
  }
}

const app = angular.module('myApp', ['ng', 'util']);

app.service('userService', UserService);

app.controller('UserListController', [
  '$scope', 'userService',
  function ($scope, userService) {
    $scope.users = [];

    userService.getAll().then(({ data }) => {
      $scope.users = data;
    });
  },
]);

app.state('users', {
  url: '/users',
  template: '<user-list></user-list>',
});

app.config(['$locationProvider', function ($locationProvider) {
  $locationProvider.html5Mode(true);
}]);

app.run(['logger', function (logger) {
  logger.info('Application started');
}]);

Module dependencies

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:

TokenDescription
$rootScopeThe root of the scope hierarchy
$compileTemplate compiler
$httpHTTP client
$interpolateTemplate interpolation
$parseExpression parser
$filterFilter registry
$animateAnimation support
$locationURL management
$machineReactive mode machines
$sceStrict contextual escaping
$stateRouter state service
$eventBusPub/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:

// Register an injectable reactive mode machine
app.machine('sessionMachine', {
  initial: 'setup',
  data: {},
  transitions: {},
});

// Register machine/workflow config from DI-backed factories
app.value('settings', {
  sessionMode: 'setup',
});

function machineConfig(settings) {
  return {
    initial: settings.sessionMode,
    data: { roomId: '' },
    transitions: {
      setup: {
        join(data, message) {
          data.roomId = message.roomId;

          return 'waiting';
        },
      },
    },
  };
}

machineConfig.$inject = ['settings'];

app.machine('sessionMachine', machineConfig);

function workflowConfig(settings) {
  return {
    id: 'onboarding',
    initial: 'idle',
    data: { sessionMode: settings.sessionMode },
    transitions: {
      idle: {
        start(data) {
          data.sessionMode = 'running';

          return 'running';
        },
      },
    },
  };
}

workflowConfig.$inject = ['settings'];

app.workflow('onboardingWorkflow', workflowConfig);

app.wasm('mathLib', '/wasm/math.wasm', {});

// Register a Web Worker connection
app.worker('backgroundWorker', '/workers/bg.js');

// Register a REST resource backed by $rest
app.rest('Post', '/api/posts', Post);

// Register a persisted service (session, local, or cookie storage)
app.store('prefs', UserPreferences, 'local');

// Register a pre-configured SSE stream
app.sse('notifications', '/events/stream');

// Register a pre-configured WebSocket connection
app.websocket('chat', 'wss://chat.example.com/ws');

These methods all push entries onto the module’s internal invoke queue. They are resolved by the injector during bootstrap exactly like any other $provide registration.

2.5 - 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 ...
  const listeners = this._watchers.get(property);
  if (listeners) {
    this._scheduleListener(listeners); // queues a microtask flush
  }
  return true;
}

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:

const child = $scope.$new();

// Isolate child — does not inherit watchable properties
const isolate = $scope.$newIsolate();

// Transcluded child — linked to an outer parent scope
const transcluded = $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.

$rootScope
├── MainCtrl scope
│   ├── SidebarCtrl scope
│   └── ContentCtrl scope
│       ├── ng-repeat item scope (item 0)
│       ├── ng-repeat item scope (item 1)
│       └── ng-repeat item scope (item 2)
└── NavCtrl scope

You can search for a named scope from anywhere in the tree:

$scope.$scopename = 'userPanel';

// Find it from $rootScope
const panel = $rootScope.$searchByName('userPanel');

// Or via the angular instance
const panel2 = 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.

const deregister = $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:

  1. Broadcasts $destroy downward to all children.
  2. Removes all watcher registrations for this scope’s ID from the shared _watchers map.
  3. Clears all $on listeners.
  4. Removes itself from the parent’s _children array.
  5. Sets _destroyed = true and nulls internal references on the next microtask.
const tempScope = $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:

  <button ng-click="increment()">+</button>
  <span>{{ count }}</span>
</div>
  $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

MethodDescription
$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.

2.6 - 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:

<p>Hello, {{ user.name }}!</p>

<!-- Method call -->
<p>Total: {{ cart.getTotal() }}</p>

<!-- Arithmetic -->
<p>Items: {{ items.length }}</p>

<!-- Ternary -->
<p>Status: {{ isActive ? 'Online' : 'Offline' }}</p>

<!-- String concatenation -->
<p>{{ firstName + ' ' + lastName }}</p>

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 -->
<li ng-repeat="item in items | orderBy:'name'">{{ item.name }}</li>

<!-- Limit a list and filter it by a search term -->
<li ng-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 subsequent 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 -->
<li ng-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
    const greetFn = $interpolate('Hello, {{ name }}! You have {{ count }} messages.');

    $scope.name = 'Alice';
    $scope.count = 5;

    // Evaluate the template against a context object
    const result = 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 via $interpolateProvider in a config block:

  $interpolateProvider.startSymbol = '{[';
  $interpolateProvider.endSymbol = ']}';
}]);

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
    const getScore = $parse('user.score');

    console.log(getScore($scope));  // 42

    // Expressions with assign support two-way binding
    const nameParse = $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
    const getScore2 = $parse('user.score');
    console.log(getScore === getScore2); // true
  },
]);

Compiled expression properties

PropertyTypeDescription
_constantbooleantrue if the expression is a literal constant
_literalbooleantrue if the expression is a simple literal
_assignfunction?Present for l-value expressions; assigns a value to the context
_inputsany[]?Sub-expressions tracked for change detection
_decoratedNodeBodyNodeThe annotated AST for this expression

Security and SCE

When binding HTML content directly into the DOM, AngularTS enforces Strict Contextual Escaping (SCE). Plain interpolation always produces text, never HTML. To bind HTML you must use ng-bind-html with a trusted value:

<p>{{ userComment }}</p>

<!-- To render HTML, mark it as trusted first -->
<p ng-bind-html="trustedHtml"></p>
  '$scope', '$sce',
  function ($scope, $sce) {
    // Explicitly trust HTML from a known-safe source
    $scope.trustedHtml = $sce.trustAsHtml('<strong>Bold</strong> text');
  },
]);

Warning: Never call $sce.trustAsHtml() on user-provided input. Only use it on HTML that your application controls — such as server-rendered content that has already been sanitized.

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 - Directive Reference

3.1 - ng-repeat

Complete reference for ng-repeat: iterating arrays and objects, track by, special scope variables, filters, multi-element repeat, 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

<li ng-repeat="item in items">{{ item.name }}</li>

<!-- Iterate an object (key-value pairs) -->
<tr ng-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 track by expression — with explicit tracking
  • 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.

$first

  • Type: boolean

true for the first item ($index === 0).

$last

  • Type: boolean

true for the last item.

$middle

  • Type: boolean

true for items that are neither first nor last.

$even

  • Type: boolean

true when $index is even.

$odd

  • Type: boolean

true when $index is odd.

    ng-class="{ first: $first, last: $last, odd: $odd }">
  {{ $index + 1 }}. {{ item.name }}
</li>

track by

By default, ng-repeat tracks items by object identity. When the collection changes, it destroys and recreates DOM nodes for items that aren’t the same object reference. track by lets you specify a stable key, dramatically improving performance when data is re-fetched from a server.

<li ng-repeat="user in users track by user.id">{{ user.name }}</li>

<!-- Track by $index — useful for arrays of primitives -->
<li ng-repeat="tag in tags track by $index">{{ tag }}</li>

Warning: Do not use track by $index when items can be reordered or deleted — it causes incorrect DOM reuse. Use a stable unique ID instead.

Filtering and sorting

Apply filters inline in the ng-repeat expression:

<li ng-repeat="item in items | filter:searchText">{{ item.name }}</li>

<!-- Filter by object (matches any field) -->
<li ng-repeat="item in items | filter:{ category: 'books' }">{{ item.name }}</li>

<!-- Sort ascending -->
<li ng-repeat="item in items | orderBy:'name'">{{ item.name }}</li>

<!-- Sort descending -->
<li ng-repeat="item in items | orderBy:'-price'">{{ item.name }}</li>

<!-- Limit to first 10 -->
<li ng-repeat="item in items | limitTo:10">{{ item.name }}</li>

<!-- Chain filters -->
<li ng-repeat="item in items | filter:query | orderBy:'name' | limitTo:20">
  {{ item.name }}
</li>

Multi-element repeat

Use ng-repeat-start and ng-repeat-end to repeat a block of sibling elements (not just a single element):

<dd ng-repeat-end>{{ def.description }}</dd>

This produces alternating <dt> / <dd> pairs, one for each item in definitions.

Animating ng-repeat

ng-repeat integrates with $animate. Added items receive .ng-enter, removed items .ng-leave, and moved items .ng-move CSS classes:

  transition: opacity 0.3s;
  opacity: 0;
}
.my-list li.ng-enter-active {
  opacity: 1;
}
.my-list li.ng-leave {
  transition: opacity 0.3s;
  opacity: 1;
}
.my-list li.ng-leave-active {
  opacity: 0;
}

Performance guidance

  • Always use track by with a unique ID for large lists.
  • 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.

3.2 - ARIA directives

This is covered by the ARIA provider, which documents all supported aria-* behavior and related configuration.

Use the provider page if you need details about global ARIA support and defaults.

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

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.

  <input name="email" ng-model="user.email" required type="email">
  <button type="submit" ng-disabled="registrationForm.$invalid">Register</button>
</form>

The name attribute (here registrationForm) is the scope property under which the controller is available.

FormController properties

$valid

  • Type: boolean

true when all child controls are valid.

$invalid

  • Type: boolean

true when any child control is invalid.

$pristine

  • Type: boolean

true until any control has been changed.

$dirty

  • Type: boolean

true after any control has been changed.

$submitted

  • Type: boolean

true after the form has been submitted at least once.

$pending

  • Type: object

Map of pending async validators. Empty object when none are pending.

$error

  • Type: object

Map of validator names to arrays of controls that are failing that validator. E.g., { required: [ctrl1], email: [ctrl2] }.

FormController methods

$setPristine()

  • Type: function

Resets the form and all controls to pristine state. Useful after a successful save.

$setUntouched()

  • Type: function

Resets the $touched state of all controls.

$setSubmitted()

  • Type: function

Marks the form as submitted programmatically.

Built-in validators

Apply validators as attributes on <input>, <textarea>, or <select> elements. When validation fails, the validator’s name appears in $error.

required

  • Type: none

The field must have a non-empty value. Alias: ng-required="expression" for conditional requirement.

minlength

  • Type: number

Minimum number of characters. Alias: ng-minlength="expression".

maxlength

  • Type: number

Maximum number of characters. Alias: ng-maxlength="expression".

pattern

  • Type: string

JavaScript regex pattern the value must match. Alias: ng-pattern="expression".

min

  • Type: number

Minimum value for type="number" or type="date" inputs.

max

  • Type: number

Maximum value for type="number" or type="date" inputs.

type

  • Type: string

HTML input type. AngularTS adds validators for email, url, number, date, time, week, month, datetime-local.

  <input name="username"
         ng-model="user.username"
         required
         minlength="3"
         maxlength="20"
         pattern="[a-zA-Z0-9_]+">

  <input name="age"
         ng-model="user.age"
         type="number"
         ng-min="18"
         ng-max="120">
</form>

ng-messages / ng-message

Display validation error messages tied to a field’s $error object.

  <div ng-message="required">Email is required.</div>
  <div ng-message="email">Please enter a valid email address.</div>
  <div ng-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

  <div>
    <label>Email</label>
    <input name="email" type="email" ng-model="user.email" required>
    <div ng-messages="signupForm.email.$error" ng-show="signupForm.email.$dirty">
      <div ng-message="required">Required.</div>
      <div ng-message="email">Invalid email format.</div>
    </div>
  </div>

  <div>
    <label>Password</label>
    <input name="password" type="password" ng-model="user.password"
           required minlength="8">
    <div ng-messages="signupForm.password.$error" ng-show="signupForm.password.$dirty">
      <div ng-message="required">Required.</div>
      <div ng-message="minlength">Must be at least 8 characters.</div>
    </div>
  </div>

  <button type="submit" ng-disabled="signupForm.$invalid">Sign Up</button>

</form>

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

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

Parameters


ng-bind

  • Type: Expression

  • Restrict: A

  • Element: ANY

  • Priority: 0

  • Description: Expression to evaluate and modify textContent property.

  • Example:

    <div ng-bind="name"></div>
    

Directive modifiers

data-lazy

  • Type: N/A

  • Description: Apply expression once the bound model changes.

  • Example:

    <div ng-bind="name" data-lazy></div>
    <!-- or -->
    <div ng-bind="name" lazy></div>
    

Demo

<section ng-app>
  <!-- Eager bind -->
  <label>Enter name: <input type="text" ng-model="name" /></label><br />
  Hello <span ng-bind="name">I am never displayed</span>!

  <!-- Lazy bind with short-hand `lazy` -->
  <button ng-click="name1 = name">Sync</button>
  <span ng-bind="name1" lazy>I am server content</span>!
</section>

Hello I am never displayed! I am server content!

3.6 - ng-bind-html

Bind trusted HTML content to an element.

Sets the innerHTML of an element to a trusted HTML string. The value must be explicitly trusted through the $sce service to prevent XSS.

  $scope.trustedHtml = $sce.trustAsHtml('<strong>Hello</strong> <em>world</em>');
});

ng-bind-html

  • Type: expression
  • Required: yes

Expression that evaluates to a value marked as trusted HTML via $sce.trustAsHtml(). Untrusted strings will throw an SCE error.

Warning: Never pass user-supplied content directly to $sce.trustAsHtml(). Only trust HTML you control or have saniti

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

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.

3.8 - ng-blur

Handler for blur event

Description

The ng-blur directive allows you to specify custom behavior when an element loses focus.

Parameters


ng-blur

  • Type: Expression

  • Description: Expression to evaluate upon blur event. FocusEvent object is available as $event.

  • Example:

    <div ng-blur="$ctrl.handleBlur($event)"></div>
    

Demo

<section ng-app>
  <input
    type="text"
    ng-blur="count++"
    ng-init="count = 0"
    placeholder="Click or tab away from me"
  />
  Lost focus {{ count }} times
</section>
Lost focus {{ count }} times

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

Demo

<div ng-app>
  <!-- With empty node -->
  <div ng-channel="epoch"></div>

  <!-- With template -->
  <div ng-channel="user">Hello {{ user.firstName }} {{ user.lastName }}</div>
</div>

<button
  class="btn btn-dark"
  onclick="angular.$eventBus.publish('epoch', Date.now())"
>
  Publish epoch
</button>

<button
  class="btn btn-dark"
  onclick="
    angular.$eventBus.publish('user', {
      user: {
        firstName: 'John',
        lastName: 'Smith',
      },
    })
  "
>
  Publish name
</button>
Hello {{ user.firstName }} {{ user.lastName }}

3.10 - ng-class

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:

<button ng-class="tile.classes"></button>
/**
 * @param {{ state: string, sunk: boolean }} tile
 * @returns {ng.ClassMap}
 */
function tileClasses(tile) {
  return {
    placed: tile.state === 'unit',
    hit: tile.state === 'hit',
    miss: tile.state === 'miss',
    sunk: tile.sunk,
  };
}

When the expression changes:

  • Previously added classes are removed.
  • New classes are added.
  • Duplicate classes are avoided.

Important: Avoid using interpolation ({{ ... }}) in the value of the class attribute together with ng-class. See interpolation known issues for details.

Animations

If data-animate attribute is present, the following animations will be applied to the element:

AnimationOccurs
add-classBefore the class is applied to the element
remove-classBefore the class is removed from the element
set-classBefore classes are simultaneously added and removed

ng-class supports standard CSS3 transitions/animations even if they don’t follow $animate service naming conventions.

Parameters


ng-class

  • Type: string | object | array

  • Type alias: ng.ClassValue

  • Description: An expression whose result determines the CSS classes to apply.

  • Example:

    <div ng-class="{ active: isActive, disabled: isDisabled }"></div>
    

Demo

<style>
  .strike {
    text-decoration: line-through;
  }
  .bold {
    font-weight: bold !important;
  }
  .red {
    color: red;
  }
  .has-error {
    color: red;
    background-color: yellow;
  }
  .orange {
    color: orange;
  }
</style>
<section ng-app>
  <p ng-class="{strike: deleted, bold: important, 'has-error': error}">
    Map Syntax Example
  </p>
  <label>
    <input type="checkbox" ng-model="deleted" />deleted (apply "strike" class)
  </label>
  <br />
  <label>
    <input type="checkbox" ng-model="important" />important (apply "bold" class)
  </label>
  <br />
  <label>
    <input type="checkbox" ng-model="error" />error (apply "has-error" class)
  </label>
  <hr />
  <p ng-class="style">Using String Syntax</p>
  <input
    type="text"
    ng-model="style"
    placeholder="Type: bold strike red"
    aria-label="Type: bold strike red"
  />
  <hr />
  <p ng-class="[style1, style2, style3]">Using Array Syntax</p>
  <input
    ng-model="style1"
    placeholder="Type: bold, strike or red"
    aria-label="Type: bold, strike or red"
  /><br />
  <input
    ng-model="style2"
    placeholder="Type: bold, strike or red"
    aria-label="Type: bold, strike or red 2"
  /><br />
  <input
    ng-model="style3"
    placeholder="Type: bold, strike or red"
    aria-label="Type: bold, strike or red 3"
  /><br />
  <hr />
  <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
  <input
    ng-model="style4"
    placeholder="Type: bold, strike"
    aria-label="Type: bold, strike"
  />
  <br />
  <label
    ><input type="checkbox" ng-model="warning" /> warning (apply "orange"
    class)</label
  >
</section>

Map Syntax Example




Using String Syntax


Using Array Syntax





Using Array and Map Syntax



3.11 - ng-click

Handler for click event

Description

The ng-click directive allows you to specify custom behavior when an element is clicked.

Parameters


ng-click

  • Type: Expression

  • Restrict: A

  • Element: ANY

  • Priority: 0

  • Description: Expression to evaluate upon click event. PointerEvent object is available as $event.

  • Example:

    <div ng-click="$ctrl.greet($event)"></div>
    

    Event policy attributes can be added to the same element:

    <button ng-click="$ctrl.submit($event)" data-event-prevent data-event-once>
      Submit
    </button>
    

    data-event-prevent, data-event-stop, data-event-capture, data-event-once, and data-event-passive apply to every event directive on the same element.


Demo

<section ng-app>
  <button class="btn btn-dark" ng-init="count = 0" ng-click="count++">
    Increment
  </button>
  <span> count: {{count}} </span>
</section>
count: {{count}}

3.12 - ng-cloak

Hide interpolated templates

Description

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:

@charset "UTF-8";

[ng-cloak],
[data-ng-cloak],
.ng-cloak,
.ng-hide:not(.ng-hide-animate) {
  display: none !important;
}

.ng-animate-shim {
  visibility: hidden;
}

.ng-anchor {
  position: absolute;
}

CSS styles are available in npm distribution:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@angular-wave/angular.ts/dist/angular.css"
/>

Example

<style>
  @charset "UTF-8";
  .ng-cloak,
  .ng-hide:not(.ng-hide-animate),
  [data-ng-cloak],
  [ng-cloak] {
    display: none !important;
  }
  .ng-animate-shim {
    visibility: hidden;
  }
  .ng-anchor {
    position: absolute;
  }
</style>
<section ng-app ng-cloak>These tags are invisible {{ hello }}</section>

Demo

These tags are invisible {{ hello }}

3.13 - ng-copy

Handler for copy event

Description

The ng-copy directive allows you to specify custom behavior when an element is copied.

Parameters


ng-copy

  • Type: Expression

  • Description: Expression to evaluate upon copy event. ClipboardEvent object is available as $event.

  • Example:

    <div contenteditable="true" ng-copy="$ctrl.greet($event)">Content</div>
    

Demo

<section ng-app>
  <div class="border p-2" ng-copy="copied = true" contenteditable="true">
    Copy text from this box via Ctrl-C
  </div>
  {{ copied }}
</section>
Copy text from this box via Ctrl-C
{{ copied }}

3.14 - ng-cut

Handler for cut event

Description

The ng-cut directive allows you to specify custom behavior when an element is cut.

Parameters


ng-cut

  • Type: Expression

  • Description: Expression to evaluate upon cut event. ClipboardEvent object is available as $event.

  • Example:

    <div contenteditable="true" ng-cut="$ctrl.onCut($event)">
      Cuttable content
    </div>
    

Demo

<section ng-app>
  <div class="border p-2" ng-cut="cut = true" contenteditable="true">
    Cut text from this box via Ctrl-X
  </div>
  {{ cut }}
</section>
Cut text from this box via Ctrl-X
{{ cut }}


3.15 - ng-dblclick

Handler for dblclick event

Description

The ng-dblclick directive allows you to specify custom behavior when an element is double clicked.

Parameters


ng-dblclick

  • Type: Expression

  • Restrict: A

  • Element: ANY

  • Priority: 0

  • Description: Expression to evaluate upon dblclick event. MouseEvent object is available as $event.

  • Example:

    <div ng-dblclick="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <button class="btn btn-dark" ng-init="count = 0" ng-dblclick="count++">
    Increment
  </button>
  <span> count: {{count}} </span>
</section>
count: {{count}}

3.16 - ng-el

Reference to an element

Description

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:

<section ng-controller="BoardController as $ctrl">
  <canvas ng-el="$ctrl.boardEl"></canvas>
</section>
function BoardController() {
  this.boardEl = null;
}

Use a bare name for simple scope shorthand:

<canvas ng-el="boardEl"></canvas>

Use a full assignable expression for controller-as or object-path refs:

<canvas ng-el="$ctrl.boardEl"></canvas>
<section ng-el="refs.panel"></section>

For component or directive controller references, use ng-ref instead:

<search-box ng-ref="$ctrl.search"></search-box>

ng-ref-read is only a modifier for ng-ref. It is useful when you need an assignable expression and want to force a specific read target:

<canvas ng-ref="$ctrl.boardEl" ng-ref-read="$element"></canvas>

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.

  • Example:

    <div ng-el="box"></div>
    <div ng-el="$ctrl.box"></div>
    <div id="box" ng-el></div>
    

Demo

<section ng-app>
  <div ng-el="$chesireCat"></div>
  <div ng-el id="response"></div>
  <button
    class="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>


3.17 - ng-focus

Handler for focus event

Description

The ng-focus directive allows you to specify custom behavior when an element is focused.

Parameters


ng-focus

  • Type: Expression

  • Description: Expression to evaluate upon focus event. FocusEvent object is available as $event.

  • Example:

    <div ng-focus="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <input
    type="text"
    ng-focus="count++"
    ng-init="count = 0"
    placeholder="Click or tab into me"
  />
  Focused {{ count }} times
</section>
Focused {{ count }} times

3.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 a JSON-response, the directive will treat it as an object to be merged into the current scope and the swap strategy will be ignored.

Example

<section>
  <div ng-get="/json">Get</div>
  <!-- Enpoint returns {name: 'Bob'}-->
  {{ name }}
  <!-- 'Bob' will be merged into current scope. -->
</section>

In case of error, the directive displays the error in place of success result or will merge it into the current scope if response contains JSON.

Example

<section>
  <div ng-post="/json">Get</div>
  <!-- Enpoint returns 404 with {error: 'Not found'}-->
  {{ error }}
  <!-- 'Not found' will be merged into current scope. Nothing to swap -->
</section>

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:

    <div ng-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.

  • Example:

    <div ng-get="/example" trigger="mouseover">Get</div>
    

data-latch

  • Type: string

  • 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:

    <div ng-get="/example" latch="{{ latch }}" ng-mouseover="latch = !latch">
      Get
    </div>
    

data-swap

  • Type: SwapMode

  • Description: Controls how the response is inserted

  • Example:

    <div ng-get="/example" swap="outerHTML">Get</div>
    

data-target

  • Type: selectors

  • Description: Specifies a DOM element where the response should be rendered or name of scope property for response binding

  • Example:

    <div ng-get="/example" target=".test">Get</div>
    <div ng-get="/json" target="person">{{ person.name }}</div>
    

data-delay

  • Type: delay

  • Description: Delay request by N millisecond

  • Example:

    <div ng-get="/example" delay="1000">Get</div>
    

data-interval

  • Type: delay

  • Description: Repeat request every N milliseconds

  • Example:

    <div ng-get="/example" interval="1000">Get</div>
    

data-throttle

  • Type: delay

  • Description: Ignores subsequent requests for N milliseconds

  • Example:

    <div ng-get="/example" throttle="1000">Get</div>
    

data-loading

  • Type: N/A

  • Description: Adds a data-loading=“true/false” flag during request lifecycle.

  • Example:

    <div ng-get="/example" data-loading>Get</div>
    

data-loading-class

  • Type: string

  • Description: Toggles the specified class on the element while loading.

  • Example:

    <div ng-get="/example" data-loading-class="red">Get</div>
    

data-success

  • Type: Expression

  • Description: Evaluates expression when request succeeds. Response data is available as a $res property on the scope.

  • Example:

    <div ng-get="/example" success="message = $res">Get {{ message }}</div>
    

data-error

  • Type: Expression

  • Description: Evaluates expression when request fails. Response data is available as a $res property on the scope.

  • Example:

    <div ng-get="/example" error="errormessage = $res">
      Get {{ errormessage }}
    </div>
    

data-success-state

  • Type: string

  • Description: Name of the state to nagitate to when request succeeds

  • Example:

    <ng-view></ng-view>
    <div ng-get="/example" success-state="account">Get</div>
    

data-success-error

  • Type: string

  • Description: Name of the state to nagitate to when request fails

  • Example:

    <ng-view></ng-view>
    <div ng-get="/example" error-state="login">Get</div>
    

3.19 - ng-hide

Hide an element when an expression is truthy.

Toggles the CSS display property of an element without removing it from the DOM. The element’s scope is always alive, regardless of visibility.

<div ng-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

Aspectng-ifng-show / ng-hide
DOM presenceRemoved when falseAlways in DOM
Scope lifetimeDestroyed when falseAlways alive
Child watchersRemoved when falseAlways active
Initial render costOnly when trueAlways rendered
Animation eventsng-enter / ng-leaveng-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

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

  Welcome back, {{ user.name }}!
</div>

ng-if

  • Type: expression
  • Required: yes

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.

3.21 - ng-include

Include a template

Description

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

  • Example:

    <div ng-inject="userService;accountService"></div>
    

Demo

<section ng-app>
  <div ng-inject="$window"></div>
  {{ $window.document.location }}
</section>
{{ $window.document.location }}

3.23 - ng-keydown

Handler for keydown event

Description

The ng-keydown directive allows you to specify custom behavior when pressing keys, regardless of whether they produce a character value.

Parameters


ng-keydown

  • Type: Expression

  • Description: Expression to evaluate upon keydown event. KeyboardEvent object is available as $event.

  • Example:

    <div ng-keydown="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <input
    type="text"
    ng-keydown="count++"
    ng-init="count = 0"
    placeholder="Click here, then press down a key."
  />
  Keydown {{ count }} times
</section>
Keydown {{ count }} times

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

Parameters


ng-keyup

  • Type: Expression

  • Description: Expression to evaluate upon keyup event. KeyboardEvent object is available as $event.

  • Example:

    <div ng-keyup="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <input
    type="text"
    ng-keyup="count++"
    ng-init="count = 0"
    placeholder="Click here, then press down a key."
  />
  Keyup {{ count }} times
</section>
Keyup {{ count }} times

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

Parameters


ng-load

  • Type: Expression

  • Description: Expression to evaluate upon load event. Event object is available as $event.

  • Example:

    <img src="url" ng-load="$ctrl.load($event)"></div>
    

Demo

<section ng-app>
  <img
    ng-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 }}

3.26 - ng-model

Two-way data binding for form controls.

Binds the value of <input>, <textarea>, <select>, and custom controls to a scope expression.

<textarea ng-model="user.bio"></textarea>
<select ng-model="user.role" ng-options="r for r in roles"></select>
<input type="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.

Model controller (NgModelController)

When ng-model is applied, AngularTS creates an NgModelController accessible as formName.fieldName on the scope. It exposes:

$viewValue

  • Type: any

The value as seen by the user in the input (always a string for text inputs).

$modelValue

  • Type: any

The value after parsers have run — what is stored on the scope.

$valid

  • Type: boolean

true when all validators pass.

$invalid

  • Type: boolean

true when any validator fails.

$pristine

  • Type: boolean

true until the user has interacted with this field.

$dirty

  • Type: boolean

true after the user has changed the value at least once.

$touched

  • Type: boolean

true after the field has received and lost focus.

$error

  • Type: object

Map of failing validator names to true. E.g., { required: true, minlength: true }.

Parsers and formatters

ng-model processes values through two pipelines:

  • Parsers ($parsers): Convert $viewValue$modelValue. Applied on user input. Return undefined to mark invalid.
  • Formatters ($formatters): Convert $modelValue$viewValue. Applied when scope value changes.
  return {
    require: 'ngModel',
    link: function(scope, el, attrs, ngModel) {
      ngModel.$parsers.push(function(value) {
        var n = parseInt(value, 10);
        ngModel.$setValidity('integer', !isNaN(n));
        return isNaN(n) ? undefined : n;
      });
    }
  };
});

3.27 - ng-model-options

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 -->
<input ng-model="name" ng-model-options="{ updateOn: 'blur' }">

<!-- Update on both blur and custom event -->
<input ng-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 -->
<input ng-model="search" ng-model-options="{ debounce: 300 }">

<!-- Different debounce per event -->
<input ng-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).

getterSetter

  • Type: boolean

When true, the ng-model expression is treated as a getter/setter function rather than a plain property. The function is called with no arguments to get and with the new value to set.

$scope.getUser = function(newVal) {
  if (arguments.length) { _user = newVal; }
  return _user;
};
<input ng-model="getUser" ng-model-options="{ getterSetter: true }">

`time

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

Parameters


ng-mousedown

  • Type: Expression

  • Description: Expression to evaluate upon mousedown event. MouseEvent object is available as $event.

  • Example:

    <div ng-mousedown="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <button ng-init="count = 0" ng-mousedown="count++">Press Mouse Down</button>
  Mouse Down {{ count }} times
</section>
Mouse Down {{ count }} times

3.29 - ng-mouseenter

Handler for mouseenter event

Description

The ng-mouseenter directive allows you to specify custom behavior when a mouse enters an element.

Parameters


ng-mouseenter

  • Type: Expression

  • Description: Expression to evaluate upon mouseenter event. MouseEvent object is available as $event.

  • Example:

    <div ng-mouseenter="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mouseenter="count++">Mouse Enter</div>
  Mouse Enter {{ count }} times
</section>
Mouse Enter
Mouse Enter {{ count }} times

3.30 - ng-mouseleave

Handler for mouseleave event

Description

The ng-mouseleave directive allows you to specify custom behavior when an element a mouse leaves entire element.

Parameters


ng-mouseleave

  • Type: Expression

  • Description: Expression to evaluate upon mouseleave event. MouseEvent object is available as $event.

  • Example:

    <div ng-mouseleave="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mouseleave="count++">Mouse Leave</div>
  Mouse Leave {{ count }} times
</section>
Mouse Leave
Mouse Leave {{ count }} times

3.31 - ng-mousemove

Handler for mousemove event

Description

The ng-mousemove directive allows you to specify custom custom behavior when a mouse is moved over an element.

Parameters


ng-mousemove

  • Type: Expression

  • Description: Expression to evaluate upon mousemove event. MouseEvent object is available as $event.

  • Example:

    <div ng-mousemove="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mousemove="count++">Mouse Move</div>
  Mouse Move {{ count }} times
</section>
Mouse Move
Mouse Move {{ count }} times

3.32 - ng-mouseout

Handler for mouseout event

Description

The ng-mouseout directive allows you to specify custom behavior when a mouse leaves any part of the element or its children.

Parameters


ng-mouseout

  • Type: Expression

  • Description: Expression to evaluate upon mouseout event. MouseEvent object is available as $event.

  • Example:

    <div ng-mouseout="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mouseout="count++">Mouse Out</div>
  Mouse Out {{ count }} times
</section>
Mouse Out
Mouse Out {{ count }} times

3.33 - ng-mouseover

Handler for mouseover event

Description

The ng-mouseover directive allows you to specify custom behavior when a mouse is placed over an element.

Parameters


ng-mouseover

  • Type: Expression

  • Description: Expression to evaluate upon mouseover event. MouseEvent object is available as $event.

  • Example:

    <div ng-mouseover="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mouseover="count++">Mouse Over</div>
  Mouse Over {{ count }} times
</section>
Mouse Over
Mouse Over {{ count }} times

3.34 - ng-mouseup

Handler for mouseup event

Description

The ng-mouseup directive allows you to specify custom behavior when a pressed mouse is released over an element.

Parameters


ng-mouseup

  • Type: Expression

  • Description: Expression to evaluate upon mouseup event. MouseEvent object is available as $event.

  • Example:

    <div ng-mouseup="$ctrl.greet($event)"></div>
    

Demo

<section ng-app>
  <div ng-init="count = 0" ng-mouseup="count++">Mouse Up</div>
  Mouse Up {{ count }} times
</section>
Mouse Up
Mouse Up {{ count }} times

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

  • Priority: 1000

  • Element: ANY

  • Example:

    <section ng-app>
        <div ng-non-bindable>{{ 2 + 2 }}</div>
      </section>
      

    {{ 2 + 2 }}

3.36 - ng-pointer-capture

Capture active pointer streams on an element

Description

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. Use ng-on-pointerdown, ng-on-pointermove, ng-on-pointerup, and ng-on-pointercancel for your application logic.

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:

    <div
      ng-pointer-capture
      ng-on-pointerdown="$ctrl.startDrag($event)"
      ng-on-pointermove="$ctrl.drag($event)"
      ng-on-pointerup="$ctrl.drop($event)"
      ng-on-pointercancel="$ctrl.cancelDrag($event)"
      data-event-prevent
    ></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: 1px solid #cbd5e1;
    background:
      linear-gradient(90deg, rgba(14, 165, 233, 0.12) 1px, transparent 1px),
      linear-gradient(rgba(14, 165, 233, 0.12) 1px, transparent 1px), #f8fafc;
    background-size: 2rem 2rem;
    touch-action: none;
    user-select: none;
  }

  .pointer-capture-demo__marker {
    position: absolute;
    left: 0;
    top: 0;
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid #fff;
    border-radius: 999px;
    background: #2563eb;
    box-shadow: 0 0.5rem 1rem rgba(15, 23, 42, 0.22);
    will-change: transform;
  }

  .pointer-capture-demo__status {
    margin: 0;
    color: #475569;
  }
</style>

<section ng-app="pointerCaptureDemo" class="pointer-capture-demo">
  <div ng-controller="PointerCaptureDemo as $ctrl">
    <div
      class="pointer-capture-demo__board"
      ng-pointer-capture
      ng-on-pointerdown="$ctrl.start($event)"
      ng-on-pointermove="$ctrl.move($event)"
      ng-on-pointerup="$ctrl.end($event)"
      ng-on-pointercancel="$ctrl.cancel()"
      data-event-prevent
    >
      <div
        class="pointer-capture-demo__marker"
        ng-style="$ctrl.markerStyle"
      ></div>
    </div>
    <p class="pointer-capture-demo__status">{{ $ctrl.status }}</p>
  </div>
</section>

<script>
  window.angular
    .module('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) {
        const rect = event.currentTarget.getBoundingClientRect();
        const radius = 12;
        const x = Math.max(
          radius,
          Math.min(event.clientX - rect.left, rect.width - radius),
        );
        const y = 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';
      };
    });
</script>

{{ $ctrl.status }}


3.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 a JSON-response, the directive will treat it as an object to be merged into the current scope. In such a case, the swap strategy will be ignored and the content will be interpolated into current scope. 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 request payload:

Example

<form ng-post="/register">
  <input name="username" type="text" />
</form>

With form elements, the directive can be registered anywhere inside a form:

Example

<form>
  <input name="username" type="text" />
  <button ng-post="/register">Send</button>
</form>

In case of error, the directive displays the error in place of success result or will merge it into the current scope if response contains JSON. 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.

Example

<form
  ng-post="/register"
  ng-if="$ctrl.success === false"
  success="$ctrl.success = true"
>
  <h2>Register form</h2>

  <label ng-class="{ error: errors.username }">
    Username
    <input
      name="username"
      type="text"
      aria-invalid="{{ errors.username !== undefined}}"
      ng-keyup="errors.username = undefined"
    />
    <span>{{ errors.username }}</span>
  </label>

  <button type="submit">Sign up</button>
</form>

For other input elements, the directive adds form-like behavior. The example below showcases an input acting as a search form:

<input
  name="seach"
  ng-post="/search"
  target="#output"
  trigger="keyup"
  placeholder="Search..."
/>

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:

    <div ng-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.

  • Example:

    <form ng-post="/urlencoded" enctype="application/x-www-form-urlencoded">
      <input type="text" name="name" />
    </form>
    

data-form

  • Type: string

  • Description: If placed outside a form element, specifies id of the form to use for datasource.

  • Example:

    <button ng-post="/register" form="register">Send</button>
    <form id="register">
      <input name="username" type="text" />
    </form>
    

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.

  • Example:

    <div ng-post="/example" trigger="mouseover">Get</div>
    

data-latch

  • Type: string

  • 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:

    <div ng-post="/example" latch="{{ latch }}" ng-mouseover="latch = !latch">
      Get
    </div>
    

data-swap

  • Type: SwapMode

  • Description: Controls how the response is inserted

  • Example:

    <div ng-post="/example" swap="outerHTML">Get</div>
    

data-target

  • Type: selectors

  • Description: Specifies a DOM element where the response should be rendered or name of scope property for response binding

  • Example:

    <div ng-post="/example" target=".test">Post</div>
    <div ng-post="/json" target="person">{{ person.name }}</div>
    

data-delay

  • Type: delay

  • Description: Delay request by N millisecond

  • Example:

    <div ng-post="/example" delay="1000">Get</div>
    

data-interval

  • Type: delay

  • Description: Repeat request every N milliseconds

  • Example:

    <div ng-post="/example" interval="1000">Get</div>
    

data-throttle

  • Type: delay

  • Description: Ignores subsequent requests for N milliseconds

  • Example:

    <div ng-post="/example" throttle="1000">Get</div>
    

data-loading

  • Type: N/A

  • Description: Adds a data-loading=“true/false” flag during request lifecycle.

  • Example:

    <div ng-post="/example" data-loading>Get</div>
    

data-loading-class

  • Type: string

  • Description: Toggles the specified class on the element while loading.

  • Example:

    <div ng-post="/example" data-loading-class="red">Get</div>
    

data-success

  • Type: Expression

  • Description: Evaluates expression when request succeeds. Response data is available as a $res property on the scope.

  • Example:

    <div ng-post="/example" success="message = $res">Get {{ message }}</div>
    

data-error

  • Type: Expression

  • Description: Evaluates expression when request fails. Response data is available as a $res property on the scope.

  • Example:

    <div ng-post="/example" error="errormessage = $res">
      Get {{ errormessage }}
    </div>
    

data-success-state

  • Type: string

  • Description: Name of the state to nagitate to when request succeeds

  • Example:

    <ng-view></ng-view>
    <div ng-post="/example" success-state="account">Get</div>
    

data-success-error

  • Type: string

  • Description: Name of the state to nagitate to when request fails

  • Example:

    <ng-view></ng-view>
    <div ng-post="/example" error-state="login">Get</div>
    

3.38 - ng-show

Show an element when an expression is truthy.

Toggles the CSS display property of an element without removing it from the DOM. The element’s scope is always alive, regardless of visibility.

<div ng-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

Aspectng-ifng-show / ng-hide
DOM presenceRemoved when falseAlways in DOM
Scope lifetimeDestroyed when falseAlways alive
Child watchersRemoved when falseAlways active
Initial render costOnly when trueAlways rendered
Animation eventsng-enter / ng-leaveng-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

3.39 - ng-sref

Create links to named router states.

Generates an href attribute pointing to a named state and handles click navigation. Equivalent to <a href="..."> but driven by state name rather than a raw URL.

<a ng-sref="home">Go home</a>

<!-- State with parameters -->
<a ng-sref="user.profile({ userId: user.id })">{{ user.name }}</a>

<!-- State with query params -->
<a ng-sref="search({ q: 'angular', page: 1 })">Search</a>

ng-sref

  • Type: expression
  • Required: yes

State name, optionally followed by a params object in parentheses: "stateName" or "stateName({ param: value })". The expression is evaluated in the current scope.

ng-sref-opts

  • Type: object

Options passed to $state.go(). Common options: { reload: true }, { inherit: false }, { location: 'replace' }.

<a ng-sref="home" ng-sref-opts="{ reload: true }">Reload Home</a>

3.40 - ng-sref-active

Apply classes when a linked state is active.

Adds a CSS class to the element when the referenced state (or any of its descendants) is active. Used to highlight active navigation links.

  <a ng-sref="home" ng-sref-active="active">Home</a>
  <a ng-sref="about" ng-sref-active="active">About</a>
  <a ng-sref="users" ng-sref-active="active">Users</a>
</nav>

ng-sref-active

  • Type: string
  • Required: yes

CSS class name(s) to apply when the state is active. Multiple classes separated by spaces are supported.

ng-sref-active uses $state.includes() — it is active for the referenced state AND any of its child states.

<a ng-sref="users" ng-sref-active="active">Users</a>

ng-sref-active-eq

Strict variant that only activates for an exact state match (uses $state.is() rather than $state.includes()):

<a ng-sref="users" ng-sref-active-eq="active">Users</a>

3.41 - ng-state

Bind state declaration data in templates.

A dynamic alternative to ng-sref where both the state name and params come from scope expressions rather than being hardcoded in the attribute.

ng-state

  • Type: expression
  • Required: yes

Expression that evaluates to a state name string.

ng-state-params

  • Type: expression

Expression that evaluates to the params object for the state.

$scope.currentParams = { userId: 42 };

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

  <div ng-switch-when="admin">Admin panel</div>
  <div ng-switch-when="editor">Editor tools</div>
  <div ng-switch-default>Standard view</div>
</div>

ng-switch

  • Type: expression
  • Required: yes

The value to switch on. Applied to the container element.

ng-switch-when

  • Type: string
  • Required: yes

The value to match against ng-switch. The element is rendered when the switch value equals this.

ng-switch-default

  • Type: none

Rendered when no ng-switch-when matches. No value required.

Multiple ng-switch-when values match the same element:

  <div ng-switch-when="pending" ng-switch-when="processing">In progress</div>
  <div ng-switch-when="done">Complete</div>
</div>

Animation hooks: .ng-enter / .ng-leave on matched/unmatched elements.

3.43 - 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 -->
<div ng-view></div>

<!-- Named view (for multiple named views in one state) -->
<div ng-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>' }
  }
});

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

For standard event names, see Window events and Document events.

Parameters


ng-window-*

  • Type: Expression

  • Description: Expression to evaluate upon event dispatch. Event object is available as $event.

  • Example:

    <div ng-window-message="data = $event.message.date">{{ data }}</div>
    

ng-document-*

  • Type: Expression

  • Description: Expression to evaluate upon document event dispatch. Event object is available as $event.

  • Example:

    <div ng-document-pointerup="$ctrl.endDrag($event)"></div>
    

Demo

<section ng-app>
  In Chrome DevTools: Open <b>Network tab</b> → Select
  <b>Throttling</b> dropdown -> Click <b>Offline</b> checkbox
  <div ng-window-online="online = true">Connected: {{ online }}</div>
  <div ng-window-offline="offline = true">Disconnected: {{ offline }}</div>
  <button ng-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' }}

4 - Directive Guides

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

  1. Overview: how directives are discovered, compiled, linked, and prioritized.
  2. Data Binding: directives that synchronize scope data with text, HTML, classes, styles, and form controls.
  3. Structural Directives: directives that add, remove, repeat, switch, include, or protect DOM sections.
  4. Forms: validation, model options, form state, and messages.
  5. HTTP Directives: declarative requests and Server-Sent Events from HTML.
  6. Animation Directives: CSS hooks and animation-specific attributes.
  7. 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.

4.1 - Advanced directives: workers, WASM, channels, viewport

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.

  <p>{{ ctrl.title }}</p>
</div>
  .controller('TodoController', function($scope) {
    $scope.title = 'My Todos';
  });

ng-scope

Marks an explicit scope boundary. Useful for isolating a section of the page without a controller.

  <p>Hello {{ user.name }}</p>
</div>

ng-init

Initializes scope variables inline in the template. Best suited for simple one-off values or prototyping.

  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.

<section ng-controller="BoardController as $ctrl">
  <canvas ng-el="$ctrl.boardEl"></canvas>
</section>
function BoardController() {
  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:

<canvas ng-el="boardEl"></canvas>

If the value is omitted, AngularTS uses the element id.

<canvas id="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.

<search-box ng-ref="$ctrl.search"></search-box>
<button ng-click="$ctrl.search.clear()">Clear</button>

By default, ng-ref reads the component or element-directive controller when one exists, and falls back to the DOM element.

ng-ref-read is not a standalone directive. It only changes what ng-ref assigns.

<canvas ng-ref="$ctrl.boardEl" ng-ref-read="$element"></canvas>
<my-widget ng-ref="$ctrl.resizeHandle" ng-ref-read="resizeHandle"></my-widget>

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.

function BoardController() {
  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.

  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.

  Scroll-tracked content
</div>

ng-inject

Injects a named service directly into the scope, making it accessible in the template without a controller.

  <!-- $http is now available in this scope -->
</div>

Lazy loading and performance

ng-viewport

Uses the browser’s Intersection Observer API to defer content rendering until the element enters the viewport. Ideal for below-the-fold content.

  <!-- loadComments() is called once when this enters the viewport -->
  <div ng-repeat="comment in comments">{{ comment.text }}</div>
</div>

ng-viewport

  • Type: expression

Expression to evaluate when the element enters the viewport. Called once and not repeated.

ng-cloak

Prevents the browser from briefly displaying uncompiled template syntax ({{ }}) before AngularTS bootstraps. Add to the root element or any element with interpolation.


<div ng-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.

     data-params="{ items: largeList }"
     data-on-result="sortedItems = $result">
  <div ng-repeat="item in sortedItems">{{ item.name }}</div>
</div>

ng-worker

  • Type: string
  • Required: yes

Path to the Web Worker JavaScript file.

data-params

  • Type: expression

Data to pass to the worker as the message payload.

data-on-result

  • Type: expression

Expression evaluated when the worker posts a result. $result contains the worker’s response.

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:

  var sorted = e.data.items.slice().sort((a, b) => a.name.localeCompare(b.name));
  postMessage(sorted);
};

ng-wasm

Loads a WebAssembly module and makes its exports available on scope.

  <p>Result: {{ math.add(3, 4) }}</p>
</div>

ng-wasm

  • Type: string
  • Required: yes

Path to the .wasm file to load.

as

  • Type: string

Scope property name to assign the WASM exports object to. Defaults to wasm.

Transclusion

ng-transclude

Used inside custom directive templates to mark where transcluded content should be inserted.

  return {
    transclude: true,
    template: '<div class="panel"><ng-transclude></ng-transclude></div>'
  };
});
  <p>This content is transcluded into the panel.</p>
</my-panel>

Non-bindable sections

ng-non-bindable

Prevents AngularTS from compiling or interpolating a subtree. Use this for displaying raw Angular syntax as documentation or code examples.

  {{ this will not be interpolated }}
  ng-repeat="item in items"
</pre>

Aria directives

AngularTS automatically manages ARIA attributes for accessibility. These directives are applied alongside their functional counterparts:

DirectiveARIA attribute managed
ng-disabledaria-disabled
ng-checkedaria-checked
ng-readonlyaria-readonly
ng-requiredaria-required
ng-show / ng-hidearia-hidden
ng-modelaria-value*, role

The $aria service and $ariaProvider let you configure which ARIA attributes are automatically managed:

  $ariaProvider.config({
    ariaDisabled: true,
    ariaChecked: true,
    ariaReadonly: true,
    ariaRequired: true,
    ariaHidden: true,
    ariaValue: true,
    tabindex: true
  });
});

Event directives

AngularTS generates event directives for all common DOM events. They all follow the ng-<eventname>="expression" pattern:

DirectiveDOM event
ng-clickclick
ng-dblclickdblclick
ng-focusfocus
ng-blurblur
ng-keydownkeydown
ng-keyupkeyup
ng-keypresskeypress
ng-mouseentermouseenter
ng-mouseleavemouseleave
ng-mousemovemousemove
ng-mouseovermouseover
ng-mousedownmousedown
ng-mouseupmouseup
ng-submitsubmit (on forms)
ng-cutcut
ng-copycopy
ng-pastepaste
ng-loadload
ng-oncustom event name

The expression is evaluated in the current scope. $event refers to the native DOM event object:

<input ng-keydown="$event.key === 'Enter' && submit()">

Event policy can be declared once on the same element and applies to every ng-on-* or generated event directive on that element:

<div
  ng-on-pointerdown="$ctrl.startDrag($event)"
  data-event-prevent
  data-event-capture>
</div>

Supported policy attributes:

AttributeBehavior
data-event-preventCalls $event.preventDefault() first
data-event-stopCalls $event.stopPropagation() first
data-event-captureRegisters the listener with capture: true
data-event-onceRegisters the listener with once: true
data-event-passiveRegisters the listener with passive: true

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 ng-on-pointer* handlers for game and board interactions that need reliable pointer streams:

<div
  ng-pointer-capture
  ng-on-pointerdown="$ctrl.startDrag($event)"
  ng-on-pointermove="$ctrl.drag($event)"
  ng-on-pointerup="$ctrl.drop($event)"
  data-event-prevent>
</div>

4.2 - Animation Directives: ng-animate-swap and CSS Hooks

Use ng-animate-swap, ng-animate-children, and CSS class hooks to animate structural directives like ng-if, ng-repeat, and ng-show with the $animate service.

AngularTS integrates animations into the same lifecycle that drives structural directives. Rather than managing setTimeout calls or CSS transitions manually, you add the animate attribute to an element and AngularTS’s $animate service handles entry, exit, and class-change transitions — applying and removing CSS hook classes at the right moment in the digest cycle.

How $animate integrates with directives

Structural directives — ng-if, ng-repeat, ng-show, ng-hide, ng-switch, ng-include, and ng-animate-swap — all check whether an element carries animation data before manipulating the DOM. When the animate attribute is present on an element (detected via hasAnimate(element)), they delegate DOM operations to $animate instead of performing them directly:

OperationWithout $animateWith $animate
Insert elementelement.after(clone)$animate.enter(clone, parent, after)
Remove elementelement.remove()$animate.leave(element)
Toggle classelement.classList.add(cls)$animate.addClass(element, cls)
Swap classesel.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:

Element enter

ng-enter  →  ng-enter + ng-enter-active  →  (classes removed)

Element leave

ng-leave  →  ng-leave + ng-leave-active  →  element removed

Element move (ng-repeat reorder)

ng-move  →  ng-move + ng-move-active  →  (classes removed)

Class add/remove

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: opacity 0.25s ease, transform 0.25s ease;
}
.card.ng-enter-active {
  opacity: 1;
  transform: translateY(0);
}

/* Fade out when the element is removed */
.card.ng-leave {
  opacity: 1;
  transition: opacity 0.2s ease;
}
.card.ng-leave-active {
  opacity: 0;
}

ng-animate-swap

ng-animate-swap swaps between transcluded content blocks using enter/leave animations as a watched expression changes. It runs at priority 550 — after ng-if (600) but before most attribute directives.

<div ng-animate-swap="currentAlert">
  <div ng-if="currentAlert === 'success'" class="banner banner-success">
    {{ message }}
  </div>
  <div ng-if="currentAlert === 'error'" class="banner banner-error">
    {{ message }}
  </div>
</div>

Each time the watched expression changes, the old element is removed via $animate.leave and the new transcluded clone is inserted via $animate.enter. The previous scope is destroyed before the new clone is linked.

A more common pattern is using ng-animate-swap to animate between stateful view components:

<div class="tab-content" ng-animate-swap="activeTab">
  <div class="tab-panel" ng-include="activeTab + '.html'"></div>
</div>
  opacity: 0;
  transform: translateX(20px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.tab-panel.ng-enter-active {
  opacity: 1;
  transform: translateX(0);
}
.tab-panel.ng-leave {
  opacity: 1;
  transition: opacity 0.2s ease;
}
.tab-panel.ng-leave-active {
  opacity: 0;
}

ng-animate-swap

  • Type: expression

The watched expression. Any time its value changes, the current transcluded block is removed with a leave animation and a new clone is entered. Use the for attribute alias interchangeably.


ng-animate-children

ng-animate-children propagates an animation-children flag to the element’s cache so that the $animate queue knows whether child elements should also be animated during parent transitions.

<div ng-if="showPanel" ng-animate-children="true">
  <div class="panel-header">Header</div>
  <div class="panel-body" ng-repeat="item in items">{{ item }}</div>
</div>

When ng-animate-children is set to "on" or "true" (or is present as an empty attribute), child animations run in parallel with the parent. Without it, the animation queue suppresses child animations while the parent is entering or leaving.

<ul ng-repeat="group in groups" ng-animate-children>
  <li ng-repeat="item in group.items" class="list-item">{{ item }}</li>
</ul>

ng-animate-children

  • Type: string | none

Accepts "on", "true", or an empty attribute to enable child animations. Any other value (or an interpolated expression evaluating to a falsy string) disables child animations.


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.

  <li class="task-item"
      ng-repeat="task in tasks track by task.id"
      animate>
    <span ng-bind="task.title"></span>
    <button ng-click="removeTask(task)">Remove</button>
  </li>
</ul>
  animation: slideIn 0.25s ease forwards;
}

.task-item.ng-leave {
  animation: slideOut 0.2s ease forwards;
}

.task-item.ng-move {
  transition: all 0.3s ease;
}

@keyframes slideIn {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideOut {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(16px); }
}
  .controller('TaskCtrl', function($scope) {
    $scope.tasks = [
      { id: 1, title: 'Design wireframes' },
      { id: 2, title: 'Write tests' },
      { id: 3, title: 'Deploy to staging' }
    ];

    $scope.removeTask = function(task) {
      const idx = $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:

  {{ message }}
</div>
  transition: opacity 0.3s ease, max-height 0.3s ease;
  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 via $animate

The $animateJs service allows you to register JavaScript-based animation hooks alongside CSS animations:

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

        return function(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;
      }
    };
  });
  {{ 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:

     trigger="load"
     swap="innerHTML"
     animate
     class="post-content">
</div>
  opacity: 0;
  transition: opacity 0.4s ease;
}
.post-content.ng-enter-active {
  opacity: 1;
}

4.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, <span ng-bind="user.name"></span>!</p>

The implementation watches the expression on every digest. The lazy attribute defers the first watch until a digest is explicitly triggered:

<span ng-bind="heavyExpression" lazy></span>

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.

<title ng-bind-template="{{pageTitle}} — {{siteName}}"></title>

<!-- Equivalent using child elements (not possible inside <title>) -->

The directive uses attr.$observe to watch the already-interpolated string; Angular re-evaluates the interpolation on each digest and writes the result 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.

<div ng-bind-html="article.body"></div>
  .controller('ArticleCtrl', function($scope) {
    $scope.article = {
      body: '<p>Hello <strong>world</strong></p>'
    };
  });

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.

Text input

<input type="text" ng-model="user.name" />
<p>Hello, {{ user.name }}!</p>

Checkbox

<input type="checkbox" ng-model="settings.notifications" />
<p ng-bind="settings.notifications ? 'On' : 'Off'"></p>

Select

<select ng-model="selected.country">
  <option value="us">United States</option>
  <option value="gb">United Kingdom</option>
</select>
<p>You selected: {{ selected.country }}</p>

Textarea

<textarea ng-model="message.body" rows="4"></textarea>
<p>Characters: {{ message.body.length }}</p>

ng-model creates an NgModelController instance that manages:

  • $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

PropertyTypeDescription
$pristinebooleantrue if the user has not interacted with this control
$dirtybooleantrue after the user has changed the value
$touchedbooleantrue after the control has lost focus
$untouchedbooleantrue before the control has ever been blurred
$validbooleantrue if all validators pass
$invalidbooleantrue 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.

<div ng-class="{ active: isActive, 'text-danger': hasError }">
  Status panel
</div>
$scope.isActive = true;
$scope.hasError = false;
// Result: class="active"

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.

<button ng-class="tile.classes">
  Fire
</button>
/**
 * @param {{ state: string, sunk: boolean }} tile
 * @returns {ng.ClassMap}
 */
function tileClasses(tile) {
  return {
    placed: tile.state === 'unit',
    hit: tile.state === 'hit',
    miss: tile.state === 'miss',
    sunk: tile.sunk,
  };
}

$scope.tile = {
  state: 'unit',
  sunk: false,
  classes: tileClasses({ state: 'unit', sunk: false }),
};

Array syntax

Each element is evaluated as a class name or object. Useful when combining static and conditional classes.

<div ng-class="[baseClass, { highlighted: isSelected }]">
  Item
</div>
$scope.baseClass = 'card';
$scope.isSelected = true;
// Result: class="card highlighted"

String syntax

The expression evaluates to a space-separated class string.

<div ng-class="currentTheme">
  Themed content
</div>
$scope.currentTheme = 'theme-dark compact';
// Result: class="theme-dark compact"

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.

ng-class-even / ng-class-odd

These variants apply classes only to even- or odd-indexed rows inside an ng-repeat.

  <li ng-repeat="item in items"
      ng-class-even="'row-even'"
      ng-class-odd="'row-odd'">
    {{ item.name }}
  </li>
</ul>

ng-class

  • Type: ng.ClassValue (string | ng.ClassMap | array)
  • Required: yes
  • String — a space-delimited list of class names.
  • 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.

<div ng-style="boxStyles">
  Dynamic box
</div>
  'background-color': '#4f46e5',
  'color': '#ffffff',
  'padding': '16px',
  'border-radius': '8px'
};
<p ng-style="{ 'font-size': fontSize + 'px', 'font-weight': isBold ? 'bold' : 'normal' }">
  Styled text
</p>

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.

<div ng-init="count = 0; title = 'Hello'">
  <button ng-click="count = count + 1">Clicked {{ count }} times</button>
</div>
<ul>
  <li ng-repeat="item in items" ng-init="pos = $index + 1">
    {{ pos }}. {{ item.name }}
  </li>
</ul>

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.

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

  <input type="email" name="email"
         ng-model="user.email"
         required />

  <button type="submit"
          ng-disabled="registrationForm.$invalid">
    Register
  </button>
</form>
  .controller('RegistrationCtrl', function($scope) {
    $scope.register = function() {
      if ($scope.registrationForm.$valid) {
        // submit logic
      }
    };
  });

FormController state properties

PropertyTypeDescription
$validbooleantrue when all child controls are valid
$invalidbooleantrue when any child control is invalid
$pristinebooleantrue before any control has been changed
$dirtybooleantrue after any control has been changed
$submittedbooleantrue after the form’s submit event fires
$errorobjectKeys are validator names; values are arrays of failing controls
$pendingobjectKeys 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:

ClassApplied when
ng-pristineControl has not been changed
ng-dirtyControl has been changed
ng-validAll validators pass
ng-invalidAny validator fails
ng-submittedForm has been submitted
ng-touchedInput has been blurred at least once
ng-untouchedInput has never been blurred
ng-pendingAn async validator is in progress
input.ng-invalid.ng-dirty {
  border: 2px solid #ef4444;
}

input.ng-valid.ng-dirty {
  border: 2px solid #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.

  <!-- Shipping address group -->
  <ng-form name="shippingForm">
    <input name="street" ng-model="shipping.street" required />
    <input name="city" ng-model="shipping.city" required />
    <p ng-if="shippingForm.$invalid">Please complete shipping address.</p>
  </ng-form>

  <!-- Billing address group -->
  <ng-form name="billingForm">
    <input name="street" ng-model="billing.street" required />
    <p ng-if="billingForm.$invalid">Please complete billing address.</p>
  </ng-form>

  <button ng-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.

Text / Email / URL

<input type="text"
       name="username"
       ng-model="user.username"
       required
       minlength="3"
       maxlength="20"
       ng-pattern="/^[a-z0-9_]+$/" />

<input type="email"
       name="email"
       ng-model="user.email"
       required />

<input type="url"
       name="website"
       ng-model="user.website" />

Number

<input type="number"
       name="age"
       ng-model="user.age"
       min="18"
       max="120"
       required />

Parses the input into a JavaScript number. Sets $error.number if the value is not numeric, $error.min or $error.max if out of range.

Checkbox

<!-- Simple boolean checkbox -->
<input type="checkbox"
       name="agree"
       ng-model="user.agreedToTerms"
       required />

<!-- Custom true/false values -->
<input type="checkbox"
       ng-model="settings.mode"
       ng-true-value="'dark'"
       ng-false-value="'light'" />

Radio

<label>
  <input type="radio" ng-model="plan" value="free" /> Free
</label>
<label>
  <input type="radio" ng-model="plan" value="pro" /> Pro
</label>
<label>
  <input type="radio" ng-model="plan" value="enterprise" /> Enterprise
</label>
<p>Selected: {{ plan }}</p>

Select

<!-- Simple string options -->
<select name="country" ng-model="user.country" required>
  <option value="">-- Choose a country --</option>
  <option value="us">United States</option>
  <option value="gb">United Kingdom</option>
</select>

<!-- Object options with ng-options -->
<select ng-model="selectedRole"
        ng-options="role.id as role.label for role in roles">
  <option value="">-- 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.


<!-- Conditionally required -->
<input type="text"
       ng-model="user.company"
       ng-required="user.accountType === 'business'" />

The validator calls NgModelController.$isEmpty on the view value. For text inputs, empty means "". For checkboxes, empty means false.

minlength / ng-minlength

       ng-model="user.password"
       ng-minlength="8" />
<p ng-if="form.password.$error.minlength">
  Password must be at least 8 characters.
</p>

maxlength / ng-maxlength

       ng-model="tweet.text"
       ng-maxlength="280" />
<p>{{ 280 - tweet.text.length }} characters remaining</p>

pattern / ng-pattern

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.

<input type="text"
       ng-model="user.phone"
       pattern="^\d{10}$" />

<!-- Dynamic pattern from scope -->
<input type="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).

  <div class="field">
    <label>Email</label>
    <input type="email"
           name="email"
           ng-model="user.email"
           required />

    <div ng-messages="signupForm.email.$error" role="alert">
      <p ng-message="required">Email address is required.</p>
      <p ng-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:

  <p ng-message="required">Password is required.</p>
  <p ng-message="minlength">At least 8 characters required.</p>
  <p ng-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:

  <p ng-message="pattern">Invalid format.</p>
  <p ng-message-default>This field has an error.</p>
</div>

Reusable message templates

Use ng-messages-include to load a shared message template file:

  <div ng-messages-include="'/partials/common-messages.html'"></div>
  <p ng-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:

<input type="text"
       ng-model="search.query"
       ng-model-options="{ debounce: 300 }" />

This fires a search 300ms after the last keystroke rather than on every character.

Update on blur

Only update the model when the input loses focus:

<input type="text"
       ng-model="user.username"
       ng-model-options="{ updateOn: 'blur' }" />

Combined events

Update immediately on blur, but also debounced on default (which covers all other events):

<input type="text"
       ng-model="user.slug"
       ng-model-options="{
         updateOn: 'blur default',
         debounce: { 'default': 500, 'blur': 0 }
       }" />

Getter/setter

When getterSetter: true, the ng-model expression is expected to be a function that both gets and sets the value:

<input type="text"
       ng-model="user.getName"
       ng-model-options="{ getterSetter: true }" />
let _name = 'Alice';
$scope.user = {
  getName: function(newValue) {
    if (arguments.length) {
      _name = newValue.trim();
    }
    return _name;
  }
};

Available options

updateOn

  • Type: string

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

getterSetter

  • Type: boolean

When true, the ng-model expression is treated as a function that acts as both getter and setter.

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.


  <!-- Username -->
  <div class="field">
    <label for="username">Username</label>
    <input id="username"
           type="text"
           name="username"
           ng-model="form.username"
           ng-model-options="{ debounce: 300 }"
           required
           ng-minlength="3"
           ng-maxlength="20"
           ng-pattern="/^[a-z0-9_]+$/" />

    <div ng-messages="regForm.username.$error"
         ng-if="regForm.username.$touched || regForm.$submitted">
      <p ng-message="required">Username is required.</p>
      <p ng-message="minlength">At least 3 characters.</p>
      <p ng-message="maxlength">No more than 20 characters.</p>
      <p ng-message="pattern">Only lowercase letters, numbers, and underscores.</p>
    </div>
  </div>

  <!-- Email -->
  <div class="field">
    <label for="email">Email</label>
    <input id="email"
           type="email"
           name="email"
           ng-model="form.email"
           required />

    <div ng-messages="regForm.email.$error"
         ng-if="regForm.email.$touched || regForm.$submitted">
      <p ng-message="required">Email is required.</p>
      <p ng-message="email">Enter a valid email address.</p>
    </div>
  </div>

  <!-- Password -->
  <div class="field">
    <label for="password">Password</label>
    <input id="password"
           type="password"
           name="password"
           ng-model="form.password"
           required
           ng-minlength="8" />

    <div ng-messages="regForm.password.$error"
         ng-if="regForm.password.$dirty">
      <p ng-message="required">Password is required.</p>
      <p ng-message="minlength">At least 8 characters required.</p>
    </div>
  </div>

  <!-- Terms acceptance -->
  <div class="field">
    <label>
      <input type="checkbox"
             name="terms"
             ng-model="form.agreedToTerms"
             required />
      I agree to the terms and conditions
    </label>
    <p ng-if="regForm.terms.$error.required && regForm.$submitted">
      You must accept the terms.
    </p>
  </div>

  <button type="submit"
          ng-disabled="regForm.$invalid && regForm.$submitted">
    Create account
  </button>
</form>
  .controller('RegistrationCtrl', function($scope) {
    $scope.form = {};

    $scope.submitRegistration = function() {
      $scope.regForm.$setSubmitted();

      if ($scope.regForm.$valid) {
        // Call your API
        console.log('Registering:', $scope.form);
      }
    };
  });

4.5 - HTTP Directives: ng-get, ng-post, ng-put, ng-sse

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 inspired by HTMX. 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 scope merging — all configured through HTML attributes.

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:

<button ng-click="loadUser()">Load user</button>
<p>{{ user.name }}</p>
  $http.get('/api/user/1').then(function(res) {
    $scope.user = res.data;
  });
};
<button ng-get="/api/user/1">Load user</button>
<p>{{ name }}</p>

When the server returns JSON, the directive merges it into scope automatically. When it returns HTML, the result is compiled and injected into the DOM 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).

<button ng-get="/api/weather?city=London">
  Get weather
</button>
{{ temperature }}°C — {{ description }}
<button ng-get="/partials/user-card.html"
        swap="outerHTML"
        target="#user-area">
  Show profile
</button>
<div id="user-area"></div>

Automatic trigger

Use trigger="load" to fire the request immediately when the element is linked (no user interaction required):

<div ng-get="/api/dashboard/stats" trigger="load">
  <span ng-bind="stats.users"></span> users
</div>

Polling with interval

<div ng-get="/api/live-count"
     trigger="load"
     interval="5000">
  {{ count }} online
</div>

ng-post

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.

<form ng-post="/api/contact" name="contactForm">
  <input name="email" type="email" ng-model="contact.email" required />
  <textarea name="message" ng-model="contact.message" required></textarea>
  <button type="submit" ng-disabled="contactForm.$invalid">Send</button>
</form>
<button ng-post="/api/cart/add"
        success="cartMessage = $res.message"
        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:

      name="profileForm"
      success="profileSaved = true"
      state-success="dashboard">
  <input name="name" ng-model="user.name" required />
  <input name="email" type="email" ng-model="user.email" required />
  <button type="submit">Save changes</button>
</form>

ng-delete

ng-delete fires a DELETE request. It shares all the same modifier attributes as ng-get:

<li>
  <span>{{ item.name }}</span>
  <button ng-delete="/api/items/{{ item.id }}"
          swap="delete"
          target="#item-{{ item.id }}">
    Delete
  </button>
</li>

ng-sse

ng-sse opens a persistent Server-Sent Events connection using the $sse service. Incoming messages are handled the same way as HTTP responses: JSON payloads are merged into scope, HTML strings are injected using the configured swap strategy.

<div ng-sse="/api/events/notifications"
     swap="beforeend">
</div>
<div ng-sse="/api/events/market"
     trigger="load">
  <p>{{ price | currency }}</p>
  <p>{{ change }}%</p>
</div>

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.

app.get('/api/events/market', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ price: 42.50, change: 0.3 })}\n\n`);
  }, 2000);

  req.on('close', () => clearInterval(interval));
});

Shared modifier attributes

All HTTP directives accept the same set of modifier attributes to control their behaviour:

swap

  • Type: string
  • Default: innerHTML

Controls how the response HTML is inserted. Possible values:

  • innerHTML — replaces the element’s inner content (default)
  • outerHTML — replaces the entire element
  • textContent — inserts plain text without HTML parsing
  • beforebegin — inserts immediately before the element
  • afterbegin — inserts inside the element, before its first child
  • beforeend — inserts inside the element, after its last child
  • afterend — inserts immediately after the element
  • delete — removes the target element entirely
  • none — performs no DOM insertion (useful when only scope merging is needed)

target

  • Type: CSS selector

A querySelector selector for the element that receives the response. When omitted, the directive element itself is the target.

<button ng-get="/api/user" target="#profile-card">Load</button>
<div id="profile-card"></div>

trigger

  • Type: DOM event name

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.

<div ng-get="/api/search" latch="{{ query }}" swap="innerHTML">
  {{ results.length }} results
</div>

interval

  • Type: number

Fires the request immediately and then repeats every N milliseconds. The interval is cleared when the scope is destroyed.

<div ng-get="/api/status" interval="10000">{{ status }}</div>

delay

  • Type: number

Wait N milliseconds before sending the request after the trigger event fires.

throttle

  • Type: number

Ignore subsequent trigger events for N milliseconds after the request fires.

loading

  • Type: none

When present, sets data-loading="true" on the element while the request is in flight and data-loading="false" when it completes. Useful as a CSS hook.

<button ng-get="/api/data" loading>Load</button>
button[data-loading="true"] {
  opacity: 0.6;
  cursor: wait;
}

loading-class

  • Type: string

A CSS class toggled on the element while the request is in flight.

<button ng-get="/api/data" loading-class="is-loading">Load</button>

success

  • Type: expression

Expression evaluated when the response has a 2xx status code. The response data is available as $res.

<button ng-get="/api/items" success="items = $res">Fetch</button>

error

  • Type: expression

Expression evaluated when the response has a 4xx or 5xx status code. The response data is available as $res.

<button ng-post="/api/login"
        success="redirect('/dashboard')"
        error="loginError = $res.message">
  Log in
</button>

state-success

  • Type: string

Router state name to navigate to on success (calls $state.go).

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

  <thead>
    <tr>
      <th>Name</th>
      <th>Status</th>
      <th>Joined</th>
    </tr>
  </thead>
  <tbody ng-get="/api/users"
         trigger="load"
         swap="innerHTML">
    <!-- Populated with server-rendered rows -->
  </tbody>
</table>

Infinite scroll

  <li ng-repeat="post in posts">{{ post.title }}</li>
</ul>

<button ng-get="/api/posts?page={{ nextPage }}"
        ng-viewport
        on-enter="loadMore()"
        swap="beforeend"
        target="#post-list">
  Load more
</button>

Real-time notifications via SSE

  <span ng-bind="notifications.unread"></span>
</div>

<!-- SSE merges JSON payloads into scope automatically -->
<div ng-sse="/api/events/notifications" trigger="load">
</div>

Form submission with redirect on success

      ng-post="/api/auth/login"
      state-success="app.dashboard"
      error="authError = $res.message">
  <input type="email" name="email" ng-model="credentials.email" required />
  <input type="password" name="password" ng-model="credentials.password" required />
  <p ng-if="authError" ng-bind="authError"></p>
  <button type="submit" ng-disabled="loginForm.$invalid">Log in</button>
</form>

4.6 - Directive Overview

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.

Exact custom directive contracts live in TypeDoc:

How Directives Are Matched

The restrict option controls where a directive can appear.

RestrictMatch formExample
AAttribute<span ng-bind="name"></span>
EElement<my-widget></my-widget>
AE / EAAttribute or element<div ng-form> or <ng-form>

Attribute directives are the common case in AngularTS. They keep the host element in place and augment it.

Built-In Directive Groups

AngularTS groups its built-in directives by the user-facing job they perform. Keep these groups in mind when choosing where to look in the docs.

GroupPurposeExamples
Data bindingSynchronize scope data with the viewng-bind, ng-model, ng-class, ng-style
StructuralAdd, remove, repeat, or switch DOMng-if, ng-repeat, ng-show, ng-hide, ng-switch
FormsTrack validity, dirty state, messages, and model optionsform, ng-model, ng-messages, validators
HTTPTrigger declarative network work from the DOMng-get, ng-post, ng-put, ng-delete, ng-sse
AnimationsCoordinate directive-driven animation hooksng-animate-swap, ng-animate-children
AdvancedBridge browser APIs and integration boundariesng-worker, ng-wasm, ng-viewport, ng-pointer-capture

Create A Custom Directive

Register directives on a module with .directive(name, factory). The factory returns a directive definition object.

angular.module("demo", []).directive("highlight", () => {
  return {
    restrict: "A",
    scope: {
      color: "@highlight",
    },
    link(scope, element) {
      const paint = (color) => {
        element.style.backgroundColor = color || "yellow";
      };

      paint(scope.color);

      scope.$watch("color", paint);
    },
  };
});
<p highlight="gold">Pinned note</p>

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.

<li ng-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.

Next Steps

4.7 - Structural Directives: ng-if, ng-repeat, ng-switch

Master AngularTS structural directives including ng-if, ng-show, ng-hide, ng-repeat with track by, 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.

<div ng-if="user.role === 'admin'">
  <h2>Admin Panel</h2>
  <p>Welcome, {{ user.name }}. You have full access.</p>
</div>
<div ng-if="isLoggedIn">
  <p>Welcome back, {{ user.name }}!</p>
  <button ng-click="logout()">Log out</button>
</div>
<div ng-if="!isLoggedIn">
  <p>Please <a href="/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.

<div ng-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.

<div ng-hide="isLoading">
  <p>Content is ready.</p>
</div>
<div ng-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.

.my-panel.ng-hide {
  opacity: 0;
}
.my-panel.ng-hide-animate {
  transition: opacity 0.3s ease;
}

ng-show

  • Type: expression
  • Required: yes

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

VariableTypeDescription
$indexnumberZero-based index of the current iteration
$firstbooleantrue for the first item
$lastbooleantrue for the last item
$middlebooleantrue when neither first nor last
$evenbooleantrue when $index is even
$oddbooleantrue when $index is odd

Repeating over an array

  <li ng-repeat="product in products">
    <strong>{{ product.name }}</strong> — ${{ product.price }}
    <span ng-if="$first">(newest)</span>
    <span ng-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.

  <dt ng-repeat="(key, value) in user">{{ key }}</dt>
  <dd>{{ value }}</dd>
</dl>

Track by

By default, ng-repeat tracks items by identity (hashKey). When the collection changes, AngularTS tries to reuse existing DOM nodes by matching on the track key. The track by clause lets you provide a stable identity for items, which dramatically improves performance when working with large lists or when items are replaced by new objects from the server.

<div ng-repeat="user in users">{{ user.name }}</div>

<!-- With track by id — DOM nodes for unchanged users are reused -->
<div ng-repeat="user in users track by user.id">{{ user.name }}</div>

<!-- Track by $index — useful for arrays of primitives -->
<div ng-repeat="tag in tags track by $index">{{ tag }}</div>

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>
  <li ng-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:

  {{ item.name }}
</li>
  opacity: 0;
  transition: opacity 0.3s;
}
.list-item.ng-enter-active {
  opacity: 1;
}
.list-item.ng-leave {
  opacity: 1;
  transition: opacity 0.3s;
}
.list-item.ng-leave-active {
  opacity: 0;
}

Warning: Duplicate track keys in the same repeater throw an ngRepeat:dupes error. Use track by $index or a unique property like track by item.id to guarantee uniqueness.

ng-repeat

  • Type: string
  • Required: yes

Expression in one of the forms:

  • item in collection
  • (key, value) in object
  • item in collection track by expression
  • item in collection | filter as alias

ng-switch

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.

  <div ng-switch-when="active">
    <p>Account is active. Welcome, {{ user.name }}!</p>
  </div>
  <div ng-switch-when="suspended">
    <p>Your account has been suspended. Please contact support.</p>
  </div>
  <div ng-switch-when="pending">
    <p>Your account is awaiting verification.</p>
  </div>
  <div ng-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:

  <div ng-switch-when="Saturday Sunday" ng-switch-when-separator=" ">
    Weekend!
  </div>
  <div ng-switch-default>Weekday</div>
</div>

ng-switch

  • Type: expression
  • Required: yes

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.

<div ng-include="'/partials/user-profile.html'"></div>

<!-- Include a dynamic template based on user role -->
<div ng-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
<div ng-include="templateUrl"
     ng-init="templateUrl = '/partials/home.html'"
     onload="onTemplateLoaded()">
</div>
  console.log('Template inserted successfully');
};

ng-include

  • Type: expression
  • Required: yes

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.

<pre ng-non-bindable>
  &lt;p&gt;Use {{ expression }} to interpolate values.&lt;/p&gt;
  &lt;div ng-bind="myValue"&gt;&lt;/div&gt;
</pre>
<div ng-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.

5 - Providers

5.1 - $anchorScrollProvider

Configure automatic anchor scrolling when the URL hash changes.

$anchorScrollProvider controls whether $anchorScroll reacts automatically when $location changes the URL hash.

Exact signatures live in TypeDoc:

Disable Automatic Scrolling

angular.module("demo", []).config(($anchorScrollProvider) => {
  $anchorScrollProvider.autoScrollingEnabled = false;
});

Disable automatic scrolling when the application needs to coordinate hash changes with custom routing, animation, or focus management.

For service usage, see $anchorScroll.

5.2 - $animateProvider

Configure the animation system.

Use $animateProvider during module configuration to limit which elements animate and to register JavaScript animation factories.

Filtering Animations

Class-name filtering is useful when you want animation support only for specific parts of the DOM.

angular.module('app', []).config(($animateProvider) => {
  $animateProvider.classNameFilter(/ng-animate/);
});

JavaScript Animation Factories

Register a factory for a CSS selector when CSS transitions are not enough.

angular.module('app', []).config(($animateProvider) => {
  $animateProvider.register('.fade-card', () => ({
    enter(element, done) {
      element.animate([{ opacity: 0 }, { opacity: 1 }], 150).finished.then(done);
    },
  }));
});

See also Animation Directives.

5.3 - $ariaProvider

Configure automatic ARIA attributes.

Use $ariaProvider to choose which accessibility attributes AngularTS manages for common directives such as ng-show, ng-hide, ng-model, and disabled controls.

angular.module('app', []).config(($ariaProvider) => {
  $ariaProvider.config({
    ariaHidden: true,
    ariaChecked: true,
    ariaDisabled: true,
    ariaRequired: true,
    ariaReadonly: true,
    ariaValue: true,
    tabindex: true,
  });
});

See also ng-aria.

5.4 - $compileProvider

Configure compiler behavior.

Use $compileProvider during module configuration for compiler-level behavior: registering directives and controlling debug metadata.

Registering Directives

angular.module('app', []).config(($compileProvider) => {
  $compileProvider.directive('focusOn', () => ({
    restrict: 'A',
    link(scope, element) {
      element.focus();
    },
  }));
});

Debug Metadata

Disable debug metadata in production when you do not need scope lookup from DOM nodes.

angular.module('app', []).config(($compileProvider) => {
  $compileProvider.debugInfoEnabled(false);
});

5.5 - $cookieProvider

Configure default attributes for cookies written by the $cookie service.

$cookieProvider configures defaults that are merged into every $cookie.put(), $cookie.putObject(), and $cookie.remove() call.

Exact signatures live in TypeDoc:

Set Defaults

angular.module("demo", []).config(($cookieProvider) => {
  $cookieProvider.defaults = {
    path: "/",
    secure: true,
    samesite: "Lax",
  };
});

Use provider defaults for application-wide settings such as cookie path, HTTPS-only behavior, and SameSite policy. Per-call options still override these defaults.

For service usage, see $cookie.

5.6 - $eventBusProvider

Configure the application-wide $eventBus service.

$eventBusProvider creates the injectable $eventBus singleton and exposes the same instance through the global Angular service for integrations outside dependency injection.

Exact signatures live in TypeDoc:

Replace The Bus

angular.module("demo", []).config(($eventBusProvider) => {
  $eventBusProvider.eventBus = new MyCustomPubSub();
});

Most applications should use the default PubSub instance. Replace it only when you need custom dispatch, instrumentation, or compatibility with another event system.

For service usage, see $eventBus.

5.7 - $exceptionHandlerProvider

Configure the $exceptionHandler service used for framework and application errors.

$exceptionHandlerProvider configures the function used by $exceptionHandler. The default handler rethrows the exception, and custom handlers should preserve that behavior after reporting the error.

Exact signatures live in TypeDoc:

Configure Error Reporting

angular.module("demo", []).config(($exceptionHandlerProvider) => {
  $exceptionHandlerProvider.handler = (error) => {
    myLogger.capture(error);
    throw error;
  };
});

Rethrowing matters because the framework assumes $exceptionHandler does not silently swallow fatal errors.

For service usage, see $exceptionHandler.

5.8 - $httpProvider

Configure defaults and interceptors for $http.

Use $httpProvider during module configuration to set application-wide HTTP behavior before any request is made.

Exact provider members are documented in TypeDoc:

Defaults

Use defaults for headers, credentials, transforms, XSRF names, caching, and parameter serialization.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.defaults.headers.common.Authorization = 'Bearer token';
  $httpProvider.defaults.withCredentials = true;
});

Interceptors

Register interceptors to add cross-cutting request and response behavior.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.interceptors.push(() => ({
    request(config) {
      config.headers['X-Timestamp'] = Date.now();
      return config;
    },
  }));
});

XSRF

Add trusted origins before AngularTS sends XSRF headers to cross-origin APIs.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
});

See also $http.

5.9 - $interpolateProvider

Configure interpolation delimiters.

Use $interpolateProvider when AngularTS interpolation conflicts with a server-side template language that also uses {{ }}.

angular.module('app', []).config(($interpolateProvider) => {
  $interpolateProvider.startSymbol = '[[';
  $interpolateProvider.endSymbol = ']]';
});

See also Templates and interpolation.

5.10 - $locationProvider

Configure URL mode and hash-prefix behavior for $location.

Use $locationProvider during module configuration to choose how AngularTS reads and writes browser URLs.

Exact provider members are documented in TypeDoc:

HTML5 Mode

HTML5 mode uses the History API for clean URLs. Server routing must return the application shell for deep links.

angular.module('app', []).config(($locationProvider) => {
  $locationProvider.html5ModeConf = {
    enabled: true,
    requireBase: false,
    rewriteLinks: true,
  };
});

Hash Prefix

Hash mode keeps application state after the # fragment. Configure the prefix when you need hashbang-style URLs or compatibility with existing links.

angular.module('app', []).config(($locationProvider) => {
  $locationProvider.hashPrefixConf = '!';
});

See also $location.

5.11 - $logProvider

Configure logging behavior for $log.

Use $logProvider during module configuration to enable debug logging or replace the logger implementation.

Exact provider members are documented in TypeDoc:

Debug Logging

angular.module('app', []).config(($logProvider) => {
  $logProvider.debug = true;
});

Custom Logger

angular.module('app', []).config(($logProvider) => {
  $logProvider.setLogger(() => ({
    log: console.log,
    info: console.info,
    warn: console.warn,
    error: console.error,
    debug: console.debug,
  }));
});

See also $log.

5.12 - $rootScopeProvider

Configure root scope behavior.

Use $rootScopeProvider for application-wide scope configuration before the root scope service is created.

Exact provider members are documented in TypeDoc:

Digest Iteration Limit

The digest TTL prevents infinite watch loops. Increase it only when you have a known, intentional chain of watchers that requires more iterations.

angular.module('app', []).config(($rootScopeProvider) => {
  $rootScopeProvider.digestTtl(15);
});

See also $rootScope.

5.13 - $sceDelegateProvider

Configure trusted resource URL policies and URL sanitization for SCE.

Use $sceDelegateProvider to control which resource URLs AngularTS may load for contexts such as templates, includes, iframes, and other resource fetches. It also owns the URL allowlists used when sanitizing bound link and media URLs.

Exact provider members are documented in TypeDoc:

Trusted Resource URLs

Allow same-origin resources with self, exact strings, wildcard strings, or regular expressions.

angular.module('app', []).config(($sceDelegateProvider) => {
  $sceDelegateProvider.trustedResourceUrlList([
    'self',
    'https://cdn.example.com/**',
  ]);
});

Banned Resource URLs

Banned patterns override trusted patterns. Use them to block a narrower path inside a broader trusted origin.

angular.module('app', []).config(($sceDelegateProvider) => {
  $sceDelegateProvider.bannedResourceUrlList([
    'https://cdn.example.com/private/**',
  ]);
});

URL Sanitization

Configure trusted URL patterns for links and media sources before templates are compiled.

angular.module('app', []).config(($sceDelegateProvider) => {
  $sceDelegateProvider.aHrefSanitizationTrustedUrlList(/^https?:/);
  $sceDelegateProvider.imgSrcSanitizationTrustedUrlList(
    /^\s*((https?|file|blob):|data:image\/)/,
  );
});

See also $sce.

5.14 - $sceProvider

Strict Contextual Escaping service

$sceProvider and $sce Documentation

$sceProvider

The $sceProvider provider allows developers to configure the $sce service.

  • Enable/disable Strict Contextual Escaping (SCE) in a module
  • Override the default implementation with a custom delegate

Read more about Strict Contextual Escaping (SCE).


$sce

$sce is a service that provides Strict Contextual Escaping (SCE) services to AngularTS.


Strict Contextual Escaping

Overview

Strict Contextual Escaping (SCE) is a mode in which AngularTS constrains bindings to only render trusted values. Its goal is to:

  • Help you write secure-by-default code.
  • Simplify auditing for vulnerabilities such as XSS and clickjacking.

By default, AngularTS treats all values as untrusted in HTML or sensitive URL bindings. When binding untrusted values, AngularTS will:

  • Sanitize or validate them based on context.
  • Or throw an error if it cannot guarantee safety.

Example — ng-bind-html renders its value directly as HTML (its “context”). If the input is untrusted, AngularTS will sanitize or reject it.

To bypass sanitization, you must mark a value as trusted before binding it.

Note: Since version 1.2, AngularTS ships with SCE enabled by default.


Example: Binding in a Privileged Context

<input ng-model="userHtml" aria-label="User input" />
<div ng-bind-html="userHtml"></div>

If SCE is disabled, this allows arbitrary HTML injection — a serious XSS risk.

To safely render user content, you should sanitize the HTML (on the server or client) before binding it.

SCE ensures that only trusted, validated, or sanitized values are rendered.

You can mark trusted values explicitly using:

$sce.trustAs(context, value);

or shorthand methods such as:

$sce.trustAsHtml(value);
$sce.trustAsUrl(value);
$sce.trustAsResourceUrl(value);

How It Works

Directives and Angular internals bind trusted values using:

$sce.getTrusted(context, value);

Example: the ngBindHtml directive uses $sce.parseAsHtml internally:

let ngBindHtmlDirective = [
  '$sce',
  function ($sce) {
    return function (scope, element, attr) {
      scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function (value) {
        element.html(value || '');
      });
    };
  },
];

Impact on Loading Templates

SCE affects both:

  • The ng-include directive
  • The templateUrl property in directives

By default, AngularTS loads templates only from the same domain and protocol as the main document.
It uses:

$sce.getTrustedResourceUrl(url);

To allow templates from other domains, use:

  • $sceDelegateProvider.trustedResourceUrlList
  • Or $sce.trustAsResourceUrl(url)

Note: Browser CORS and Same-Origin policies still apply.


Is This Too Much Overhead?

SCE applies only to interpolation expressions.

Constant literals are automatically trusted, e.g.:

<div ng-bind-html="'<b>implicitly trusted</b>'"></div>

If the ngSanitize module is included, $sceDelegate will use $sanitize to clean untrusted HTML automatically.

AngularTS’ default $sceDelegate allows loading from your app’s domain, blocking others unless explicitly whitelisted.

This small overhead provides major security benefits and simplifies auditing.


Supported Trusted Contexts

ContextDescription
$sce.HTMLSafe HTML (used by ng-bind-html).
$sce.CSSSafe CSS. Currently unused.
$sce.MEDIA_URLSafe media URLs (auto-sanitized).
$sce.URLSafe navigable URLs.
$sce.RESOURCE_URLSafe resource URLs (used in ng-include, iframe, etc.).
$sce.JSSafe JavaScript for execution.

⚠️ Before AngularTS 1.7.0, a[href] and img[src] sanitized directly.
As of 1.7.0, these now use $sce.URL and $sce.MEDIA_URL respectively.


Resource URL List Patterns

Trusted and banned resource URL lists accept:

  • 'self' → matches same domain & protocol
  • Strings with wildcards:
    • * → matches within a single path segment
    • ** → matches across path segments (use carefully)
  • Regular expressions (RegExp)

Caution: Regex patterns are powerful but harder to maintain — use only when necessary.


You can disable SCE globally — though this is strongly discouraged.

angular.module('myAppWithSceDisabled', []).config(function ($sceProvider) {
  // Completely disable SCE. For demonstration purposes only!
  // Do not use in new projects or libraries.
  $sceProvider.enabled(false);
});

5.15 - $templateCacheProvider

Configure the cache backing the $templateCache service.

$templateCacheProvider initializes the cache used by $templateCache. The default cache is a Map, but applications can provide another implementation that satisfies the TemplateCache contract.

Exact signatures live in TypeDoc:

Use A Custom Cache

class LocalStorageTemplateCache {
  constructor(prefix = "tpl:") {
    this.prefix = prefix;
  }

  key(name) {
    return `${this.prefix}${name}`;
  }

  get(name) {
    const value = localStorage.getItem(this.key(name));
    return value === null ? undefined : value;
  }

  set(name, value) {
    localStorage.setItem(this.key(name), value);
    return this;
  }

  has(name) {
    return localStorage.getItem(this.key(name)) !== null;
  }

  delete(name) {
    localStorage.removeItem(this.key(name));
    return true;
  }

  clear() {
    Object.keys(localStorage)
      .filter((key) => key.startsWith(this.prefix))
      .forEach((key) => localStorage.removeItem(key));
  }
}

angular.module("demo", []).config(($templateCacheProvider) => {
  $templateCacheProvider.cache = new LocalStorageTemplateCache();
});

Custom caches are useful when templates should survive reloads, be shared across tabs, or be backed by another browser storage layer.

For service usage, see $templateCache.

5.16 - $templateRequestProvider

Template request service

6 - Service API

6.1 - $anchorScroll

Anchor scroll service

Description

$anchorScroll service scrolls to the element hash or (if omitter) to the current value of $location.getHash(), according to the rules speciffied in the HTML spec.

It also watches the URL hash and automatically scrolls to any matched anchor whenever it is changed by $location. This can be disabled by a setting on $anchorScrollProvider.

Additionally, you can use the yOffset property to specify a vertical scroll-offset (either fixed or dynamic).

Example

<style>
  #scrollArea {
    height: 100px;
    overflow: auto;
  }

  #bottom {
    display: block;
    margin-top: 2000px;
  }
</style>
<section ng-app="demo">
  <div id="scrollArea" ng-controller="ScrollController">
    <button class="bottom" ng-click="gotoBottom()">Go to bottom</button>
    <a id="bottom"></a> You're at the bottom!
  </div>
</section>
window.angular.module('demo', []).controller('ScrollController', [
  '$scope',
  '$location',
  '$anchorScroll',
  function ($scope, $location, $anchorScroll) {
    $scope.gotoBottom = function () {
      // set the location.hash to the id of
      // the element you wish to scroll to.
      $location.setHash('bottom');

      // call $anchorScroll()
      $anchorScroll();
    };
  },
]);

Demo

You're at the bottom!

The example below illustrates the use of a vertical scroll-offset (specified as a fixed value).

Example

<style>
  .demo2 {
    height: 100px;
    overflow: auto;
  }

  .anchor {
    border: 2px dashed DarkOrchid;
    padding: 10px 10px 400px 10px;
  }

  .fixed-header {
    background-color: rgba(0, 0, 0, 0.2);
    height: 50px;
  }

  .fixed-header > a {
    display: inline-block;
    margin: 5px 15px;
  }
</style>

<section id="demo2" class="demo2">
  <div ng-controller="headerCtrl" class="fixed-header">
    <button class="btn" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
      Go to anchor {{x}}
    </button>
  </div>
  <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
    Anchor {{x}} of 5
  </div>
</section>
window.angular
  .module('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;
        const newHash = 'anchor' + x;
        if ($location.getHash() !== newHash) {
          // set the $location.hash to `newHash` and
          // $anchorScroll will automatically scroll to it
          $location.setHash('anchor' + x);
        } else {
          // call $anchorScroll() explicitly,
          // since $location.hash hasn't changed
          $anchorScroll();
        }
      };
    },
  ]);

window.angular.bootstrap(document.getElementById('demo2'), ['demo2']);

Demo

Anchor {{x}} of 5

For detailed method description, see $anchorScroll.

6.2 - $compile

Template compilation service

6.3 - $controller

Controller service

6.4 - $cookie

Provides read/write access to browser’s cookies

Description

The $cookie service offers a simple API for interacting with browser cookies in AngularTS applications. It allows you to:

  • Read existing cookies
  • Write new cookies
  • Delete cookies
  • Store and retrieve JavaScript objects (via JSON serialization)
  • Automatically URI-encode and decode values

Example

angular.module('app').controller(
  'UserCtrl',
  /** @param {ng.CookieService} $cookie */
  function ($cookie) {
    // Write cookie
    $cookie.put('session_id', 'abc123');

    // Read cookie
    const session = $cookie.get('session_id');

    // Store object
    $cookie.putObject('profile', { name: 'Alice', admin: true });

    // Read object
    const profile = $cookie.getObject('profile');

    // Remove cookie
    $cookie.remove('session_id');
  },
);

Cookie behavior can be customized globally using $cookieProvider.defaults. Cookies are limited to roughly 4 KB, including key and value, so void storing large objects; prefer identifiers or short tokens.

For detailed method description, see CookieService

6.5 - $eventBus

Pubsub messaging service

Description

A messaging service, backed by an instance of PubSub. This implementation is based on the original Google Closure PubSub but uses queueMicrotask instead of Window.setTimeout() for its async implementation.

$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

<section ng-app="demo">
  <div ng-controller="DemoCtrl as $ctrl">
    Milliseconds elapsed since the epoch <b>{{ $ctrl.ms }}</b>
  </div>
</section>

<!--We are using a regular onclick and `angular` global -->
<button
  class="btn btn-dark"
  onclick="angular.$eventBus.publish('demo', Date.now())"
>
  Publish time
</button>
window.angular.module('demo', []).controller(
  'DemoCtrl',
  class {
    static $inject = ['$eventBus', '$scope'];
    constructor($eventBus, $scope) {
      const unsubscribe = $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 }}

For detailed method description, see PubSub


6.6 - $exceptionHandler

Error handling service

Description

$exceptionHandler is the central hook for uncaught exceptions inside an AngularTS application. The framework routes all errors from synchronous code, async tasks, expression evaluation, and dependency injection through this service.

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.

For type description, see ng.ExceptionHandler.

For service customisation, see ng.ExceptionHandlerProvider

6.7 - $http

HTTP client service

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:

Usage

Call $http with a full request configuration when you need explicit control:

$http<User>({
  method: 'GET',
  url: '/api/users/42',
}).then((response) => {
  user = response.data;
});

For common HTTP verbs, use shorthand methods:

$http.get<User>('/api/users/42').then(({ data }) => {
  user = data;
});

$http.post<User>('/api/users', {
  name: 'Ada',
  email: 'ada@example.com',
});

Request Behavior

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.

$http.get<Article[]>('/api/articles', {
  params: {
    category: 'news',
    tags: ['featured', 'breaking'],
  },
});

Response Behavior

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.';
  } else if (error.status === 404) {
    message = 'User not found.';
  }
});

Defaults

Configure defaults before bootstrap with $httpProvider.defaults, or mutate runtime defaults through $http.defaults.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.defaults.headers.common.Authorization = 'Bearer token';
  $httpProvider.defaults.withCredentials = true;
});

Default response transforms strip AngularJS JSON protection prefixes and parse JSON responses automatically.

Interceptors

Interceptors are registered with $httpProvider.interceptors. Request hooks run in registration order; response hooks run in reverse order.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.interceptors.push(() => ({
    request(config) {
      config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
      return config;
    },

    responseError(rejection) {
      if (rejection.status === 401) {
        window.location.href = '/login';
      }

      return Promise.reject(rejection);
    },
  }));
});

XSRF

$http reads the configured XSRF cookie and sends it in the configured XSRF header for trusted origins. Add cross-origin APIs explicitly:

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
});

6.8 - $interpolate

HTTP service

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

Exact signatures live in TypeDoc:

Read The Current URL

$location.path();
$location.search();
$location.hash();
$location.url();
$location.absUrl();

Update The URL

$location
  .path("/settings")
  .search({ tab: "profile" })
  .hash("details");

Setter methods return $location, so updates can be chained.

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

$rootScope.$on("$locationChangeStart", (event, newUrl, oldUrl) => {
  if (shouldBlock(newUrl, oldUrl)) {
    event.preventDefault();
  }
});

$rootScope.$on("$locationChangeSuccess", (_event, newUrl) => {
  analytics.track("page_view", { url: newUrl });
});

For provider configuration, see $locationProvider.

6.10 - $log

Logging service

Using $log

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.

Example*
angular.module('demo').controller('MyController', ($log) => {
  $log.log('log');
  $log.info('info');
  $log.warn('warn!');
  $log.error('error');
  $log.debug('debug');
});

To reveal the location of the calls to $log in the JavaScript console, you can “blackbox” the AngularTS source in your browser:

Note: Not all browsers support blackboxing.
The default is to log debug messages. You can use $logProvider.debug to change this.

For configuration and custom implementations, see $logProvider.

Decorating $log

You can also optionally override any of the $log service methods with $provide decorator. Below is a simple example that overrides default $log.error to log errors to both console and a backend endpoint.

Example*
angular.module('demo').config(($provide) => {
  $provide.decorator('$log', ($delegate, $http, $exceptionHandler) => {
    const originalError = $delegate.error;
    $delegate.error = () => {
      originalError.apply($delegate, arguments);
      const errorMessage = Array.prototype.slice.call(arguments).join(' ');
      $http.post('/api/log/error', { message: errorMessage });
    };
    return $delegate;
  });
});

Overriding console

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.module('demo').run(($http) => {
  /**
   * Decorate console.error to log error messages to the server.
   */
  const $delegate = window.console.error;
  window.console.error = (...args) => {
    try {
      const errorMessage = args
        .map((arg) => (arg instanceof Error ? 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

6.11 - $machine

Reactive mode machines with declarative transitions

$machine

$machine creates a small reactive mode 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: current 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 modes.

Use $workflow when the flow also needs command boundaries, diagnostics, snapshots, restore, retry, or repeat.

Create a Machine

Inject $machine and assign the created machine to a controller or scope property:

app.controller('SessionCtrl', function ($machine) {
  this.session = $machine({
    initial: 'setup',
    data: {
      roomId: '',
      error: '',
    },
    transitions: {
      setup: {
        join(data, message) {
          data.roomId = message.roomId;
          return 'waiting';
        },
      },
      waiting: {
        matched(data, message) {
          data.roomId = message.roomId;
          return 'playing';
        },
        unavailable(data, reason) {
          data.error = reason;
          return 'setup';
        },
      },
    },
  });
});

Docs call current a mode. Transition handlers return a mode string to move to another mode.

Register a Named Machine

Use module.machine(name, config) when a machine should be injectable by name:

app.machine('sessionMachine', {
  initial: 'setup',
  data: {
    roomId: '',
  },
  transitions: {
    setup: {
      join(data, message) {
        data.roomId = message.roomId;
        return 'waiting';
      },
    },
  },
});

app.controller('SessionCtrl', function (sessionMachine) {
  this.session = sessionMachine;
});

You can also provide a resolvable config factory so it can read injectables at registration time:

function sessionMachineConfig(appSettings) {
  return {
    initial: appSettings.initialMode,
    data: {
      roomId: '',
      error: '',
    },
    transitions: {
      setup: {
        join(data, message) {
          data.roomId = message.roomId;

          return 'waiting';
        },
      },
    },
  };
}

sessionMachineConfig.$inject = ['appSettings'];

app.machine('sessionMachine', sessionMachineConfig);

This is useful when defaults depend on environment, user state, 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:

<button ng-disabled="!$ctrl.session.matches('setup')">Random</button>

<div ng-show="$ctrl.session.matches('waiting')">Waiting for opponent...</div>

<span>{{ $ctrl.session.data.roomId }}</span>

Runtime API

session.current; // current mode string
session.data; // reactive data object
session.matches('setup'); // true when current mode is setup
session.can('join', payload); // true when current mode can run join
session.send('join', payload);
session.snapshot(); // structured clone of { current, data }
session.restore(snapshot);

send(type, payload) returns true when a transition entry exists for the current mode, its optional guard passes, and its handler runs. Missing transitions and blocked guarded transitions return false and do not throw. $machine does not record diagnostics for blocked or missing transitions; use $workflow when failures need structured evidence, retry, or recovery.

Transition return values:

  • return a mode string to change current
  • return false, undefined, an empty string, a non-string value, or no value to stay in the current mode

can(type, payload) checks the transition table and any optional guard for the current mode. It does not run the transition target. Guards should be side-effect-free because templates may call can() repeatedly.

Guarded Transitions

Use a transition descriptor when a transition has a cheap synchronous condition that templates or controllers should be able to ask about through can():

const session = $machine({
  initial: 'setup',
  data: {
    roomId: '',
    inviteCode: '',
  },
  transitions: {
    setup: {
      join: {
        guard(data, message) {
          return !!data.inviteCode && message.roomId !== '';
        },
        target(data, message) {
          data.roomId = message.roomId;

          return 'waiting';
        },
      },
    },
  },
});

session.can('join', { roomId: 'abc' }); // evaluates guard only
session.send('join', { roomId: 'abc' }); // runs target when guard passes

The original function shorthand remains valid:

join(data, message) {
  data.roomId = message.roomId;

  return 'waiting';
}

Guards and transitions are synchronous. Put async work, retries, diagnostics, and command recovery in $workflow, then use a machine transition to project the resulting mode/data into the UI.

Example: Tic Tac Toe

A machine is useful when user actions must follow a small set of legal modes. 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 mode, so further moves return false.

app.controller('GameCtrl', function ($machine) {
  const wins = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];

  const winnerOf = (board) => {
    for (const [a, b, c] of wins) {
      const mark = board[a];

      if (mark !== '-' && mark === board[b] && mark === board[c]) {
        return mark;
      }
    }

    return '';
  };

  this.game = $machine({
    initial: 'playing',
    data: {
      board: ['-', '-', '-', '-', '-', '-', '-', '-', '-'],
      nextPlayer: 'X',
      winner: '',
      moveCount: 0,
      lastError: '',
    },
    transitions: {
      playing: {
        move(data, { index }) {
          if (
            !Number.isInteger(index) ||
            index < 0 ||
            index >= data.board.length ||
            data.board[index] !== '-'
          ) {
            data.lastError = 'invalid_move';
            return false;
          }

          const player = data.nextPlayer;

          data.board[index] = player;
          data.moveCount += 1;
          data.lastError = '';

          const winner = winnerOf(data.board);

          if (winner) {
            data.winner = winner;
            return winner === 'X' ? 'xWon' : 'oWon';
          }

          if (data.moveCount === data.board.length) {
            return 'draw';
          }

          data.nextPlayer = player === 'X' ? 'O' : 'X';
          return 'playing';
        },
      },
    },
  });
});
<button
  ng-repeat="cell in $ctrl.game.data.board track by $index"
  ng-disabled="!$ctrl.game.matches('playing') || cell !== '-'"
  ng-click="$ctrl.game.send('move', { index: $index })"
>
  {{ cell }}
</button>

<p ng-show="$ctrl.game.matches('playing')">
  Next: {{ $ctrl.game.data.nextPlayer }}
</p>

<p ng-show="$ctrl.game.data.winner">Winner: {{ $ctrl.game.data.winner }}</p>

The transition owns both validation and projection. A valid move mutates data.board, updates whose turn is next, and returns the next mode. An invalid move records lastError and returns false, leaving current unchanged. Machine transitions do not roll back data mutations when they return false or throw.

Persist With Transition Hooks

Transition hooks are a convenient place to persist the game after each handled move. The hook runs after current has been updated, so the snapshot contains the terminal mode when the move ends the game.

app.controller('GameCtrl', function ($machine) {
  const storageKey = 'tic-tac-toe';

  this.game = $machine({
    initial: 'playing',
    data: {
      board: ['-', '-', '-', '-', '-', '-', '-', '-', '-'],
      nextPlayer: 'X',
      winner: '',
      moveCount: 0,
      lastError: '',
    },
    transitions: {
      playing: {
        move(data, { index }) {
          // Same move logic as above.
        },
      },
    },
    hooks: {
      transition({ machine }) {
        localStorage.setItem(storageKey, JSON.stringify(machine.snapshot()));
      },
    },
  });

  const saved = localStorage.getItem(storageKey);

  if (saved) {
    this.game.restore(JSON.parse(saved));
  }
});

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 machine state.

Snapshots

Use snapshot() to capture the durable machine state. A snapshot is a structured clone of { current, data }:

const snapshot = session.snapshot();

localStorage.setItem('session', JSON.stringify(snapshot));

A snapshot contains only current 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:

const snapshot = JSON.parse(localStorage.getItem('session'));

session.restore(snapshot);

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 does not run transition hooks.

Hooks

Use hooks.enter and hooks.exit for mode-specific effects. Use hooks.transition for logging or cross-cutting work that should run after any handled transition, including same-mode transitions:

const session = $machine({
  initial: 'setup',
  data: {
    status: 'idle',
  },
  transitions: {
    setup: {
      join() {
        return 'waiting';
      },
    },
    waiting: {
      matched() {
        return 'playing';
      },
    },
  },
  hooks: {
    exit: {
      setup({ data }) {
        data.status = 'joining';
      },
    },
    enter: {
      waiting({ data }) {
        data.status = 'waiting';
      },
    },
    transition({ type, from, to }) {
      console.log(`${type}: ${from} -> ${to}`);
    },
  },
});

Hook context contains type, from, to, payload, data, and machine. exit runs before current changes, enter runs after current changes, and transition runs after mode-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/current 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 mode 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.

An explicit $machine($scope, config) call is also supported when a caller wants to bind the machine to a scope immediately, but assigning the machine to a controller or scope property is usually enough. Named machines registered with module.machine() follow the same rule: they bind when a scope proxy observes them.

TypeScript

import { defineMachine } from '@angular-wave/angular.ts';

type SessionData = {
  roomId: string;
  error: string;
};

type SessionEvents = {
  join: { roomId: string };
  fail: string;
  reset: undefined;
};

const config = defineMachine<SessionData, SessionEvents>({
  initial: 'setup',
  data: {
    roomId: '',
    error: '',
  } satisfies SessionData,
  transitions: {
    setup: {
      join(data, message) {
        data.roomId = message.roomId;
        return 'waiting';
      },
    },
  },
});

const session = $machine(config);

session.send('join', { roomId: 'abc' });
session.send('reset');

Machine definitions are strict by default in TypeScript. If no event map is provided, send() has no valid event names and transition maps accept no event keys. Use defineMachine<Data, Events>() for checked event names and payloads, or opt into ng.MachineEventMap when you intentionally need dynamic event names.

6.12 - $parse

URL normalization for HTML5/hashbang modes

6.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:

Creating A Resource

Inject $rest and call it with a base URL:

const posts = $rest<Post, number>('/api/posts');

The returned RestService supports common CRUD workflows:

const all = await posts.list();
const one = await posts.get(42);
const created = await posts.create({ title: 'Hello' } as Post);
const updated = await posts.update(42, { title: 'Updated' });
const deleted = await posts.delete(42);

Entity Mapping

Pass an entity class when raw JSON should be converted into richer objects.

class Post {
  id!: number;
  title!: string;
  createdAt: Date;

  constructor(raw: any) {
    Object.assign(this, raw);
    this.createdAt = new Date(raw.created_at);
  }
}

const posts = $rest<Post, number>('/api/posts', Post);
const post = await posts.get(1);

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.

const posts = $rest<Post, number>('/api/posts', Post, {
  headers: { 'X-Tenant': tenantId },
  withCredentials: true,
});

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.

For cached reads, wrap the HTTP backend with CachedRestBackend:

import {
  CachedRestBackend,
  HttpRestBackend,
} from '@angular-wave/angular.ts/services/rest';
import type {
  RestCacheStore,
  RestResponse,
} from '@angular-wave/angular.ts/services/rest';

class MapRestCacheStore implements RestCacheStore {
  private cache = new Map<string, RestResponse<unknown>>();

  async get<T>(key: string): Promise<RestResponse<T> | undefined> {
    return this.cache.get(key) as RestResponse<T> | undefined;
  }

  async set<T>(key: string, response: RestResponse<T>): Promise<void> {
    this.cache.set(key, response as RestResponse<unknown>);
  }

  async delete(key: string): Promise<void> {
    this.cache.delete(key);
  }

  async deletePrefix(prefix: string): Promise<void> {
    for (const key of this.cache.keys()) {
      if (key.startsWith(prefix)) {
        this.cache.delete(key);
      }
    }
  }
}

const posts = $rest<Post, number>('/api/posts', Post, {
  backend: new CachedRestBackend({
    network: new HttpRestBackend($http),
    cache: new MapRestCacheStore(),
    strategy: 'network-first',
  }),
});

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().

const repos = $rest<Repo>('/api/{org}/repos');

const angularRepos = await repos.list({
  org: 'angular-wave',
  type: 'public',
});

The org value expands into the path. Remaining params are forwarded to $http as query parameters.

Provider Registration

Register definitions during configuration with $restProvider.rest() when a resource should be available from shared setup code.

angular.module('app', []).config(($restProvider) => {
  $restProvider.rest('posts', '/api/posts', Post);
});

Demo

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.

6.14 - $rootElement

URL normalization for HTML5/hashbang modes

6.15 - $rootScope

Root scope service

6.16 - $sce

Strict Contextual Escaping service

$sceProvider and $sce Documentation

$sceProvider

The $sceProvider provider allows developers to configure the $sce service.

  • Enable/disable Strict Contextual Escaping (SCE) in a module
  • Override the default implementation with a custom delegate

Read more about Strict Contextual Escaping (SCE).


$sce

$sce is a service that provides Strict Contextual Escaping (SCE) services to AngularTS.


Strict Contextual Escaping

Overview

Strict Contextual Escaping (SCE) is a mode in which AngularTS constrains bindings to only render trusted values. Its goal is to:

  • Help you write secure-by-default code.
  • Simplify auditing for vulnerabilities such as XSS and clickjacking.

By default, AngularTS treats all values as untrusted in HTML or sensitive URL bindings. When binding untrusted values, AngularTS will:

  • Sanitize or validate them based on context.
  • Or throw an error if it cannot guarantee safety.

Example — ng-bind-html renders its value directly as HTML (its “context”). If the input is untrusted, AngularTS will sanitize or reject it.

To bypass sanitization, you must mark a value as trusted before binding it.

Note: Since version 1.2, AngularTS ships with SCE enabled by default.


Example: Binding in a Privileged Context

<input ng-model="userHtml" aria-label="User input" />
<div ng-bind-html="userHtml"></div>

If SCE is disabled, this allows arbitrary HTML injection — a serious XSS risk.

To safely render user content, you should sanitize the HTML (on the server or client) before binding it.

SCE ensures that only trusted, validated, or sanitized values are rendered.

You can mark trusted values explicitly using:

$sce.trustAs(context, value);

or shorthand methods such as:

$sce.trustAsHtml(value);
$sce.trustAsUrl(value);
$sce.trustAsResourceUrl(value);

How It Works

Directives and Angular internals bind trusted values using:

$sce.getTrusted(context, value);

Example: the ngBindHtml directive uses $sce.parseAsHtml internally:

let ngBindHtmlDirective = [
  '$sce',
  function ($sce) {
    return function (scope, element, attr) {
      scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function (value) {
        element.html(value || '');
      });
    };
  },
];

Impact on Loading Templates

SCE affects both:

  • The ng-include directive
  • The templateUrl property in directives

By default, AngularTS loads templates only from the same domain and protocol as the main document.
It uses:

$sce.getTrustedResourceUrl(url);

To allow templates from other domains, use:

  • $sceDelegateProvider.trustedResourceUrlList
  • Or $sce.trustAsResourceUrl(url)

Note: Browser CORS and Same-Origin policies still apply.


Is This Too Much Overhead?

SCE applies only to interpolation expressions.

Constant literals are automatically trusted, e.g.:

<div ng-bind-html="'<b>implicitly trusted</b>'"></div>

If the ngSanitize module is included, $sceDelegate will use $sanitize to clean untrusted HTML automatically.

AngularTS’ default $sceDelegate allows loading from your app’s domain, blocking others unless explicitly whitelisted.

This small overhead provides major security benefits and simplifies auditing.


Supported Trusted Contexts

ContextDescription
$sce.HTMLSafe HTML (used by ng-bind-html).
$sce.CSSSafe CSS. Currently unused.
$sce.MEDIA_URLSafe media URLs (auto-sanitized).
$sce.URLSafe navigable URLs.
$sce.RESOURCE_URLSafe resource URLs (used in ng-include, iframe, etc.).
$sce.JSSafe JavaScript for execution.

⚠️ Before AngularTS 1.7.0, a[href] and img[src] sanitized directly.
As of 1.7.0, these now use $sce.URL and $sce.MEDIA_URL respectively.


Resource URL List Patterns

Trusted and banned resource URL lists accept:

  • 'self' → matches same domain & protocol
  • Strings with wildcards:
    • * → matches within a single path segment
    • ** → matches across path segments (use carefully)
  • Regular expressions (RegExp)

Caution: Regex patterns are powerful but harder to maintain — use only when necessary.


You can disable SCE globally — though this is strongly discouraged.

angular.module('myAppWithSceDisabled', []).config(function ($sceProvider) {
  // Completely disable SCE. For demonstration purposes only!
  // Do not use in new projects or libraries.
  $sceProvider.enabled(false);
});

6.17 - $templateCache

Map object for storing templates

$templateCache is a Map object created by $templateCacheProvider.

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.

Adding via the script tag:

Example

<script type="text/ng-template" id="templateId.html">
  <p>This is the content of the template</p>
</script>

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

const myApp = angular.module('myApp', []).run(($templateCache) => {
  $templateCache.set('templateId.html', 'This is the content of the template');
});

To retrieve the template, simply use it in your component:

myApp.component('myComponent', {
  templateUrl: 'templateId.html',
});

or include it with ng-include:

<div ng-include="'templateId.html`"></div>

or get it via the $templateCache service:

myApp.controller(
  'Test',
  class {
    constructor($templateCache) {
      const tmp = $templateCache.get('templateId.html');
    }
  },
);

6.18 - $templateRequest

Template request service

6.19 - $url

URL management for state transitions

6.20 - $workflow

Inspectable command workflows built on reactive mode machines

$workflow

$workflow adds command boundaries, diagnostics, history, snapshots, restore, retry, and repeat on top of $machine.

Use $machine when you only need legal modes and transitions. Use $workflow when external work needs to be run, inspected, repaired, repeated, or handed to another process as JSON.

Create a Workflow

Inject $workflow and assign the workflow to a controller or scope property:

app.controller('DocsCtrl', function ($workflow) {
  this.build = $workflow({
    id: 'docs-build',
    initial: 'idle',
    data: {
      status: 'idle',
      output: '',
    },
    transitions: {
      idle: {
        start(data) {
          data.status = 'running';
          return 'running';
        },
      },
      running: {
        complete(data, output) {
          data.status = 'complete';
          data.output = output;
          return 'complete';
        },
        fail(data, reason) {
          data.status = reason;
          return 'failed';
        },
      },
    },
    commands: {
      build({ workflow, data, input }) {
        workflow.send('start');
        data.output = String(input);
        workflow.send('complete', data.output);

        return {
          ok: true,
          output: {
            file: data.output,
          },
        };
      },
    },
  });
});

workflow.current is the current mode. workflow.data is reactive data for templates. workflow.run(name, input) is the explicit boundary for work that can succeed, fail, or be retried.

Commands may be synchronous or async. run(), retry(), and repeat() always return a promise that resolves to a normalized WorkflowCommandResult.

Register a Named Workflow

Use module.workflow(name, config) when a workflow should be injectable by name:

app.workflow('docsWorkflow', {
  id: 'docs',
  initial: 'idle',
  data: {
    runs: 0,
  },
  transitions: {
    idle: {
      start(data) {
        data.runs += 1;
        return 'running';
      },
    },
  },
});

app.controller('DocsCtrl', function (docsWorkflow) {
  this.workflow = docsWorkflow;
});

You can make workflow registration resumable and environment-driven by passing a config factory:

function docsWorkflowConfig(buildConfig) {
  return {
    id: buildConfig.workflowId,
    initial: buildConfig.initialMode,
    data: {
      runs: 0,
      status: buildConfig.initialMode,
    },
    transitions: {
      idle: {
        start(data) {
          data.runs += 1;
          data.status = 'running';

          return 'running';
        },
      },
    },
    commands: {},
  };
}

docsWorkflowConfig.$inject = ['buildConfig'];

app.workflow('docsWorkflow', docsWorkflowConfig);

Named workflows are DI singletons for an injector. Observing scopes can be destroyed without destroying the workflow instance.

Command Diagnostics

Commands return a WorkflowCommandResult, or they can throw. Thrown values are converted to structured diagnostics and partial data mutations are preserved:

const result = await workflow.run('publish', 'index.html');

if (!result.ok) {
  for (const diagnostic of result.diagnostics) {
    console.warn(diagnostic.code, diagnostic.message);
  }
}

Diagnostics are safe to serialize:

const diagnosticsJson = JSON.stringify(workflow.diagnostics);

Snapshot And Restore

Snapshots are the JSON handoff format:

const snapshot = workflow.snapshot();

localStorage.setItem('docsWorkflow', JSON.stringify(snapshot));

const restored = JSON.parse(localStorage.getItem('docsWorkflow'));

workflow.restore(restored);

A snapshot contains:

{
  version: 1,
  id: 'docs-build',
  current: 'failed',
  data: {},
  diagnostics: [],
  history: []
}

restore(snapshot) requires version 1 and a matching workflow id. Restored diagnostics and history entries are normalized into the same JSON-safe shape that live commands produce. Restored history IDs are normalized to unique positive integers. Command inputs and outputs in workflow.history and snapshots are stored as JSON-safe projections. Live workflows still retry or repeat with the original input value; after restore(snapshot), retry and repeat use the serialized input from the snapshot.

Use migrateSnapshot(snapshot) to restore older persisted shapes:

const workflow = $workflow({
  id: 'docs-build',
  initial: 'idle',
  data: {
    title: '',
  },
  transitions: {},
  migrateSnapshot(snapshot) {
    return {
      version: 1,
      id: 'docs-build',
      current: snapshot.state,
      data: {
        title: snapshot.title,
      },
      diagnostics: [],
      history: [],
    };
  },
});

Retry, Repeat, And Repair

retry(commandName?) reruns the latest failed command with its original input:

const retryResult = await workflow.retry('publish');

repeat(commandName?) reruns the latest completed command with its original input:

const repeatResult = await workflow.repeat('publish');

Repair is intentionally configured as a normal command instead of a magic built-in policy:

app.controller('DocsCtrl', function ($workflow) {
  this.workflow = $workflow({
    id: 'repairable-docs',
    initial: 'idle',
    data: {
      title: '',
    },
    transitions: {
      idle: {
        validate(data) {
          return data.title ? 'complete' : 'failed';
        },
      },
      failed: {
        complete() {
          return 'complete';
        },
      },
    },
    commands: {
      validate({ workflow }) {
        workflow.send('validate');

        return workflow.matches('complete')
          ? { ok: true }
          : {
              ok: false,
              diagnostics: [
                {
                  code: 'docs.missingTitle',
                  message: 'Missing title.',
                  recoverable: true,
                },
              ],
            };
      },
      repair({ workflow, data, input }) {
        data.title = String(input);
        workflow.send('complete');

        return {
          ok: true,
        };
      },
    },
  });
});
await workflow.run('validate');
await workflow.run('repair', 'Guide');

Diagnostics and history are append-only in v1. Repair, retry, and repeat add evidence; they do not erase the commands that made recovery or replay necessary.

Production Policies

Concurrent command calls are allowed by default. Set concurrency to reject or queue overlapping runs for the same command:

const workflow = $workflow({
  id: 'publish',
  concurrency: 'queue',
  initial: 'idle',
  data: {},
  transitions: {},
  commands: {
    publish() {
      return { ok: true };
    },
  },
});

Per-run options can override the workflow default:

await workflow.run('publish', payload, { concurrency: 'reject' });

Use commandTimeout or per-run timeout to fail commands that exceed a time budget. Timeout and cancellation resolve the command result with diagnostics; commands receive an AbortSignal so async work can stop early:

const workflow = $workflow({
  id: 'publish',
  commandTimeout: 5000,
  initial: 'idle',
  data: {},
  transitions: {},
  commands: {
    async publish({ cleanup, signal }) {
      const controller = new AbortController();

      cleanup(() => controller.abort());
      signal.addEventListener('abort', () => controller.abort(), {
        once: true,
      });

      await fetch('/publish', { signal: controller.signal });

      return { ok: true };
    },
  },
});

workflow.cancel('publish');

cleanup(callback) runs after the command resolves, fails, times out, or is cancelled. Use it for timers, event listeners, observers, and request controllers created by the command.

restore(snapshot) is a hard recovery boundary. It cancels running commands, prevents queued commands from starting, and ignores late writes from cancelled command continuations. Command code should still observe signal.aborted so it can release external resources promptly.

Diagnostics and history are bounded to 1000 entries by default. Use diagnosticLimit and historyLimit to choose different bounds:

const workflow = $workflow({
  id: 'bounded',
  diagnosticLimit: 100,
  historyLimit: 100,
  initial: 'idle',
  data: {},
  transitions: {},
});

Runtime API

workflow.current;
workflow.data;
workflow.diagnostics;
workflow.history;
workflow.matches('idle');
workflow.can('start');
workflow.send('start');
workflow.run('publish', payload, { timeout: 5000 });
workflow.retry('publish', { concurrency: 'reject' });
workflow.repeat('publish');
workflow.cancel('publish');
workflow.snapshot();
workflow.restore(snapshot);

TypeScript callers can specify the expected command output type at the command boundary:

const result = await workflow.run<{ file: string }>('publish', payload);

if (result.ok) {
  result.output?.file;
}

Workflow definitions are strict by default in TypeScript. If no event or command map is provided, send(), run(), retry(), and repeat() have no valid names. Use defineWorkflow<Data, Events, Commands>() and defineCommand() for checked event names, command names, inputs, and outputs:

import { defineCommand, defineWorkflow } from '@angular-wave/angular.ts';

type DocsData = {
  output: string;
};

type DocsEvents = {
  complete: { output: string };
};

type DocsCommands = {
  publish: ng.WorkflowCommand<DocsData, string, { file: string }, DocsEvents>;
};

const config = defineWorkflow<DocsData, DocsEvents, DocsCommands>({
  id: 'docs',
  initial: 'idle',
  data: {
    output: '',
  } satisfies DocsData,
  transitions: {
    idle: {
      complete(data, payload) {
        data.output = payload.output;
        return 'complete';
      },
    },
  },
  commands: {
    publish: defineCommand<DocsData, string, { file: string }, DocsEvents>(
      ({ workflow, input }) => {
        workflow.send('complete', { output: input });

        return {
          ok: true,
          output: {
            file: input,
          },
        };
      },
    ),
  },
});

const workflow = $workflow(config);
const result = await workflow.run('publish', 'index.html');

When a command map is provided, commands is required and every declared command key must be present and callable. This keeps workflow.run('name') aligned with the runtime command table.

When a command calls another command, pass the full command map to defineCommand() so context.workflow.run() is checked too:

type DocsCommands = {
  build: ng.WorkflowCommand<
    DocsData,
    string,
    { file: string },
    DocsEvents,
    DocsCommands
  >;
  publish: ng.WorkflowCommand<
    DocsData,
    { file: string },
    { url: string },
    DocsEvents,
    DocsCommands
  >;
};

const build = defineCommand<
  DocsData,
  string,
  { file: string },
  DocsEvents,
  DocsCommands
>(({ workflow, input }) => {
  workflow.run('publish', { file: input });

  return {
    ok: true,
    output: {
      file: input,
    },
  };
});

Use ng.WorkflowCommandMap only when you intentionally need dynamic command names. Dynamic command inputs are typed as unknown; narrow the value inside the command before using it.

7 - Service Guides

7.1 - Making HTTP requests with the $http service

Send requests with the $http service and configure defaults, transforms, interceptors, and XSRF behavior.

Use $http for application HTTP calls when you need request configuration, response transforms, interceptors, or integration with AngularTS services.

This guide focuses on workflows. Exact call signatures and exported interfaces live in TypeDoc:

Basic Requests

$http.get<User>('/api/users/42').then(({ data }) => {
  this.user = data;
});

Use a full request config when method, URL, headers, params, timeout, response type, or upload handlers need to be assembled together.

$http({
  method: 'POST',
  url: '/api/users',
  data: { name: 'Ada' },
  headers: { 'X-Trace': traceId },
}).then(({ data }) => {
  this.user = data;
});

Query Parameters

params are appended to the URL using $httpParamSerializer.

$http.get<Article[]>('/api/articles', {
  params: {
    category: 'news',
    tags: ['featured', 'breaking'],
  },
});

The default serializer repeats array keys, JSON-encodes object values, sorts keys alphabetically, and omits null, undefined, and function values.

Request Bodies

Plain objects are JSON serialized by default. FormData, Blob, and File values are sent as native browser payloads.

const formData = new FormData();
formData.append('avatar', fileInput.files[0]);

$http.post('/api/users/42/avatar', formData, {
  uploadEventHandlers: {
    progress(event: ProgressEvent) {
      this.progress = Math.round((event.loaded / event.total) * 100);
    },
  },
});

Headers And Defaults

Set application-wide defaults during module configuration:

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.defaults.headers.common.Authorization = 'Bearer token';
  $httpProvider.defaults.withCredentials = true;
});

Runtime defaults are available through $http.defaults:

angular.module('app').run(($http) => {
  $http.defaults.headers.common['X-App-Version'] = '2.1.0';
});

Interceptors

Interceptors centralize cross-cutting request and response behavior such as auth headers, retries, logging, and redirects.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.interceptors.push(() => ({
    request(config) {
      config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
      return config;
    },

    responseError(rejection) {
      if (rejection.status === 401) {
        window.location.href = '/login';
      }

      return Promise.reject(rejection);
    },
  }));
});

Error Handling

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.';
  } else if (error.status === 404) {
    this.error = 'User not found.';
  } else if (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.

angular.module('app', []).config(($httpProvider) => {
  $httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
});

Next Steps

7.2 - Decoupled Messaging

Use $eventBus for application-wide publish/subscribe messaging and scope events for scope-tree communication.

AngularTS has two messaging models:

  • $eventBus is an application-wide PubSub instance for decoupled communication, including messages from non-Angular code.
  • Scope events ($scope.$on, $scope.$emit, $scope.$broadcast) travel through the scope tree and are best for parent/child communication.

Exact $eventBus method signatures live in TypeDoc:

Publish and subscribe

Inject $eventBus when a publisher and subscriber should not know about each other.

class CartService {
  static $inject = ["$eventBus"];

  constructor(private $eventBus: ng.PubSub) {}

  addItem(product: Product, quantity: number) {
    this.$eventBus.publish("cart:item-added", { product, quantity });
  }
}

class HeaderController {
  static $inject = ["$eventBus", "$scope"];

  cartCount = 0;

  constructor($eventBus: ng.PubSub, $scope: ng.Scope) {
    const unsubscribe = $eventBus.subscribe("cart:item-added", () => {
      const view = $scope["$ctrl"] as HeaderController;

      view.cartCount += 1;
    });

    $scope.$on("$destroy", unsubscribe);
  }
}

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.

class AnalyticsBootstrap {
  static $inject = ["$eventBus"];

  constructor($eventBus: ng.PubSub) {
    $eventBus.subscribeOnce("analytics:ready", (sdk) => {
      sdk.track("session_start");
    });
  }
}

window.onAnalyticsReady = (sdk) => {
  angular.$eventBus.publish("analytics:ready", sdk);
};

Async delivery

$eventBus schedules delivery with queueMicrotask. publish() returns after scheduling the event, and subscribers run after the current call stack.

$eventBus.subscribe("order:created", (order) => {
  console.log("subscriber", order.id);
});

$eventBus.publish("order:created", { id: 42 });
console.log("publisher finished");

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 error to $exceptionHandler and continues delivering the event to the remaining subscribers.

$eventBus.subscribe("order:created", () => {
  throw new Error("failed listener");
});

$eventBus.subscribe("order:created", (order) => {
  console.log("still delivered", order.id);
});

This keeps one failing listener from blocking unrelated subscribers.

Scope events

Use scope events when the relationship is already expressed by the scope tree.

MethodDirectionUse for
$scope.$broadcast(event, args)Down to descendantsParent notifying child scopes
$scope.$emit(event, args)Up toward $rootScopeChild notifying parents
$scope.$on(event, handler)Current scope listenerLocal event handling and cleanup
$scope.$broadcast("filter:changed", { status: "active" });

$scope.$on("filter:changed", (_event, filter) => {
  this.applyFilter(filter);
});

$scope.$emit("child:ready");

Scope listeners are synchronous and return a deregistration function. Pair them with $destroy when the listener can outlive the current view.

const off = $scope.$on("filter:changed", handler);
$scope.$on("$destroy", off);

Choosing a messaging model

ConcernPrefer $eventBusPrefer scope events
Publisher and subscriber do not share a scope ancestryYesNo
Event comes from non-Angular codeYesNo
Communication is strictly parent/childUsually noYes
Delivery should be asyncYesNo
Listener cleanup should be tied to a scopeWorks with $destroyBuilt 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.

7.3 - Real-Time Communication

Stream server events with $sse, exchange bidirectional messages with $websocket or $webTransport, and offload work with ng-worker and ng-wasm.

AngularTS provides five building blocks for real-time and compute-heavy work:

  • $sse for Server-Sent Events.
  • $websocket for bidirectional WebSocket connections.
  • $webTransport for HTTP/3 WebTransport sessions with datagrams and streams.
  • ng-worker for JavaScript work in a Web Worker.
  • ng-wasm for loading WebAssembly modules.

Exact service and connection signatures live in TypeDoc:

Server-Sent Events

$sse creates a managed EventSource connection. It handles query parameters, JSON message parsing, heartbeat detection, automatic reconnection, and clean shutdown.

class NewsFeedController {
  static $inject = ["$sse", "$scope"];

  items: NewsItem[] = [];
  private connection: ng.SseConnection;

  constructor($sse: ng.SseService, $scope: ng.Scope) {
    this.connection = $sse("/api/news/stream", {
      withCredentials: true,
      params: { category: "top" },
      retryDelay: 3000,
      heartbeatTimeout: 30000,
      onMessage: (item: NewsItem) => {
        const view = $scope["$ctrl"] as NewsFeedController;

        view.items = [item, ...view.items].slice(0, 50);
      },
      onReconnect: (attempt) => {
        console.log("SSE reconnect", attempt);
      },
    });

    $scope.$on("$destroy", () => this.connection.close());
  }
}

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.

class ChatController {
  static $inject = ["$websocket", "$scope"];

  messages: ChatMessage[] = [];
  private socket: ng.WebSocketConnection;

  constructor($websocket: ng.WebSocketService, $scope: ng.Scope) {
    this.socket = $websocket("wss://api.example.com/chat", ["v1"], {
      retryDelay: 2000,
      maxRetries: 20,
      onMessage: (message: ChatMessage) => {
        const view = $scope["$ctrl"] as ChatController;

        view.messages = [...view.messages, message];
      },
      onClose: (event) => {
        console.log("WebSocket closed", event.code);
      },
    });

    $scope.$on("$destroy", () => this.socket.close());
  }

  send(text: string) {
    this.socket.send({ type: "message", text });
  }
}

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.

class TelemetryController {
  static $inject = ["$webTransport", "$scope"];

  events: string[] = [];
  private session: ng.WebTransportConnection;

  constructor($webTransport: ng.WebTransportService, $scope: ng.Scope) {
    this.session = $webTransport("https://localhost:4433/webtransport", {
      reconnect: true,
      retryDelay: 500,
      maxRetries: 5,
      requireUnreliable: true,
      transformDatagram: (data) => new TextDecoder().decode(data),
      onDatagram: ({ message }) => {
        const view = $scope["$ctrl"] as TelemetryController;

        view.events = [...view.events, String(message)];
      },
      onReconnect: ({ connection }) => {
        return connection.sendText(JSON.stringify({ subscribe: "telemetry" }));
      },
    });
  }

  send(value: string) {
    return this.session.sendText(value);
  }
}

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.

<div
  ng-web-transport="transportUrl"
  data-config="transportConfig"
  data-mode="datagram"
  data-transform="json"
  data-as="session"
  data-reconnect="true"
  data-on-message="events.push($message)"
  data-on-reconnect="reconnects = $attempt"
  data-on-error="error = $error"
></div>

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

<button
  ng-worker="./workers/compress.js"
  data-params="vm.fileBuffer"
  data-on-result="vm.compressed = $result"
  data-on-error="vm.error = $error"
  trigger="click"
>
  Compress
</button>

<span
  ng-worker="./workers/sensor-reader.js"
  interval="5000"
  data-on-result="vm.sensorData = $result"
></span>

The directive evaluates data-params, posts the result to the worker, and exposes worker responses as $result in data-on-result. When no result expression is provided, it swaps the result into the element using the configured swap strategy.

A worker module receives values through self.onmessage and returns results through self.postMessage.

self.onmessage = function ({ data: { limit } }) {
  const sieve = new Uint8Array(limit + 1).fill(1);
  sieve[0] = sieve[1] = 0;

  for (let i = 2; i * i <= limit; i++) {
    if (sieve[i]) {
      for (let j = i * i; j <= limit; j += i) sieve[j] = 0;
    }
  }

  const primes = [];
  for (let i = 2; i <= limit; i++) {
    if (sieve[i]) primes.push(i);
  }

  self.postMessage(primes);
};

WebAssembly

ng-wasm loads a .wasm file and exposes its exports on the scope under a configurable name.

<div
  ng-wasm
  src="/wasm/image-processor.wasm"
  as="imageProcessor"
></div>
const result = $scope.imageProcessor.grayscale(pixelBuffer, width, height);

WebAssembly loading is asynchronous. Guard calls until the export object exists or trigger work after the directive has linked.

Choosing A Transport

NeedUse
Server pushes one-way updates$sse
Client and server both send messages$websocket
HTTP/3 datagrams or streams$webTransport
CPU-heavy JavaScriptng-worker
Compiled compute moduleng-wasm

7.4 - Typed REST Resources

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.

Exact method signatures live in TypeDoc:

Register a resource

Register shared resources in a config block. The provider stores resource definitions before the application starts, then the $rest factory uses the live $http service at runtime.

class User {
  id: number;
  name: string;
  createdAt: Date;

  constructor(data: any) {
    this.id = data.id;
    this.name = data.name;
    this.createdAt = new Date(data.created_at);
  }
}

angular.module("demo", []).config(($restProvider: ng.RestProvider) => {
  $restProvider.rest("users", "/api/users", User, {
    timeout: 5000,
    withCredentials: true,
  });
});

The resource name is informational in the current API. The URL can be a plain path or an RFC 6570 URI template.

Create a resource at runtime

Inject $rest anywhere you need a resource client. This keeps controllers and services focused on the workflow instead of repeating request setup.

class UserRepository {
  static $inject = ["$rest"];

  private users: ng.RestService<User, number>;

  constructor($rest: ng.RestFactory) {
    this.users = $rest<User, number>("/api/users", User);
  }

  listAdmins() {
    return this.users.list({ role: "admin" });
  }

  getUser(id: number) {
    return this.users.get(id);
  }
}

Use URI templates

$rest expands RFC 6570 templates before sending a request. Template variables are taken from the params object you pass to list() or get().

const issues = $rest<Issue>(
  "/api/repos/{owner}/{repo}/issues{?labels*}",
  Issue,
);

const openBugs = await issues.list({
  owner: "angular-wave",
  repo: "angular.ts",
  labels: ["bug", "ui"],
});

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.

class Article {
  id: number;
  title: string;
  publishedAt: Date;

  constructor(data: any) {
    this.id = data.id;
    this.title = data.title;
    this.publishedAt = new Date(data.published_at);
  }

  get isPublished() {
    return this.publishedAt.getTime() <= Date.now();
  }
}

const articles = $rest<Article, number>("/api/articles", Article);
const article = await articles.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.

class ArticleController {
  static $inject = ["$rest"];

  private articles: ng.RestService<Article, number>;
  items: Article[] = [];

  constructor($rest: ng.RestFactory) {
    this.articles = $rest<Article, number>("/api/articles", Article);
  }

  async publish(draft: Partial<Article>) {
    const created = await this.articles.create(draft as Article);
    this.items.unshift(created as Article);
  }

  async rename(id: number, title: string) {
    const updated = await this.articles.update(id, { title });
    if (updated) {
      this.items = this.items.map((item) =>
        item.id === id ? (updated as Article) : item,
      );
    }
  }

  async remove(id: number) {
    if (await this.articles.delete(id)) {
      this.items = this.items.filter((item) => item.id !== id);
    }
  }
}

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

import {
  CachedRestBackend,
  HttpRestBackend,
} from "@angular-wave/angular.ts/services/rest";
import type {
  RestCacheStore,
  RestResponse,
} from "@angular-wave/angular.ts/services/rest";

class MapRestCacheStore implements RestCacheStore {
  private cache = new Map<string, RestResponse<unknown>>();

  async get<T>(key: string): Promise<RestResponse<T> | undefined> {
    return this.cache.get(key) as RestResponse<T> | undefined;
  }

  async set<T>(key: string, response: RestResponse<T>): Promise<void> {
    this.cache.set(key, response as RestResponse<unknown>);
  }

  async delete(key: string): Promise<void> {
    this.cache.delete(key);
  }

  async deletePrefix(prefix: string): Promise<void> {
    for (const key of this.cache.keys()) {
      if (key.startsWith(prefix)) {
        this.cache.delete(key);
      }
    }
  }
}

const cache = new MapRestCacheStore();
const backend = new CachedRestBackend({
  network: new HttpRestBackend($http),
  cache,
  strategy: "network-first",
});

const articles = $rest<Article, number>("/api/articles", Article, {
  backend,
});

Supported read strategies are cache-first, network-first, and stale-while-revalidate. 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.

class IndexedDbRestBackend implements ng.RestBackend {
  async request<T>(request: ng.RestRequest): Promise<ng.RestResponse<T>> {
    if (request.method === "GET") {
      return { data: (await readFromDb(request.url)) as T };
    }

    throw new Error(`Unsupported method: ${request.method}`);
  }
}

const articles = $rest<Article, number>("/api/articles", Article, {
  backend: new IndexedDbRestBackend(),
});

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.

7.5 - Client-Side Routing

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.

Exact routing API signatures live in TypeDoc:

Work With The URL

Use $location when code needs to inspect or change the raw URL.

$location.path();    // "/dashboard"
$location.search();  // { tab: "overview" }
$location.hash();    // "summary"
$location.url();     // "/dashboard?tab=overview#summary"
$location.absUrl();  // "https://app.example.com/dashboard?tab=overview#summary"

Setter methods return $location, so related URL changes can be chained.

$location
  .path("/settings/profile")
  .search({ tab: "security" })
  .hash("billing-section");

Changes to $location are applied asynchronously. $locationChangeStart and $locationChangeSuccess are broadcast on $rootScope around navigation.

Configure URL Mode

Configure $locationProvider before the application runs.

angular.module("demo", []).config(($locationProvider: ng.LocationProvider) => {
  $locationProvider.html5Mode({
    enabled: true,
    requireBase: false,
    rewriteLinks: true,
  });

  $locationProvider.hashPrefix("!");
});

When requireBase is enabled, the application document must include a <base> tag.

Use $state.go() for normal application navigation. It accepts absolute state names, parent-relative names, and sibling-relative names.

$state.go("contacts.detail", { id: 42 });

$state.go("^.list");

$state.go(".detail", { id: 42 });

$state.go($state.current, $state.params, { reload: true });

go() returns a transition promise. Use transition options when you need to control reloads, parameter inheritance, URL updates, or relative navigation.

Use $state.href() when templates or controllers need a URL without starting navigation.

const relative = $state.href("contacts.detail", { id: 42 });
const absolute = $state.href(
  "contacts.detail",
  { id: 42 },
  { absolute: true },
);

Check Active States

Use is() for exact matches and includes() for ancestors or glob patterns.

$state.is("contacts.detail");
$state.is("contacts.detail", { id: 42 });

$state.includes("contacts");
$state.includes("*.detail");

These helpers are useful for active navigation styling and conditional UI.

Register States At Runtime

$stateRegistry stores state definitions and can register states after bootstrap, which is useful for lazy-loaded feature modules.

const detail = $stateRegistry.get("contacts.detail");
const allStates = $stateRegistry.get();

$stateRegistry.register({
  name: "profile",
  url: "/profile",
  component: "profilePage",
});

Handle Navigation Events

Listen on $rootScope for URL-level events when you need a broad guard.

angular.module("demo").run(($rootScope, $state, authService) => {
  $rootScope.$on("$locationChangeStart", (event, newUrl) => {
    if (newUrl.includes("/admin") && !authService.isAuthenticated()) {
      event.preventDefault();
      $state.go("login", { returnUrl: newUrl });
    }
  });
});

For state-level lifecycle work, prefer transition hooks.

Example: Programmatic Navigation

class OrderController {
  static $inject = ["$state"];

  order!: Order;

  constructor(private $state: ng.StateService) {}

  viewOrder(id: number) {
    this.$state.go("orders.detail", { orderId: id });
  }

  backToList() {
    this.$state.go("^");
  }

  get orderLink(): string | null {
    return this.$state.href("orders.detail", { orderId: this.order.id });
  }
}

7.6 - Cookies And Browser Storage

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.

Exact cookie API signatures live in TypeDoc:

Read Cookies

$cookie decodes keys and values, parses document.cookie, and caches the parsed cookie map until the browser cookie string changes.

const token = $cookie.get("session_token");

const prefs = $cookie.getObject<UserPreferences>("user_prefs");

const all = $cookie.getAll();

Use get() for raw string values and getObject() only for cookies you control and know contain JSON.

Write Cookies

Use put() for strings and putObject() for JSON-serializable values.

$cookie.put("session_token", "abc123", {
  path: "/",
  secure: true,
  samesite: "Strict",
  expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});

$cookie.putObject(
  "user_prefs",
  { theme: "dark", fontSize: 14 },
  {
    path: "/",
    expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
  },
);

Cookie attributes are passed through the CookieOptions object. Common options are path, domain, expires, secure, and samesite.

Remove Cookies

remove() expires the cookie by writing an old expiration date.

$cookie.remove("session_token");

$cookie.remove("session_token", {
  path: "/app",
  domain: ".example.com",
});

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.

Provider Defaults

Set defaults once when every cookie should share the same attributes.

angular.module("demo", []).config(($cookieProvider: ng.CookieProvider) => {
  $cookieProvider.defaults = {
    path: "/",
    secure: true,
    samesite: "Lax",
  };
});

Per-call options are merged on top of provider defaults, so individual writes can still override a field.

Local And Session Storage

AngularTS exposes the browser window object through $window. Inject $window when a service needs localStorage or sessionStorage.

class PreferencesStorage {
  static $inject = ["$window"];

  constructor(private $window: Window & typeof globalThis) {}

  saveTheme(theme: string): void {
    this.$window.localStorage.setItem("theme", theme);
  }

  loadTheme(): string {
    return this.$window.localStorage.getItem("theme") ?? "light";
  }

  saveSessionData(key: string, data: unknown): void {
    this.$window.sessionStorage.setItem(key, JSON.stringify(data));
  }

  loadSessionData<T>(key: string): T | null {
    const raw = this.$window.sessionStorage.getItem(key);
    if (!raw) return null;

    try {
      return JSON.parse(raw) as T;
    } catch {
      return null;
    }
  }
}
ConcernlocalStoragesessionStorage
PersistenceUntil explicitly clearedUntil the browser tab closes
ScopeShared across same-origin tabsIsolated to the current tab
Typical usePreferences and cached dataWizard state and temporary form data

Storage Events

Listen for storage changes from other tabs through $window.

angular.module("demo").run(($window, $rootScope) => {
  $window.addEventListener("storage", (event: StorageEvent) => {
    if (event.key === "theme") {
      $rootScope.$broadcast("themeChanged", event.newValue);
    }
  });
});

The browser only fires storage events in other same-origin tabs or windows, not in the tab that made the change.

Example: Remember Me

class AuthService {
  static $inject = ["$cookie", "$window"];

  constructor(
    private $cookie: ng.CookieService,
    private $window: Window & typeof globalThis,
  ) {}

  login(token: string, rememberMe: boolean) {
    if (rememberMe) {
      this.$cookie.put("auth_token", token, {
        path: "/",
        secure: true,
        samesite: "Strict",
        expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
      });
    } else {
      this.$window.sessionStorage.setItem("auth_token", token);
    }
  }

  getToken(): string | null {
    return (
      this.$cookie.get("auth_token") ??
      this.$window.sessionStorage.getItem("auth_token")
    );
  }

  logout() {
    this.$cookie.remove("auth_token", { path: "/" });
    this.$window.sessionStorage.removeItem("auth_token");
  }
}

8 - Routing

8.1 - State-based routing in AngularTS applications

Learn how AngularTS implements state-based routing with states, views, resolves, transitions, and URL matching configured via $stateProvider.

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 one way to enter a state, but they are not the primary concept—states are.

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, controller, 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 from one state (or set of states) to another, with hooks you can intercept.
  • URL matching — an optional layer that maps browser URLs onto states and keeps them in sync.

How routing integrates with the module system

The router exposes three injectable services that cover the full routing API:

TokenTypePurpose
$stateStateProviderNavigate (go, transitionTo), inspect current state (is, includes, current)
$transitionsTransitionProviderRegister lifecycle hooks (onBefore, onStart, onSuccess, …)
$stateRegistryStateRegistryProviderRegister and deregister state declarations at runtime

All three share a RouterProvider globals object that tracks the current StateObject, the active Transition, and the latest resolved StateParams. The RouterProvider is injected as $router and holds $router.current, $router.transition, and $router.params.

Setting up the router

Register your states during config

Call $stateProvider.state(declaration) inside an Angular config block, or use the equivalent module-level module.state(declaration) convenience. States must have a unique name.

angular.module('app', ['ng.router'])
  .config(function ($stateProvider) {
    $stateProvider
      .state({
        name: 'home',
        url: '/home',
        template: '<h1>Home</h1>'
      })
      .state({
        name: 'contacts',
        url: '/contacts',
        templateUrl: 'contacts.html',
        controller: 'ContactsCtrl'
      })
      .state({
        name: 'contacts.detail',
        url: '/:contactId',
        resolve: {
          contact: function ($transition$, ContactService) {
            return ContactService.get($transition$.params().contactId);
          }
        },
        templateUrl: 'contact-detail.html',
        controller: 'ContactDetailCtrl'
      });
  });

The same states can be registered without an explicit config block:

angular.module('app', ['ng'])
  .state('home', {
    url: '/home',
    template: '<h1>Home</h1>'
  })
  .state('contacts', {
    url: '/contacts',
    templateUrl: 'contacts.html',
    controller: 'ContactsCtrl'
  });

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>
<html ng-app="app">
  <body>
    <nav>
      <a ng-sref="home" ng-sref-active="active">Home</a>
      <a ng-sref="contacts" ng-sref-active="active">Contacts</a>
    </nav>

    <!-- Active state template renders here -->
    <div ng-view></div>
  </body>
</html>

Use $state.go() in controllers or services to perform programmatic navigation.

angular.module('app')
  .controller('ContactsCtrl', function ($state) {
    this.viewContact = function (contactId) {
      $state.go('contacts.detail', { contactId: contactId });
    };
  });

The ng-view directive

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:

<div ng-view></div>

<!-- Named views -->
<div ng-view="header"></div>
<div ng-view="content"></div>
<div ng-view="sidebar"></div>

A state targets named views through its views property:

  name: 'dashboard',
  views: {
    'header': { template: '<app-header></app-header>' },
    'content': { templateUrl: 'dashboard.html', controller: 'DashboardCtrl' },
    'sidebar': { component: 'DashboardSidebar' }
  }
});

The ng-view directive emits $viewContentLoading before the DOM is rendered and $viewContentLoaded after. It also supports autoscroll and onload attributes.

Router directives

ng-sref

ng-sref generates an href for a state and triggers $state.go() on click. The value is a state name optionally followed by a params object:

<a ng-sref="home">Home</a>

<!-- With parameters -->
<a ng-sref="contacts.detail({ contactId: contact.id })">{{ contact.name }}</a>

<!-- Relative navigation -->
<a ng-sref="^">Up to parent</a>
<a ng-sref=".child">Down to child</a>

The ng-sref-opts attribute passes TransitionOptions:

  Dashboard
</a>

ng-sref-active

ng-sref-active adds a CSS class when the linked state (or any of its descendants) is active:

<li ng-sref-active="active">
  <a ng-sref="home">Home</a>
</li>

<!-- Multiple class/state mappings -->
<li ng-sref-active="{ 'active': 'contacts', 'exact': 'contacts' }">
  <a ng-sref="contacts">Contacts</a>
</li>

ng-sref-active-eq works like ng-sref-active but only adds the class when the state is an exact match (uses $state.is() instead of $state.includes()).

ng-state

ng-state is a dynamic alternative to ng-sref. The target state name is read from a scope expression rather than being hard-coded in the attribute:

   ng-state-params="vm.stateParams"
   ng-state-opts="{ inherit: false }">
  Dynamic link
</a>

This is useful when you build navigation menus driven by data.

Explore further

States

Define state hierarchies, configure resolves, and navigate with $state.go().

Transitions

Intercept the transition lifecycle with hooks for auth guards, analytics, and more.

URL matching

Configure parameterized URLs, typed parameters, hash mode, and base href.

Resolve

Fetch data asynchronously before a state is entered, with eager and lazy policies.

8.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 $stateProvider during configuration or $stateRegistry at runtime.

Exact state and router contracts live in TypeDoc:

Register States

Register most states in a config block, or use module.state() for the same provider registration through the fluent module API. Calls to both $stateProvider.state() and module.state() are chainable.

angular.module("demo", []).config(($stateProvider) => {
  $stateProvider
    .state({
      name: "home",
      url: "/home",
      component: "homePage",
    })
    .state({
      name: "contacts",
      url: "/contacts",
      templateUrl: "contacts/list.html",
      controller: "ContactsListCtrl",
      controllerAs: "vm",
    });
});

Equivalent module-level registration:

angular.module("demo", [])
  .state("home", {
    url: "/home",
    component: "homePage",
  })
  .state("contacts", {
    url: "/contacts",
    templateUrl: "contacts/list.html",
    controller: "ContactsListCtrl",
    controllerAs: "vm",
  });

Register states at runtime when a feature is loaded after bootstrap.

angular.module("demo").run(($stateRegistry) => {
  $stateRegistry.register({
    name: "settings",
    url: "/settings",
    component: "settingsPage",
  });
});

Runtime registration requires the parent state to exist first. If the parent is missing, the state is queued until the parent is registered.

Nest States

Use dot notation or an explicit parent property to create a hierarchy.

$stateProvider
  .state({
    name: "contacts",
    url: "/contacts",
    template: "<div ng-view></div>",
  })
  .state({
    name: "contacts.list",
    url: "/list",
    templateUrl: "contacts/list.html",
  })
  .state({
    name: "contacts.detail",
    url: "/:id",
    templateUrl: "contacts/detail.html",
  });

Child states inherit the parent URL prefix. A transition to contacts.detail with { id: 42 } produces /contacts/42.

A parent state must provide a ng-view outlet where child views can render.

Use Abstract States

Abstract states cannot be activated directly. Use them to share a URL prefix, resolves, metadata, or layout with child states.

$stateProvider
  .state({
    name: "admin",
    url: "/admin",
    abstract: true,
    template: "<admin-layout ng-view></admin-layout>",
    resolve: {
      currentUser: (AuthService) => AuthService.currentUser(),
    },
    data: { requiresAuth: true },
  })
  .state({
    name: "admin.dashboard",
    url: "/dashboard",
    component: "adminDashboard",
  });

Navigating to admin.dashboard enters both admin and admin.dashboard.

Declare Parameters

URL parameters are parsed from path and query segments.

$stateProvider.state({
  name: "product",
  url: "/products/:category?page&sort",
  component: "productList",
});

Non-URL parameters belong in the params block.

$stateProvider.state({
  name: "search",
  url: "/search?q",
  params: {
    q: { value: "", squash: true },
    filters: { value: null, type: "any" },
  },
  component: "searchPage",
});

Use parameter declarations for defaults, typed values, dynamic params, array params, squashing, inheritance, and raw URL values.

Resolve Data

Resolves fetch or compute data before a state renders. The router waits for required resolves before entering the state.

$stateProvider.state({
  name: "contacts.detail",
  url: "/:contactId",
  resolve: {
    contact: ($transition$, ContactService) =>
      ContactService.get($transition$.params().contactId),
    contactHistory: [
      "contact",
      "HistoryService",
      (contact, HistoryService) => HistoryService.forContact(contact.id),
    ],
  },
  templateUrl: "contact-detail.html",
  controller($scope, contact, contactHistory) {
    $scope.contact = contact;
    $scope.history = contactHistory;
  },
});

Use array-style resolves when you need explicit tokens, dependency metadata, or resolve policies.

resolve: [
  {
    token: "contact",
    deps: ["$transition$", "ContactService"],
    resolveFn: ($transition$, ContactService) =>
      ContactService.get($transition$.params().contactId),
    policy: { when: "EAGER", async: "WAIT" },
  },
];

Use $state.go() for normal application navigation. It supports absolute states, parent-relative states, sibling-relative states, params, and transition options.

$state.go("contacts.detail", { contactId: 42 });

$state.go("^");

$state.go("^.list");

$state.go(".detail", { contactId: 42 });

$state.go("home", {}, { location: "replace", reload: true });

$state.go() returns a TransitionPromise, which is a promise with the active Transition attached as .transition.

Use transitionTo() only when you need lower-level control.

$state.transitionTo("contacts.detail", { contactId: 42 }, {
  location: true,
  inherit: false,
  reload: false,
  supercede: true,
});

Generate links without navigating:

const url = $state.href("contacts.detail", { contactId: 42 });
const absUrl = $state.href(
  "contacts.detail",
  { contactId: 42 },
  { absolute: true },
);

Check active states:

$state.is("contacts.detail");
$state.is("contacts.detail", { contactId: 42 });

$state.includes("contacts");
$state.includes("contacts.**");
$state.includes("*.detail.*.*");

Reload the current state or an ancestor subtree:

$state.reload();
$state.reload("contacts");

8.3 - Routing transitions and lifecycle hooks

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 TreeChanges object with five paths:

PathDescription
fromAll currently active nodes, from root to the current leaf state
toAll nodes that will be active after the transition
exitingNodes that are active now but will not be active after (in reverse order, deepest first)
retainedNodes that are active both before and after (unchanged)
enteringNodes that are not active now but will be active after (parent first)

The Transition exposes these paths through trans.exiting(), trans.retained(), and trans.entering(), each returning an array of StateDeclaration objects. Use trans.treeChanges(pathname) for the raw PathNode[] arrays.

Each transition has a numeric $id and a promise that resolves to the StateDeclaration for the to-state on success, or rejects with a Rejection 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 ng.TransitionService). 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) {
    return state.data != null && state.data.requiresAuth === 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:

Return valueEffect
undefined / anything elseTransition continues normally
falseTransition is cancelled (aborted)
TargetStateTransition is redirected to the new target
Promise<false>Transition waits for the promise; cancels if it resolves to false
Promise<TargetState>Transition waits for the promise; redirects when it resolves
Rejected PromiseTransition 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) {
    return false; // abort
  }
});

A cancelled transition is rejected with a Rejection of type ABORTED. 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 data.requiresAuth and redirects unauthenticated users to the login page.

  .run(function ($transitions, $state, AuthService) {

    $transitions.onBefore(
      {
        to: function (state) {
          return state.data && state.data.requiresAuth;
        }
      },
      function (transition) {
        if (!AuthService.isAuthenticated()) {
          // Redirect to login, passing the intended destination
          return $state.target('login', {
            redirectTo: transition.to().name
          });
        }
      }
    );

  });

Mark states that require authentication:

  name: 'admin.users',
  url: '/users',
  component: 'AdminUsers',
  data: { requiresAuth: true }
});

Loading indicator

Show a spinner while any transition is in progress:

  .run(function ($transitions, $rootScope) {

    $transitions.onStart({}, function () {
      $rootScope.isLoading = true;
    });

    $transitions.onSuccess({}, function () {
      $rootScope.isLoading = false;
    });

    $transitions.onError({}, function () {
      $rootScope.isLoading = false;
    });

  });

Analytics tracking

Fire a page-view event after every successful navigation:

  .run(function ($transitions, AnalyticsService) {

    $transitions.onSuccess({}, function (transition) {
      var toState = transition.to();
      AnalyticsService.pageView({
        page: toState.name,
        url: window.location.pathname,
        params: transition.params()
      });
    });

  });

Scroll reset

Scroll to the top of the page on each navigation:

  .run(function ($transitions) {

    $transitions.onSuccess({}, function () {
      window.scrollTo(0, 0);
    });

  });

Async guard with redirect on failure

Allow users to authenticate mid-transition:

  var AuthService = transition.injector().get('AuthService');

  if (!AuthService.isAuthenticated()) {
    // Return a promise; transition waits for it to settle
    return AuthService.authenticate().catch(function () {
      return $state.target('guest');
    });
  }
});

Transition results

A transition can finish in one of four states:

ResultDescription
SuccessAll hooks passed, the to-state is now active. transition.success === true.
ErrorA hook threw, returned a rejected promise, or a resolve failed. transition.success === false, transition.error() contains the reason.
IgnoredThe transition targets the same state with the same parameters that are currently active. Treated as success from the transitionTo() perspective.
SupersededA 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.

Accessing the transition object

The active transition is stored on $router.transition. Inside any hook, the Transition instance is passed as the first argument.

  console.log('Transitioned to:', transition.to().name);
  console.log('From:', transition.from().name);
  console.log('Params:', transition.params());
  console.log('Entering:', transition.entering().map(s => s.name));
  console.log('Exiting:', transition.exiting().map(s => s.name));
  console.log('ID:', transition.$id);
});

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:

  transition.addResolvable({
    token: 'requestId',
    resolveFn: function () { return generateRequestId(); }
  });
});

Transition hook ordering: priority and phase

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.

8.4 - 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 UrlService calls sync(). It iterates all registered URL rules in priority order and finds the best match using a weighted scoring system. The winning rule’s handler is called, which calls $state.go() with the matched state and extracted parameter values.

URL rules are created automatically for every state that has a url property. You can also register custom URL rules directly on $urlService._rules.

UrlService.match(url) accepts a UrlParts object ({ path, search, hash }) and returns a MatchResult with the matching rule, the match data, and the match weight. 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.

Path parameters

Use a colon prefix for named path segments:

  name: 'user',
  url: '/users/:userId',
  component: 'UserProfile'
});

// Matches: /users/42 → { userId: '42' }

Use curly braces for parameters with inline type annotations or custom regexps:

  name: 'product',
  url: '/products/{productId:int}',
  component: 'ProductDetail'
});

// Matches: /products/123 → { productId: 123 }  (integer, not string)
// Does NOT match: /products/abc

Custom regexp:

  name: 'article',
  url: '/articles/{slug:[a-z0-9-]+}',
  component: 'Article'
});

Query parameters

Append a ? followed by parameter names. Multiple query params are separated by &:

  name: 'search',
  url: '/search?q&page',
  component: 'SearchResults'
});

// Matches: /search?q=angularts&page=2 → { q: 'angularts', page: '2' }

Typed query params:

  name: 'messages',
  url: '/messages?{before:date}&{after:date}',
  component: 'MessageList'
});

Mixed path and query params:

  name: 'mailbox',
  url: '/messages/:mailboxId?{before:date}&{after:date}',
  component: 'Mailbox'
});

Optional parameters

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

url: '/items/:name'
// { name: 'foo bar' } → /items/foo%20bar

int

Parses URL segments as integers using parseInt. The pattern is /\d+/.

url: '/users/{id:int}'
// /users/42 → { id: 42 }  (number, not string)

bool

Represents boolean values. Encodes true as "1" and false as "0". The pattern matches 1 or 0.

url: '/settings/{darkMode:bool}'
// /settings/1 → { darkMode: true }

json

Encodes arbitrary objects as a JSON string in the URL (URL-encoded). Useful for complex filter objects.

url: '/search?{filters:json}'
// { filters: { status: 'active', role: 'admin' } }
// → /search?filters=%7B%22status%22%3A%22active%22%7D

date

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 UrlConfigProvider

UrlConfigProvider (injected as $urlConfigProvider in config blocks, or accessed via $url._config at runtime) controls global URL matching behavior.

Case sensitivity

URL matching is case-sensitive by default. Allow case-insensitive matching:

  .config(function ($urlConfigProvider) {
    $urlConfigProvider.caseInsensitive(true);
  });

Strict mode (trailing slashes)

By default, /users/ and /users are distinct. Disable strict mode to treat trailing slashes as equivalent:

  .config(function ($urlConfigProvider) {
    $urlConfigProvider.strictMode(false); // /users/ matches /users
  });

Default squash policy

Control the global default for how parameters with default values appear in URLs:

  .config(function ($urlConfigProvider) {
    // 'false' (default): include the default value in the URL
    // 'true': omit default value from URL
    // '~': replace default value with '~' in the URL
    $urlConfigProvider.defaultSquashPolicy(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:

  .config(function ($urlConfigProvider) {

    // Encodes an array of integers as a dash-separated string
    $urlConfigProvider.type('intarray', {
      encode: function (array) {
        return array.join('-');
      },
      decode: function (str) {
        return str.split('-').map(function (x) { return parseInt(x, 10); });
      },
      is: function (val) {
        return Array.isArray(val) && val.every(function (x) {
          return typeof x === 'number' && !isNaN(x);
        });
      },
      equals: function (a, b) {
        return a.length === b.length &&
          a.every(function (x, i) { return x === b[i]; });
      },
      pattern: /[0-9]+(?:-[0-9]+)*/
    });

  });

Use the custom type in a state URL:

  name: 'report',
  url: '/reports/{ids:intarray}',
  component: 'Report'
});

// $state.go('report', { ids: [10, 20, 30] }) → /reports/10-20-30
// /reports/10-20-30 → { ids: [10, 20, 30] }

Note: Register custom types before any state that uses them. UrlConfigProvider.type() returns the provider itself for chaining.

Hash mode vs HTML5 mode

AngularTS inherits Angular 1’s $location modes. Configure the mode on $locationProvider:

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.module('app')
  .config(function ($locationProvider) {
    $locationProvider.html5Mode(false);
    $locationProvider.hashPrefix('!'); // optional: use #!/ instead of #/
  });

The UrlService.href() method prepends # (plus the hash prefix) when generating hrefs 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.module('app')
  .config(function ($locationProvider) {
    $locationProvider.html5Mode({
      enabled: true,
      requireBase: true  // <base href="..."> must be present in <head>
    });
  });

In HTML5 mode, UrlService.href() prepends the base path (stripped of its last segment) rather than a hash.

Base href

In HTML5 mode, the base href is read from the <base> tag in the document <head>:

  <base href="/myapp/">
</head>

UrlService.baseHref() returns the current base href. If no <base> tag is present it 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.

var base = $url.baseHref(); // "/myapp/"

Reading the current URL

UrlService provides three methods to read URL components:


$url.getPath();   // "/contacts/42"
$url.getSearch(); // { tab: 'notes' }
$url.getHash();   // "section1"

// All three at once
var parts = $url.parts();
// { path: '/contacts/42', search: { tab: 'notes' }, hash: 'section1' }

// The full normalized URL (strips base, adds hash prefix in hash mode)
$url.url(); // "/contacts/42?tab=notes#section1"

Updating the URL

UrlService.url(newUrl) replaces the current URL. The router then calls sync() to find and activate the matching state:

$url.url('/contacts/99?tab=history');

UrlService.push() is the internal method used by the built-in onSuccess hook to update the browser address bar after a successful state transition:

$url.push(state.navigable.url, $state.params, { replace: false });

URL rule priority and matching weight

When multiple rules could match the same URL, the router scores each match and picks the winner:

  1. Rules are sorted by a primary sort order (rules created from state declarations all share the same group).
  2. Within a group, the match with the highest weight wins. Weight is computed by counting matched segments, typed parameters, and specificity of regexps.
  3. A state URL like /users/{id:int} outweighs /users/:id for the path /users/42 because the typed parameter provides a stricter match.

Exact path matches score higher than prefix matches. Query parameters do not affect path scoring but must all be present if declared without defaults.

Listening for URL changes

UrlService.onChange(callback) registers a low-level listener that fires on every $locationChangeSuccess event. The listener receives the Angular scope event:

  console.log('URL changed to:', $url.url());
});

// Stop listening
deregister();

UrlService.listen(false) stops the router from responding to URL changes entirely. Call listen(true) to resume. This is useful when loading states asynchronously and you want to defer URL-driven navigation until the states are registered:

  .run(function ($url, $stateRegistry) {
    $url.listen(false);

    fetch('/api/states')
      .then(r => r.json())
      .then(function (states) {
        states.forEach(s => $stateRegistry.register(s));
        $url.listen(true);
        $url.sync(); // activate the state matching the current URL
      });
  });

9 - Animations

9.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:

  1. 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.
  2. 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.
  3. The driver waits for the next quiet requestAnimationFrame. This forces the browser to flush style calculations, so getComputedStyle() returns accurate timing values.
  4. 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.
  5. 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.
  6. 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: opacity 0.3s ease, transform 0.3s ease;
  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: opacity 0.3s ease, transform 0.3s ease;
  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:

  from {
    opacity: 0;
    transform: translateX(-20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideOut {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(20px);
  }
}

.my-element.ng-enter {
  animation: slideIn 0.4s ease forwards;
}

.my-element.ng-leave {
  animation: slideOut 0.4s ease forwards;
}

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: opacity 0.25s ease;
  opacity: 1;
}

.my-panel.ng-hide-add-active {
  opacity: 0;
}

/* Fade in when revealed */
.my-panel.ng-hide-remove {
  transition: opacity 0.25s ease;
  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.

  <li class="list-item" ng-repeat="item in items track by item.id">
    {{ item.name }}
  </li>
</ul>
.list-item.ng-leave,
.list-item.ng-move {
  transition: all 0.3s ease;
}

.list-item.ng-enter {
  opacity: 0;
  transform: scale(0.9);
}

.list-item.ng-enter-active {
  opacity: 1;
  transform: scale(1);
}

.list-item.ng-leave {
  opacity: 1;
  transform: scale(1);
}

.list-item.ng-leave-active {
  opacity: 0;
  transform: scale(0.9);
}

.list-item.ng-move {
  opacity: 0.5;
}

.list-item.ng-move-active {
  opacity: 1;
}

Example: slide transition with ng-if

ng-if removes and re-inserts the entire element, so you can animate it with ng-enter / ng-leave transitions:

  <!-- drawer content -->
</div>
  overflow: hidden;
}

.drawer.ng-enter {
  transition: max-height 0.35s ease, opacity 0.35s ease;
  max-height: 0;
  opacity: 0;
}

.drawer.ng-enter-active {
  max-height: 500px;
  opacity: 1;
}

.drawer.ng-leave {
  transition: max-height 0.35s ease, opacity 0.35s ease;
  max-height: 500px;
  opacity: 1;
}

.drawer.ng-leave-active {
  max-height: 0;
  opacity: 0;
}

Staggered animations

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: opacity 0.3s ease, transform 0.3s ease;
  opacity: 0;
  transform: translateY(8px);
}

.list-item.ng-enter-active {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger: each subsequent 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.

Enter stagger

.list-item.ng-enter-stagger {
  transition-delay: 0.1s;
  transition-duration: 0s;
}

Leave stagger

.list-item.ng-leave-stagger {
  transition-delay: 0.05s;
  transition-duration: 0s;
}

Move stagger

.list-item.ng-move-stagger {
  transition-delay: 0.08s;
  transition-duration: 0s;
}

Performance tips

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 $animateProvider.classNameFilter() or $animateProvider.customFilter() to restrict animations to specific elements. Animating large numbers of DOM nodes simultaneously causes frame drops on low-powered devices.

.my-element.ng-enter {
  transition: opacity 0.3s ease, transform 0.3s ease;
  opacity: 0;
  transform: translateY(-12px);
  will-change: opacity, transform;
}

.my-element.ng-enter-active {
  opacity: 1;
  transform: translateY(0);
}

9.2 - JavaScript-based animations with the AngularTS JS driver

Register JavaScript animation handlers with $animateProvider, 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

At startup, AnimateJsDriverProvider registers itself with $$animationProvider._drivers. During each animation request, the animation queue calls drivers in reverse registration order — the JS driver is checked before the CSS driver. The driver calls $$animateJs(element, event, classes, options) which inspects the element’s class list against all factories registered via $animateProvider.register(). 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.

The internal $$animateJs service (AnimateJsFn) handles 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 $animateProvider.register() during the config phase 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.

  $animateProvider.register('.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', function onEnd() {
            element.removeEventListener('transitionend', onEnd);
            done();
          });
        });
      },

      leave: function (element, done) {
        element.style.transition = 'opacity 0.3s ease';
        element.style.opacity = '0';
        element.addEventListener('transitionend', function onEnd() {
          element.removeEventListener('transitionend', onEnd);
          done();
        });
      },
    };
  });
}]);

Or use the module-level .animation() shorthand, which calls $animateProvider.register() internally:

  return {
    enter: function (element, done) { /* ... */ done(); },
    leave: function (element, done) { /* ... */ done(); },
  };
});

Animation lifecycle hooks

The object returned by your factory can implement any combination of these hooks. Unimplemented hooks are simply skipped.

HookSignatureWhen 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:

  const animation = element.animate(
    [{ opacity: 0 }, { opacity: 1 }],
    { duration: 300, easing: 'ease' }
  );

  animation.onfinish = done;

  // Return a cancel handler
  return function (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.

  return {
    enter: function (element, done) {
      const anim = element.animate(
        [
          { opacity: 0, transform: 'scale(0.6) translateY(-8px)' },
          { opacity: 1, transform: 'scale(1) translateY(0)' },
        ],
        {
          duration: 350,
          easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
          fill: 'forwards',
        }
      );

      anim.onfinish = done;

      return function (wasCancelled) {
        if (wasCancelled) anim.cancel();
      };
    },

    leave: function (element, done) {
      const anim = element.animate(
        [
          { opacity: 1, transform: 'scale(1) translateY(0)' },
          { opacity: 0, transform: 'scale(0.6) translateY(8px)' },
        ],
        {
          duration: 250,
          easing: 'ease-in',
          fill: 'forwards',
        }
      );

      anim.onfinish = done;

      return function (wasCancelled) {
        if (wasCancelled) anim.cancel();
      };
    },
  };
});

Apply the animation by adding the .pop-in class to any element managed by a structural directive:

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

  return {
    addClass: function (element, className, done) {
      if (className === 'active') {
        const anim = element.animate(
          [
            { backgroundColor: 'transparent' },
            { backgroundColor: '#fffbcc' },
          ],
          { duration: 400, fill: 'forwards' }
        );
        anim.onfinish = done;
      } else {
        done();
      }
    },

    removeClass: function (element, className, done) {
      if (className === 'active') {
        const anim = element.animate(
          [
            { backgroundColor: '#fffbcc' },
            { backgroundColor: 'transparent' },
          ],
          { duration: 200, fill: 'forwards' }
        );
        anim.onfinish = done;
      } else {
        done();
      }
    },
  };
});

Example: coordinating multiple elements

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:

  let leaveDeferred = null;

  return {
    leave: function (element, done) {
      leaveDeferred = $q.defer();

      const anim = element.animate(
        [{ opacity: 1, transform: 'translateX(0)' },
         { opacity: 0, transform: 'translateX(-30px)' }],
        { duration: 250, easing: 'ease-in', fill: 'forwards' }
      );

      anim.onfinish = function () {
        leaveDeferred.resolve();
        done();
      };

      return function (wasCancelled) {
        if (wasCancelled) {
          anim.cancel();
          leaveDeferred.resolve();
        }
      };
    },

    enter: function (element, done) {
      const startEnter = function () {
        const anim = element.animate(
          [{ opacity: 0, transform: 'translateX(30px)' },
           { opacity: 1, transform: 'translateX(0)' }],
          { duration: 300, easing: 'ease-out', fill: 'forwards' }
        );
        anim.onfinish = done;
      };

      if (leaveDeferred) {
        leaveDeferred.promise.then(startEnter);
      } else {
        startEnter();
      }
    },
  };
}]);

The $$animateJs service

$$animateJs is the internal function that the JS driver uses to look up and package JS animation handlers. It is available as an injectable service if you need to call it directly — for example, when writing a custom driver or testing animation behavior.

Its signature is:

  element: HTMLElement,
  event: string,
  classes?: string | null,
  options?: AnimationOptions,
): Animator | undefined

It returns an Animator object ({ _willAnimate: true, start(), end() }) if at least one registered handler matches the element’s class list, or undefined if no handler is found. The returned Animator.start() runs the before-phase and after-phase operations, and calls runner.complete(status) when both are done.

Injecting services into animation factories

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) {
        const duration = 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:

  return {
    enter: function (element, done) {
      element.classList.add('is-entering');

      requestAnimationFrame(function () {
        element.classList.add('is-entering-active');
        element.addEventListener('transitionend', function onEnd(e) {
          if (e.target !== element) return;
          element.removeEventListener('transitionend', onEnd);
          element.classList.remove('is-entering', 'is-entering-active');
          done();
        });
      });
    },
  };
});
  transition: opacity 0.3s ease, transform 0.3s ease;
  opacity: 0;
  transform: scale(0.95);
}

.hybrid.is-entering-active {
  opacity: 1;
  transform: scale(1);
}

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.

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

CSS driver

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.

JS driver

Invokes factory functions registered via $animateProvider.register(). 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.

EventPreparation classActive 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:

.my-list-item.ng-enter-stagger {
  transition-delay: 0.1s;
  transition-duration: 0s;
}

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.

  static $inject = ['$animate', '$element'];

  constructor(
    private $animate: ng.AnimateService,
    private $element: HTMLElement,
  ) {}

  showPanel(panelEl: HTMLElement) {
    // Insert panelEl after this.$element; triggers ng-enter
    this.$animate.enter(panelEl, this.$element.parentElement, this.$element);
  }

  hidePanel(panelEl: HTMLElement) {
    // Remove panelEl after the leave animation completes
    this.$animate.leave(panelEl);
  }

  highlight(el: HTMLElement) {
    // Add 'highlighted' class with an animation
    this.$animate.addClass(el, 'highlighted');
  }

  updateClasses(el: HTMLElement) {
    // Add and remove classes atomically
    this.$animate.setClass(el, 'active', 'inactive');
  }

  morphStyle(el: HTMLElement) {
    // Animate from one set of inline styles to another
    this.$animate.animate(
      el,
      { opacity: 0, transform: 'scale(0.8)' },
      { opacity: 1, transform: 'scale(1)' },
      'my-morph',
    );
  }
}

Full method signatures

MethodDescription
enter(element, parent?, after?, options?)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.

const handle = $animate.enter(panelEl, hostEl);

handle.done((completed) => {
  console.log("Enter animation settled", completed);
});

Filtering animations

Two provider-level hooks let you restrict which elements can be animated. Both are configured during the config phase via $animateProvider.

$animateProvider.classNameFilter(regex) — only animate elements whose class list matches the given regular expression:

  // Only animate elements that have an 'animate-' prefixed class
  $animateProvider.classNameFilter(/\banimate-/);
}]);

$animateProvider.customFilter(fn) — supply an arbitrary predicate that receives (node, event, options) and returns true to allow the animation:

  $animateProvider.customFilter(function (node, event) {
    // Skip all animations when in reduced-motion mode
    return !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  });
}]);

Tip: Keep both filter functions as lean as possible. They are called for every DOM operation performed by animation-aware directives.

Registering JavaScript animations

To register a JavaScript animation, call $animateProvider.register() during the config phase or use the module-level .animation() shorthand. The name must be a CSS class selector starting with .:

  $animateProvider.register('.fade-animation', ['$q', function ($q) {
    return {
      enter(element, done) {
        // run enter animation, call done() when finished
        done();
      },
      leave(element, done) {
        done();
      },
    };
  }]);
}]);

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.

ng-animate-swap

The ng-animate-swap directive swaps between transcluded blocks as a watched expression changes. The previous element is removed with a leave animation and the new element is inserted with an enter animation, making it simple to animate between different states without manual DOM management.

  <div class="panel">{{ currentView }}</div>
</div>

The directive runs at priority 550, after ng-if (600) but before most others, so it cooperates correctly with other structural directives.

ng-animate-children

By default, when a parent structural animation (enter, leave, or move) is running, child animations on descendants are suppressed. This prevents visual chaos when an entire subtree is animated at once. The ng-animate-children attribute overrides this behavior for a specific container.

<div ng-if="showPanel" ng-animate-children="true">
  <div ng-repeat="item in items" class="list-item">{{ item }}</div>
</div>

Setting ng-animate-children to "on", "true", or an empty string enables child animations. Setting it to any other value (or omitting it) defers to the default suppression behavior.

10 - Filters

10.1 -

/**

  • @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 `$`.
    
  • */