A service is one reusable object or function that AngularTS can provide to
controllers, components, directives, and other services. The act of requesting
that service by name is called dependency injection.
Start with shared models, then choose
the guide for the feature you need. Use the service
reference when you already know a service token
such as $http.
1 - Models
Use app.model() to declare shared reactive application state as an injectable service.
app.model(name, initial) declares a shared reactive model service. The
injected value is the proxied model object itself. Use models for shared,
declarative application state that should outlive one DOM scope tree.
When the model changes, DOM interpolation, ng-bind, and directive expressions
that read that model update automatically. Mutating the model proxy is
sufficient to schedule every affected observer.
Model data belongs to the app context, not to $rootScope. Every root scope
managed by the same app context resolves the same model instance. Destroying a
root scope removes that root’s observations, but the model data survives while
the app context remains alive.
Access From External Code
After the app has started with angular.bootstrap() or angular.injector(),
use angular.getModel(name) to access a model from external code:
constuser=angular.getModel<{name: string;authenticated: boolean}>('user');// An external callback can update the reactive model directly.
setTimeout(()=>{user.name='Jane';},1000);
The first lookup initializes the model if needed. Subsequent lookups and
dependency injection return the same instance. Mutations schedule updates for
every observing view without an explicit digest or $apply() call.
The getter only accepts models declared by loaded modules. Unknown names,
ordinary service names, and access before app initialization or after app
destruction throw errors. Use the Angular runtime instance that owns the app
when working with multiple applications.
Models Versus Local Scope
Use models for shared domain state such as user, cart, session, and
settings.
Use $scope for view-local state that only exists to support one template, such
as an open menu flag, a focused row id, or transient form display state.
Use $eventBus for cross-boundary messages, $machine for finite-state
transitions, and $workflow for command orchestration.
<sectionng-controller="ProfileCtrl as profile"><inputng-model="profile.user.name"/><buttonng-click="profile.signIn()">Sign in</button></section><sectionng-controller="HeaderCtrl as header"><strong>{{ header.user.name }}</strong><span>{{ header.user.authenticated ? "signed in" : "guest" }}</span></section>
Nested object reads, parent replacement, array length reads, array mutation, and
property deletion follow the same scope proxy semantics as local scope state.
Model Shape
Root models must start as plain objects. Primitives, null, arrays, and class
instances are rejected as root model values. Nested values follow AngularTS
scope proxy behavior. Duplicate model names in one module are rejected.
Model factories should initialize state only. Put sockets, workers, timers, DOM
listeners, and other side effects in services, workflows, machines, or
policy-backed browser abstractions.
Model Methods
Injected models are proxy-backed objects. They expose their own data properties,
the scope-proxy methods used by AngularTS reactivity, and model lifecycle
methods for whole-model coordination.
Those methods exist because models are implemented with the same proxy engine as
scopes. Application code should still treat a model as app-owned state, not as a
DOM scope.
Model-specific methods use ordinary method names:
snapshot() creates a plain snapshot of model state.
restore(snapshot, options?) restores model state and notifies observers.
sync(target) synchronizes model updates with a direct target or injectable
target factory.
sync() is intended for integrations such as Unity, WebSocket sessions,
workers, CRDT documents, and custom stores. The external runtime may update the
model imperatively, but AngularTS keeps the rest of the app declarative:
runtime event
-> model assignment
-> model.sync(...)
-> websocket/store/worker/crdt target
Target factories should use normal AngularTS injectable form. Bare string
service-name shortcuts are intentionally avoided for consistency:
The worker exchanges standard
ng.WorkerModelMessage
envelopes. Incoming snapshots update every DOM scope that consumes the model,
while model mutations are sent back with change metadata and loop-prevention
origin tracking.
sync() batches synchronous model mutations, sends a plain snapshot plus modest
change metadata, and uses origin metadata to prevent bidirectional sync loops.
Failures should report through AngularTS exception handling by default.
Durable storage should start as an explicit sync() target. A settings model
can restore from $cookie and write future snapshots back to the same storage
boundary without adding persistence behavior to the model declaration itself.
A sync target can implement any combination of restore, write, receive,
and dispose:
sync() checks the target at runtime. A target must implement at least one
recognized operation, and every supplied operation must be a function.
restore() results and snapshots passed to receive() must be plain objects;
invalid external values are reported through the configured sync failure policy
without partially updating the model. receive() must return either a disposer
function or undefined.
Runtime validation establishes the model container contract, but it cannot
recover erased TypeScript property types. Sync targets remain responsible for
decoding and validating domain fields received from untrusted external data.
restore(snapshot, { mode: "merge" }) keeps existing model keys and writes the
incoming keys. The default mode replaces the model root’s keys with the
snapshot’s keys.
Executable Sample
<!doctype html><html><head><scripttype="module">import"../../utils/source-bootstrap.js";import"./user-model.js";</script></head><bodyng-app="modelDemo"><sectionng-controller="ProfileCtrl as profile"><label> Name
<inputng-model="profile.user.name"aria-label="Name"data-testid="profile-name"/></label><buttonng-click="profile.signIn()">Sign in</button><buttonng-click="profile.reset()">Reset</button></section><sectionng-controller="HeaderCtrl as header"><strongdata-testid="header-name">{{ header.user.name }}</strong><spandata-testid="header-status"> {{ header.user.authenticated ? 'signed in' : 'guest' }}
</span><spandata-testid="cart-count">{{ header.cart.items.length }}</span></section></body></html>
Rejected requests use the same response object shape as successful requests. Use
status for HTTP errors and xhrStatus for transport outcomes such as timeouts
and aborts.
$http.get<User>('/api/users/99').catch((error)=>{if(error.xhrStatus==='timeout'){this.error='Request timed out.';}elseif(error.status===404){this.error='User not found.';}elseif(error.status===0){this.error='Network error.';}});
XSRF
$http can read a configured XSRF cookie and send it in a configured request
header. Cross-origin APIs must be listed as trusted origins before AngularTS
sends the token to them.
subscribe() returns an unsubscribe function. Keep that function and call it
during teardown, especially from long-lived services, directive controllers, or
manually bootstrapped integrations.
One-time listeners
Use subscribeOnce() for initialization handshakes where only the first event
matters.
This makes $eventBus useful for browser callbacks, WebSocket messages, Web
Worker results, and other boundaries where you want the publisher to stay
independent of Angular controller timing. When a subscriber changes view state,
write through proxied scope or controller state so AngularTS can observe the
assignment.
Error handling
If a subscriber throws, $eventBus forwards the value unchanged to
$exceptionHandler. The default handler rethrows, so the current publication
stops at that listener. Do not rely on later listeners running after an
exception.
Subscribers that need independent failure ownership should start an operation
with its own result or rejection boundary instead of using listener order as a
recovery mechanism.
Control delivery with a policy
Configure one callable policy when event delivery must follow an
application-wide rule. The callback is contextually typed as
EventDeliveryPolicy, so
ordinary configuration does not require a separate decision type.
Return "deliver" or "drop" for the common case. Return a structured policy
decision when shared policy infrastructure needs metadata such as a reason or
status.
Scope events
Use scope events when the relationship is already expressed by the scope tree.
Publisher and subscriber do not share a scope ancestry
Yes
No
Event comes from non-Angular code
Yes
No
Communication is strictly parent/child
Usually no
Yes
Delivery should be async
Yes
No
Listener cleanup should be tied to a scope
Works with $destroy
Built for it
Use $eventBus for cross-boundary messages such as WebSocket events, global
notifications, analytics readiness, and application-level domain events. Use
scope events for local component coordination.
EventSource does not support arbitrary custom headers in browsers. For authenticated streams, use cookies with withCredentials or include a short-lived token in query params.
WebSockets
$websocket returns a managed WebSocket connection with reconnects, heartbeat handling, message transforms, and send support.
send() serializes values as JSON before passing them to the native WebSocket.
WebTransport
$webTransport opens a browser-native WebTransport session. Use it when an
endpoint can serve HTTP/3 and the client benefits from unreliable datagrams,
reliable streams, or both in the same session.
The service expects the browser WebTransport API to exist and requires an
https: URL with an explicit port. The test backend exposes certificate hash
metadata at /webtransport/cert-hash for local browser tests.
Reconnect is opt-in at the service layer. When enabled, the
WebTransportConnection object stays stable while its native transport
instance is replaced. Use onReconnect as the renegotiation hook for
subscriptions, authentication messages, or other session state that the server
does not remember across HTTP/3 sessions.
For template-level feeds, ng-web-transport connects on load by default and
evaluates lifecycle expressions. data-mode="datagram" is the default;
data-mode="stream" reads server-opened unidirectional streams.
data-transform accepts bytes, text, or json. Message expressions receive
$connection, $data, $message, $event, and $text for text/json modes.
Reconnect is opt-in with data-reconnect="true"; tune it with
data-retry-delay and data-max-retries. on-reconnect runs after the
replacement session is ready and receives $attempt, $connection, error,
and $url.
Web Workers
Use ng-worker when a view action should run CPU-heavy JavaScript outside the main thread.
The directive evaluates data-params, posts the result to the worker, and
exposes worker responses as $result in on-result. Worker output is never
interpreted as HTML. Bind it into scope or a model and let normal directives
render the result.
Register expensive or shared workers once and reference their injectable
handle. data-request enables correlated request-response delivery:
The directive subscribes to a shared handle but does not terminate it when the
element scope is destroyed. Workers created directly from an ng-worker script
path remain element-owned and are terminated with that scope.
A worker module receives values through self.onmessage and returns results through self.postMessage.
Define typed REST endpoints once and use $rest for list, get, create, update, and delete flows backed by pluggable backends.
$rest wraps a REST backend with a small typed resource client. By default it
uses $http; pass a custom backend when a resource should read from another
data source or compose network and cache behavior.
Params that are not consumed by the template are forwarded to $http as query
params.
Map server data to classes
Pass an entity class when the raw response needs normalization, computed
properties, or methods.
classArticle{id: number;title: string;publishedAt: Date;constructor(data: any){this.id=data.id;this.title=data.title;this.publishedAt=newDate(data.published_at);}getisPublished() {returnthis.publishedAt.getTime()<=Date.now();}}constarticles=$rest<Article,number>('/api/articles',Article);constarticle=awaitarticles.get(42);if(article?.isPublished){// article is a real Article instance.
}
If you omit the entity class, $rest returns the parsed response data as-is.
Handle writes
create() sends POST, update() sends PUT, and delete() sends DELETE.
The methods intentionally stay close to HTTP semantics so errors and
interceptors still flow through $http.
get(), create(), and update() return null for an empty response.
delete() resolves with no value after the backend succeeds. Backend failures
reject for every operation, allowing one application error boundary or policy to
handle them consistently.
$rest does not perform framework-property cleanup itself. With the default
HTTP backend, $http deproxies scope payloads before JSON serialization, so
proxy helpers such as _target, _handler, and _proxy do not reach the
server. Generated repeat identity is stored as internal metadata rather than on
your model object, so it is not included in request bodies. Explicit
application-owned properties remain part of the payload.
Use a cached backend
CachedRestBackend wraps a
network backend and an async cache store. The default HTTP backend remains
available through
HttpRestBackend, while cache
storage can be memory, IndexedDB, the Cache API, or any object that implements
RestCacheStore.
Supported policy results are cache-first, network-first, and
stale-while-revalidate. Use the strategy option when every request should
use one static result. Writes always go to the network backend first; successful
writes invalidate cached collection and entity keys for the resource.
Cache keys are generated by
CachedRestBackend. A
RestCacheStore receives the
final key string in get(), set(), delete(), and deletePrefix() and
should treat that key as opaque. createRestCacheKey() is an internal REST
module helper, not a top-level namespace API.
Write a custom backend
A custom backend implements
RestBackend. It receives
normalized requests and returns raw response data for
RestService to map.
Use HttpRestBackend when the
backend should delegate to $http, and wrap it with
CachedRestBackend when
reads should use one of the cache strategies.
CRUD demo
The demo at /src/services/rest/rest-crud-demo.html uses the Go demo backend
through /api/tasks. It shows list(), get(), create(), update(), and
delete() against a real HTTP endpoint, renders rows with ng-repeat, and
includes a cache strategy toggle for network-first, cache-first, and
stale-while-revalidate.
Navigate between named states, read and modify the URL, manage history, and intercept route transitions.
AngularTS has two routing layers. $location manages the browser URL and
history directly. $state works at the application level with named states,
parameters, resolves, and transition hooks.
go() returns a transition promise. Use transition options when you need to
control reloads, parameter inheritance, URL updates, or relative navigation.
Generate Links
Use $state.href() when templates or controllers need a URL without starting
navigation.
Read and write cookies with $cookie, serialize objects, and use $window.localStorage or sessionStorage for client-side persistence.
AngularTS provides $cookie for typed, injectable cookie access and $window
for direct browser storage access. Prefer injected services over globals so unit
tests can replace browser APIs without patching window or document.
A cookie can only be removed when the path and domain used for removal match
the values used when it was created. If a cookie was created with path: "/",
pass the same path when removing it.
Cookie Defaults
Set defaults once when every cookie should share the same attributes.