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

Return to the regular view of this page.

Core concepts

Understand the small set of ideas that AngularTS applications use: modules, components, scopes, dependency injection, templates, views, and reactivity.

Read this section after the first-application tutorial. A concept explains why the framework behaves as it does; a task guide shows how to complete a particular job.

  1. Angular runtime
  2. Modules
  3. Components
  4. Scopes and state
  5. Dependency injection
  6. Change detection
  7. Templates
  8. Optional: Programmatic views

Use the glossary whenever a term is unfamiliar.

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.createModule() to create or replace a module. Use angular.getModule() to retrieve a module that is already registered.

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

app.service('UserService', UserService);

app.config({
  $location: {
    hashPrefix: '!',
  },
});

const existing = angular.getModule('myApp');

Calling angular.getModule("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']);
});

The built-in ng module is prepended automatically. Every dependency-bearing function must use array annotation or declare a static $inject property.

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

Auto-Bootstrap

The default package entry and UMD build scan for ng-app when the DOM is ready. The first root uses the global Angular instance; additional roots become sub-applications in angular.subapps. No startup call is required.

Custom runtimes do not scan the document. Bootstrap their root explicitly:

angular.bootstrap(document.querySelector('#app'), ['myApp']);

Use the same explicit call for an application root inserted after the initial document load.

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.

Custom runtime core providers include only the compile, scope, controller, parse, interpolation, and exception-handling services needed to render templates. Orchestration services such as $machine and $workflow are not part of that core set. Include machineModule for reactive state machines, workflowModule for workflows, or the compatible orchestrationModule aggregate when the runtime needs both.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { machineModule } from '@angular-wave/angular.ts/runtime/orchestration';

const angular = createAngular({
  modules: [machineModule],
});

Include wasmModule when a custom runtime loads WebAssembly or exposes AngularTS scope state through the language-neutral WASM ABI. The bridge remains owned by that runtime and is disposed with it.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { wasmModule } from '@angular-wave/angular.ts/runtime/wasm';

const angular = createAngular({
  modules: [wasmModule],
});
const injector = angular.injector(['ng']);
const playerGuest = injector.get('$wasm').load({
  source: '/player.wasm',
});
const player = injector.get('$rootScope');

await playerGuest.bind(player, { name: 'player' });
player.score = 42;

Include sseModule, websocketModule, or webTransportModule when a custom runtime uses one transport. Include the compatible realtimeModule aggregate when it uses all three. Connection defaults still use the normal typed module configuration, and all active connections close when the custom runtime is destroyed.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { websocketModule } from '@angular-wave/angular.ts/runtime/realtime';

const angular = createAngular({
  modules: [websocketModule],
});

angular.createModule('app', []).config({
  $websocket: {
    defaults: { retryDelay: 500, maxRetries: 5 },
  },
});

Include routerModule when a custom runtime owns navigation. The slice includes URL state, navigation security, route links, routed views, ARIA state, anchor scrolling, and route-template fetching, while leaving unrelated browser and orchestration services out of the build.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { routerModule } from '@angular-wave/angular.ts/runtime/router';

const angular = createAngular({
  modules: [routerModule],
});

angular.createModule('app', []).router({
  name: 'dashboard',
  url: '/dashboard',
  template: '<dashboard-view></dashboard-view>',
});

angular.bootstrap(document.body, ['app']);

Include serviceWorkerModule for managed service-worker registration, updates, and messaging without including the full browser integration group. The native listeners owned by $serviceWorker are released when the custom runtime is destroyed.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { serviceWorkerModule } from '@angular-wave/angular.ts/runtime/service-worker';

const angular = createAngular({
  modules: [serviceWorkerModule],
});

angular.createModule('app', []).serviceWorker('/service-worker.js', {
  scope: '/app/',
  autoRegister: true,
});

const injector = angular.injector(['ng', 'app']);
const appWorker = injector.get('$serviceWorker');

appWorker.onMessage((event) => {
  console.log(event.data);
});

