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.
- Angular runtime
- Modules
- Components
- Scopes and state
- Dependency injection
- Change detection
- Templates
- 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:
| Member | Purpose |
|---|
controller | The initialized component controller. |
scope | The scope that owns reactive bindings and generated DOM. |
host | The component host element. |
| transclude |
onDestroy | Registers view-owned cleanup and returns a cancellation function. |
Directive views receive a
ProgrammaticViewContext.
It adds required, containing controllers resolved from the directive’s
require declaration. Its controller is undefined when none is declared.
Create a directive view
Directives can provide view instead of a template. Generated nodes continue
through the normal compile and link pipeline, so registered directives on those
nodes remain available.
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:
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:
- O(1) lookup: Listeners are stored in a
Map<string, Listener[]> keyed by
property name. When count changes, only listeners registered under
'count' are scheduled — not every watcher in the application. - Microtask scheduling:
_scheduleListener calls queueMicrotask(), which
defers the flush until after the current synchronous call stack completes.
Multiple changes to the same property in the same tick are coalesced. - Recursive proxying: When you assign an object —
$scope.user = { name: 'Alice' } — the new value is itself wrapped in a
Proxy. Nested property changes ($scope.user.name = 'Bob') are tracked
just as well as top-level ones.
A concrete example
$scope.count = 0;
$scope.increment = function () {
$scope.count += 1;
// At this point the Proxy set trap fires:
// 1. Stores the new value
// 2. Looks up listeners for 'count' in the watchers Map
// 3. Schedules them via queueMicrotask
// 4. Returns — no $apply() needed
};
}]);
<div>
<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 mechanism | Dirty-checking digest loop | ES6 Proxy set trap |
| Watcher lookup cost | O(n) — all watchers checked | O(1) — direct Map lookup by property name |
| Trigger | Manual digest entry required for async code | Automatic — Proxy intercepts every assignment |
| Update granularity | All watchers, all the time | Only bindings for the changed property |
| Nested objects | Shallow by default; $watchCollection needed for deep | Deep — nested objects are automatically proxied |
| Async code | Must manually enter change detection | No wrapping needed for standard async (Promise, fetch, etc.) |
When no $apply is needed
Because the Proxy fires synchronously on every assignment, any code that assigns
to a scope property — whether inside a controller, a service callback, or a
Promise handler — automatically triggers the right DOM update:
$scope.users = [];
$scope.loading = true;
// No $apply() needed — the Proxy intercepts the assignment
$http.get('/api/users').then(({ data }) => {
$scope.users = data; // triggers DOM update for {{ users }}
$scope.loading = false; // triggers DOM update for ng-if="loading"
});
}]);
Tip: If you are integrating a third-party library that updates data
outside of AngularTS (such as a raw WebSocket callback or a non-Promise-based
timer), and the library stores results in scope properties, those assignments
still flow through the Proxy and trigger updates automatically. No $apply
wrapper is needed.
Microtask batching
Multiple property changes within the same synchronous block are batched. The
listener scheduler enqueues a queueMicrotask flush only once per tick:
_enqueueScheduledTask(task: ScheduledTask): void {
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.
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.
Array annotation (recommended)
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
| Method | Description |
|---|
$injector.get(token) | Returns the service instance for token. Throws if not found. |
$injector.has(token) | Returns true if a provider for token is registered. |
$injector.invoke(fn, self?, locals?) | Calls fn with injected arguments. Optionally overrides specific tokens with locals. |
$injector.instantiate(Type, locals?) | Instantiates a constructor with new, injecting its dependencies. |
$injector.annotate(fn) | Returns the array of dependency names for fn. |
$injector.loadNewModules(mods) | Loads additional modules into a running injector. |
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:
| Token | Description |
|---|
$rootScope | The root of the scope hierarchy |
$compile | Template compiler |
$http | HTTP client |
$interpolate | Template interpolation |
$parse | Expression parser |
$filter | Filter registry |
$animate | Animation support |
$location | URL management |
$machine | Reactive mode machines |
$state | Router state service |
$eventBus | Pub/sub messaging |
Tip: Splitting your application into small, focused modules makes unit
testing dramatically easier. Each module can be loaded in isolation with
angular.injector(['ng', 'myModule']), giving you a fully functional injector
without bootstrapping the DOM.
Special module methods
Beyond the standard registration methods,
NgModule supports several
higher-level conveniences:
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:
- Broadcasts
$destroy downward to all children. - Removes all watcher registrations for this scope’s ID from the shared
_watchers map. - Clears all
on listeners. - Removes itself from the parent’s
_children array. - Sets
_destroyed = true and nulls internal references on the next microtask.
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
| Method | Description |
|---|
$scope.new(child?) | Creates an inherited child scope. |
$scope.newIsolate(instance?) | Creates an isolate child scope. |
$scope.watch(expr, fn, lazy?) | Registers a watcher; returns a deregistration function. |
$scope.on(name, fn) | Registers an event listener; returns a deregistration function. |
$scope.emit(name, ...args) | Fires an event upward through the hierarchy. |
$scope.broadcast(name, ...args) | Fires an event downward to all descendants. |
$scope.merge(obj) | Copies enumerable properties from obj into the scope. |
$scope.destroy() | Tears down the scope and all its watchers. |
$rootScope.searchByName(name) | Finds a scope by its scopeName anywhere in the tree. |
$scope.getById(id) | Finds a scope by numeric ID within the subtree. |
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
| Property | Type | Description |
|---|
_constant | boolean | true if the expression is a literal constant |
_literal | boolean | true if the expression is a simple literal |
_assign | function? | Present for l-value expressions; assigns a value to the context |
_inputs | any[]? | Sub-expressions tracked for change detection |
_decoratedNode | BodyNode | The annotated AST for this expression |
Security and binding policies
Plain interpolation produces text. HTML bindings such as ng-bind-html use
$compile.htmlPolicy to sanitize strings or reject them before insertion.
Configure this callback with your application’s HTML sanitizer:
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.