Include webComponentModule when a custom runtime defines scoped custom elements directly. defineAngularElement(...) includes it automatically.

import { createAngular } from '@angular-wave/angular.ts/runtime';
import { webComponentModule } from '@angular-wave/angular.ts/runtime/web-component';

const angular = createAngular({
  modules: [webComponentModule],
});
import { defineAngularElement } from '@angular-wave/angular.ts/runtime/web-component';
import { ngEventDirectives } from '@angular-wave/angular.ts/directive/events';

defineAngularElement('billing-summary', {
  directives: {
    ngClick: ngEventDirectives.ngClick,
  },
  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 integrations such as browser callbacks, legacy scripts, or embedded widgets. Normal application code should prefer dependency injection.

Locate Named Scopes

getScopeByName() searches from $rootScope for a scope with a matching scopeName.

$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.tokens exposes public injection token strings as a typed object. Prefer it when writing $inject arrays in TypeScript.

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

2 - Typed Component Views

Construct component and directive views with real DOM nodes, reactive readers, keyed collections, and normal AngularTS module registration.

Programmatic views provide a JSX-free alternative to HTML templates. A view is an ordinary function that returns real DOM nodes created with typed tag factories. It uses the normal AngularTS compiler, scope lifecycle, dependency injection, component registration, directive registration, and transclusion pipeline.

There is no separate mount API. Register the component or directive on an AngularTS module and let the compiler instantiate it from normal markup.

Create a component view

Import the helpers you use from the package entry point:

import {
  angular,
  attrs,
  button,
  div,
  each,
  event,
  h2,
  input,
  li,
  output,
  p,
  props,
  section,
  tag,
  tagNS,
  tags,
  time,
  ul,
} from '@angular-wave/angular.ts';

Named imports are preferred in ESM applications because bundlers can retain only the factories that are used. No-build UMD applications can access tag factories through angular.tags and helpers through angular.view. Keep tags for namespace proxies; use tag() when the element name is dynamic.

Define the view directly on the component registration:

class CounterController {
  count = 0;
}

angular.createModule('app', []).component('counterButton', {
  controller: CounterController,
  view: ({ controller }) =>
    button(
      { onclick: () => controller.count++ },
      () => `Count: ${controller.count}`,
    ),
});

The component is used like any template-backed component:

<counter-button></counter-button>

Return view children

A component or directive view can return any ProgrammaticViewChild.

  • A DOM Node.
  • A string, number, boolean, or bigint.
  • A reactive child reader function.
  • A nested array of view children.
  • A DocumentFragment.
  • null, undefined, or either boolean to render nothing.

Arrays are flattened recursively. Existing nodes are moved rather than cloned, and document fragments contribute their current child nodes.

view: ({ controller }) => [
  h2(() => controller.title),
  controller.showDetails ? p(controller.initialDetails) : null,
];

The conditional expression in this example is evaluated only when the view is constructed. Make the condition itself a reader when it must remain reactive:

view: ({ controller }) => [
  h2(() => controller.title),
  () => (controller.showDetails ? p(controller.details) : null),
];

Use reactive readers

A function in a child or ordinary non-event property position is a reactive reader. AngularTS executes it, tracks the proxied scope and controller state it reads, and updates only the corresponding DOM binding when that state changes.

output(() => controller.total);

button({
  disabled: () => !controller.canSubmit,
});

input({
  value: () => controller.query,
});

A value that is evaluated before the tag function is a static snapshot:

output(controller.total);
button({ disabled: !controller.canSubmit });

Use reader functions whenever a child or property must be reevaluated reactively.

Unlike string templates, programmatic views keep bindings between the model and the rendered DOM inside TypeScript’s type system. Renaming a controller or model property updates through normal refactoring tools, while misspelled members and incompatible typed DOM property values fail at compile time rather than becoming runtime template errors.

Properties and attributes

The first plain object passed to a tag is its property map. Reactive readers use DOM property semantics, including properties exposed only by custom elements. For static values, AngularTS assigns a DOM property when it exists on the element and otherwise uses an attribute.

Normal property maps are checked against the element’s DOM type. Use attrs() for arbitrary attribute names and props() for custom-element properties; misspelled native properties are rejected by TypeScript.

input({
  value: () => controller.name,
  disabled: () => controller.saving,
  'data-field': 'name',
});

Use attrs() to force attribute semantics. Functions inside attrs() remain reactive readers.

section({
  ...attrs({
    'aria-busy': () => controller.loading,
    'data-count': () => controller.items.length,
  }),
});

Boolean HTML attributes use presence semantics. Other attributes, including ARIA attributes, are serialized normally. For example, an ARIA value of false becomes the string "false" rather than removing the attribute.

Use props() to assign exact, literal DOM property values. Values inside props() are not reactive readers. This distinction permits function-valued properties on custom elements:

const grid = tag('data-grid', {
  ...props({
    rowFormatter: (row: { name: string }) => row.name.toUpperCase(),
  }),
  hidden: () => controller.rows.length === 0,
});

Do not place reactive readers inside props():

// Reactive property
input({ value: () => controller.name });

// Literal function assigned as the value property
input(props({ value: () => controller.name }));

Potentially dangerous properties such as innerHTML and srcdoc still pass through AngularTS contextual escaping. Use trusted SCE values only when the content has been reviewed and is intentionally trusted.

Handle events

Functions assigned to native on* properties are event handlers:

button({
  onclick: (event) => controller.submit(event),
});

Use event() for listener options, object listeners, or custom event names. It uses native addEventListener semantics and is removed with the view.

button({
  click: event(() => controller.submit(), { once: true }),
});

tag('data-grid', {
  selectionchange: event<CustomEvent<Selection>>((event) =>
    controller.select(event.detail),
  ),
});

Errors thrown by programmatic event handlers and reactive bindings are routed through $exceptionHandler.

Render collections

Standard array methods are useful for static collections:

ul(controller.todos.map((todo) => li(todo.title)));

This calls map() once and creates a DOM snapshot. Changes to the array do not insert, remove, or reorder list nodes.

Use each() for a reactive collection:

ul(
  each(
    () => controller.todos,
    (todo) => todo.id,
    (todo) => li(() => todo().title),
  ),
);

each() accepts three functions:

  • The collection reader returns an iterable, null, or undefined.
  • The key selector receives the concrete item and must return a unique, stable key.
  • The renderer receives a reactive ProgrammaticViewReader rather than an item snapshot.

Calling todo() reads the current value associated with that key. When a new object replaces an old object with the same key, AngularTS preserves its DOM nodes and updates bindings that read todo(). Reordering moves existing nodes, new keys create nodes, and removed keys dispose their nodes and bindings.

Duplicate keys are rejected through $exceptionHandler. Keep keys stable and unique for the lifetime of each logical item.

Create dynamic and namespaced elements

Use tag() when the HTML tag name is dynamic or cannot use property access:

const widget = tag('account-summary', { accountId: controller.accountId });

Use tagNS() or call tags with a namespace URI for SVG, MathML, or another XML namespace:

const svg = tags('http://www.w3.org/2000/svg');

const icon = svg.svg(
  { viewBox: '0 0 24 24' },
  svg.circle({ cx: 12, cy: 12, r: 10 }),
);

const otherCircle = tagNS('http://www.w3.org/2000/svg', 'circle', {
  cx: 12,
  cy: 12,
  r: 10,
});

Access the view context

Component views receive a ProgrammaticViewContext with these members:

MemberPurpose
controllerThe initialized component controller.
scopeThe scope that owns reactive bindings and generated DOM.
hostThe component host element.
transclude
onDestroyRegisters view-owned cleanup and returns a cancellation function.

Directive views receive a ProgrammaticViewContext. It adds required, containing controllers resolved from the directive’s require declaration. Its controller is undefined when none is declared.

Create a directive view

Directives can provide view instead of a template. Generated nodes continue through the normal compile and link pipeline, so registered directives on those nodes remain available.

app.directive('liveClock', () => ({
  view: ({ scope, onDestroy }) => {
    scope.now = new Date();
    const timer = setInterval(() => (scope.now = new Date()), 1000);
    onDestroy(() => clearInterval(timer));
    return time(() => scope.now.toLocaleTimeString());
  },
}));

Use it through normal compiled markup:

<div live-clock></div>

Own external resources

Reactive bindings, event listeners, keyed children, and compiled fragments are disposed automatically. Register resources created by application code with onDestroy():

view: ({ onDestroy }) => {
  const socket = new WebSocket('/events');
  onDestroy(() => socket.close());
  return div('Connected');
};

Cleanup runs when either the owning scope is destroyed or the host element is deallocated. The returned cancellation function transfers ownership elsewhere. A cleanup registered after destruction runs immediately. Cleanup errors are reported through $exceptionHandler.

Language integrations

The Kotlin, Scala, and Dart integrations expose typed wrappers for the same runtime model:

  • ProgrammaticTags wraps angular.tags.
  • ProgrammaticViewApi wraps angular.view helpers.
  • ProgrammaticViewContext exposes controller, required, scope, host, transclude, and onDestroy.
  • Integration each() renderers receive an item reader, matching TypeScript.

Plain callback functions are reactive in normal child and property positions. Use each integration’s props() helper when assigning a literal callback as a DOM property.

Choose templates or programmatic views

Use a programmatic view when direct DOM construction, dynamic tag selection, custom-element property assignment, or fine-grained imperative integration is clearer than markup. Use an HTML template when static document structure and designer-readable markup are more important.

Both forms use the same AngularTS module, scope, compiler, directive, and lifecycle infrastructure and can coexist in one application.

3 - Components

Understand components as registered UI units with a controller, inputs, lifecycle, and either an HTML template or typed view.

A component is a reusable UI unit registered on a module. It owns a DOM region, an optional controller, explicit inputs, and one rendering definition.

angular.getModule('todoApp').component('todoSummary', {
  bindings: { count: '<' },
  template: '<strong>{{ $ctrl.count }} remaining</strong>',
});

Use it as an HTML element:

<todo-summary count="remaining()"></todo-summary>

The module registration makes the component available to compiled DOM in that application. No annotation syntax, decorator, separate mount call, or global custom-element registration is required.

Controller and bindings

The controller stores component behavior and local state. In a template, $ctrl refers to that controller. Bindings define the component’s public inputs and outputs instead of exposing its internal scope.

Template or typed view

A component can render an HTML template, load a templateUrl, or provide a typed view function. These options share component registration, dependency injection, directives, child compilation, and cleanup.

Read choose a view style before selecting one for a new component.

4 - Reactive change detection with ES6 Proxy scopes

See how AngularTS replaces the AngularJS digest loop with ES6 Proxy interception, scheduling microtask DOM updates that only re-evaluate affected bindings.

Change detection is how a framework decides when to update the DOM to reflect new data. AngularJS used a polling mechanism called the digest cycle — every time something might have changed, AngularJS ran all registered watchers, compared old and new values, and repeated until nothing changed. AngularTS replaces this entirely with ES6 Proxy-based reactive observation: the framework knows exactly which property changed, which bindings depend on it, and schedules only those bindings to re-evaluate.

The digest cycle problem

In AngularJS, every watch registered anywhere in the application was checked on every digest. A large application could have thousands of watchers running on every user interaction, mouse move, or HTTP response. This was O(n) in the number of watchers per cycle, and cycles could chain into one another.

AngularTS eliminates this entirely. There are no cycles. There is no polling.

How Proxy-based reactivity works

When AngularTS creates a scope via createScope(), it wraps the plain object in a Proxy whose handler is a Scope instance. The Scope class implements the set trap:

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
  };
}]);
<div>
  <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.

5 - Dependency injection and the AngularTS injector

Understand how the AngularTS injector resolves named tokens, how to annotate dependencies, and how the provider pattern controls the config and run phases.

Dependency injection (DI) is the mechanism AngularTS uses to supply components with the services they need. Rather than constructing dependencies directly, you declare what you need by name and the injector finds, instantiates, and delivers each dependency. This keeps components decoupled, composable, and straightforward to test in isolation.

How the injector works

When you call angular.bootstrap() (or use the ng-app directive), AngularTS calls createInjector() with your list of modules. The injector builds two internal caches:

  • Provider cache — holds provider objects, accessible during the config phase.
  • Instance cache — holds fully constructed service singletons, built on first request.

When a service is requested via $injector.get('myService'), the injector looks up myServiceProvider, calls its get method (injecting that method’s own dependencies), caches the result, and returns it. Every later get returns the same instance.

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

// Built-in token literals infer their registered service type.
const $machine = injector.get(angular.tokens.$machine);

// Custom services can provide an explicit result type.
const flags = injector.get<{ enabled: boolean }>('featureFlags');

// 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
  },
]);

Applications that use the injector directly can declare their complete custom service registry once. Built-in token inference remains available alongside the application services, and invoke() and instantiate() infer their return types.

interface AppServices {
  auth: AuthService;
  featureFlags: { enabled: boolean };
}

const appInjector: ng.InjectorService<AppServices> = injector;
const auth = appInjector.get('auth');
const answer = appInjector.invoke(() => 42); // number
const session = appInjector.instantiate(Session); // Session

Declaring dependencies

AngularTS reads dependency names from the function’s arguments — but argument names are erased by minifiers. The safe, minification-proof approach is array annotation: an array where every element except the last is a dependency name string, and the last element is the function.

angular.createModule('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.createModule('myApp', []).controller('UserCtrl', UserCtrl);

Note: The $inject static property on a class is the TypeScript-idiomatic form of array annotation. AngularTS does not infer dependencies from function parameter names.

Explicit dependency annotation

Every dependency-bearing function must use array annotation or a static $inject property. An unannotated function with parameters throws immediately. This invariant makes dependency injection deterministic under minification.

Injectable types

value

A fixed JavaScript value registered with module.value(). It is not available during the config phase.

constant

Like value, but available during the config phase and cannot be overridden by a decorator.

factory

A function whose return value becomes the singleton. Must not return undefined.

service

A constructor function. Instantiated with new once; the instance is the singleton.

provider

The most flexible type. An object with a get method. Can expose configuration methods accessible during the config phase.

decorator

Wraps an existing service. Receives $delegate (the original) and returns a replacement or augmented version.

The provider pattern

Providers give you a way to configure a service before any instance is created. During the config phase the provider itself is injected (not the instance), allowing configuration. During the run phase and everywhere else, the instance produced by get is injected.

  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.createModule('myApp', [])
  .provider('greeter', GreeterProvider)
  .config(['greeterProvider', function (greeterProvider) {
    // During config, the *provider* is injected — note the "Provider" suffix
    greeterProvider.setPrefix('Greetings');
  }])
  .run(['greeter', function (greeter) {
    // During run, the *instance* is injected
    greeter.greet('world');
  }]);

Info: During the config phase, inject a provider by appending Provider to its name: greeterProvider for the greeter service. This naming convention is enforced by AngularTS internally.

Public Injectable Contracts

ng.InjectionTokenMap is the authoritative mapping from every public single-dollar token to its named contract. InjectorService.get() uses this map automatically:

const compile: ng.CompileService = $injector.get('$compile');
const rootScope: ng.RootScopeService = $injector.get('$rootScope');
const stateRegistry: ng.StateRegistryService = $injector.get('$stateRegistry');
const browserWindow: ng.WindowService = $injector.get('$window');

Framework-owned double-dollar tokens are internal implementation details and are deliberately absent from PublicInjectionTokens, InjectionTokenMap, and the ng namespace.

Every InjectionTokenMap[ value is a named ](../../../typedoc/interfaces/InjectionTokenMap.html)ng.\*contract exported through the generated TypeDoc entry point. Adding a public token without both its namespace contract and documentation export failsmake check.

Decorating existing services

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

  .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

Framework configuration is a typed declarative object. Run blocks have access to runtime services.

app.config({
  $http: {
    defaults: {
      headers: {
        common: { 'X-App': 'myApp' },
      },
    },
  },
});

// Run — services are available
app.run([
  '$rootScope',
  '$state',
  function ($rootScope, $state) {
    $rootScope.on('$stateChangeError', () => $state.go('error'));
  },
]);

$injector API reference

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.

6 - Modules: organizing your AngularTS application

Learn how AngularTS modules group controllers, services, directives, and config into composable units that the injector loads at bootstrap.

Every AngularTS application is assembled from one or more modules. A module is a named container that registers services, directives, controllers, filters, and configuration blocks. Modules do not execute any code themselves — they record recipes that the injector later uses to construct and wire together your application. This separation between declaration and instantiation is what makes AngularTS applications easy to test and compose.

Create and retrieve modules

Use angular.createModule() to create or replace a module. Its dependency list defaults to an empty array. Use angular.getModule() to retrieve a module that is already registered.

TypeScript

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

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

JavaScript

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

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

Warning: Calling angular.createModule('myApp') a second time replaces the existing module. Use angular.getModule('myApp') when you only need to add registrations to the existing module.

Module registration methods

The NgModule instance returned by angular.createModule() or angular.getModule() exposes a fluent API for registering every kind of application artifact. All methods return the same module so calls can be chained.

.controller(name, fn)

Registers a controller constructor. The controller is instantiated by $controller and receives a child scope.

.service(name, ctor)

Registers a singleton service constructed with new. The constructor is instantiated once and reused.

.factory(name, fn)

Registers a factory function whose return value becomes the singleton. The function must not return undefined.

.provider(name, type)

Registers a full provider. The provider’s get method is called to produce the service instance. Providers can expose configuration methods accessible during the config phase.

.value(name, val)

Registers an arbitrary value as a service. Values cannot be injected during the config phase.

.constant(name, val)

Registers a constant that is available during both the config and run phases and cannot be overridden by a decorator.

.directive(name, fn)

Registers a directive factory for the $compile provider.

.filter(name, fn)

Registers a filter function for use in templates and $filter.

.config(settings)

Declares typed configuration for built-in services before they are instantiated. Configuration keys use the same names as their runtime injectables. Application code does not inject or configure provider objects directly.

.run(fn)

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

.router(definition)

Registers one RouterModuleDeclaration or a readonly forest. Literal route names, parameters, children, and resolves are inferred into the returned typed router module. Custom runtimes include router implementation code only when the router runtime is composed into the build.

A complete module example

The following example creates a module, registers a constant, a service, a controller, and a config block, then composes that module with a second utility module.

const utilModule = angular.createModule('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);
      },
    };
  },
]);
const app = angular.createModule('myApp', ['ng', 'util']);
app.service('userService', UserService);
app.component('userList', { controller: UserListController });
app.router({ name: 'users', url: '/users', component: 'userList' });

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

app.model('user', () => ({ name: 'John', authenticated: false }));
app.machine('sessionMachine', {
  initial: 'setup',
  data: {},
  states: { setup: {} },
});
app.workflow('onboarding', workflowConfig);
app.wasm('mathLib', { source: '/wasm/math.wasm' });
app.worker('backgroundWorker', '/workers/bg.js');
app.rest('Post', '/api/posts', Post);
app.store('prefs', UserPreferences, 'local');
app.sse('notifications', '/events/stream');
app.websocket('chat', 'wss://chat.example.com/ws');

These methods create typed provider-registration commands. The injector applies them through its private registry during bootstrap; the registry is not injectable application state.

7 - Scopes, data binding, and the scope hierarchy

Explore how AngularTS scopes work as reactive ES6 Proxies, how the hierarchy flows from $rootScope, and how to use events and watchers effectively.

A scope in AngularTS is the glue between the model and the view. It is a plain JavaScript object wrapped in an ES6 Proxy so that property assignments automatically trigger DOM updates without any manual notification. Every controller, directive, and component operates against a scope, and those scopes are organized into a tree rooted at $rootScope.

What a scope actually is

When you write $scope.count = 0 in a controller, AngularTS is not storing count on a special object — it is storing it on an ordinary JavaScript object. The Proxy layer intercepts the set trap and, whenever a watched property changes, schedules the affected binding listeners to re-evaluate on the next microtask.

set(target, property, value, proxy): boolean {
  // ... after storing the value ...
  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:

<section ng-scope="userPanel">
  <user-panel></user-panel>
</section>

ng-scope requires a non-empty name. It names the current scope; it does not create a new one. Find that scope from JavaScript:

// Find it from $rootScope
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:

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

8 - Templates, interpolation, and expression parsing

Master the {{ }} interpolation syntax, one-time bindings, filter pipes, and how $interpolate and $parse power programmatic expression evaluation.

Templates in AngularTS are ordinary HTML files augmented with a handful of special syntax forms. The most fundamental is interpolation: the {{ expression }} delimiters that tell the template compiler to evaluate an expression against the current scope and render its result as text. The $interpolate service handles this translation at compile time, and the reactive proxy system ensures the DOM re-renders whenever the underlying data changes.

Interpolation syntax

Place any valid AngularTS expression between {{ and }} to render its current value:

<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 later arguments. They are pure functions — given the same input, they always produce the same output, which lets the reactive system cache their results.

One-time bindings

Prefix an expression with :: to evaluate it once and then release the binding. This is a performance optimization for data that is set during initialization and never changes:

<h1>{{ ::pageTitle }}</h1>
<p>User ID: {{ ::user.id }}</p>

<!-- Inside ng-repeat — binds each item once -->
<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 with module.config({ $interpolate: ... }):

angular.createModule('app', []).config({
  $interpolate: {
    startSymbol: '{[',
    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 binding policies

Plain interpolation produces text. HTML bindings such as ng-bind-html use $compile.htmlPolicy to sanitize strings or reject them before insertion. Configure this callback with your application’s HTML sanitizer:

angular.createModule('htmlApp', []).config({
  $compile: {
    htmlPolicy: (html) => DOMPurify.sanitize(html),
  },
});
<p>{{ userComment }}</p>
<p ng-bind-html="userComment"></p>

Without an HTML policy, nonempty HTML strings are rejected. The framework also accepts native TrustedHTML. See binding policies for URL and script contexts.

Escaping interpolation delimiters

If you need to display literal {{ in a template without triggering interpolation, escape each character with a backslash:

<p>The syntax is \{\{ expression \}\}</p>

$interpolate unescapes these sequences before returning the final string.

9 - Glossary

Plain-language definitions of browser and AngularTS terms used throughout the documentation.

Application: The DOM region started by ng-app or angular.bootstrap().

Attribute: A setting written inside an HTML opening tag.

Binding: A connection that transfers a value between state and the DOM.

Bootstrap: Start an application on a specific DOM element.

Compiler: The AngularTS runtime that discovers directives and connects them to DOM nodes. It does not produce a build artifact.

Component: A registered, reusable UI unit with inputs, behavior, and a view.

Controller: An object or function that supplies state and behavior to a DOM region or component.

Dependency injection: Requesting a registered value by token instead of constructing or finding it directly.

Directive: An HTML attribute or element that adds AngularTS behavior.

DOM: The browser’s live object tree for an HTML document.

Expression: A small JavaScript-like statement evaluated against a scope in an HTML template.

Module: A named registry for an application’s components, directives, services, configuration, and dependencies.

Reactive: Able to notify dependent bindings when a value changes.

Scope: The reactive state and lifecycle context available to a compiled DOM region.

Service: Shared behavior or data created and supplied by the injector.

Template: HTML containing AngularTS directives and expressions.

Token: The name or value used to request a dependency.

Typed view: A TypeScript function that constructs component DOM with typed tags, properties, controller access, and reactive readers.

View: The DOM output rendered by a component, directive, or router state.