This is the multi-page printable view of this section. Click here to print.
Service Guides
1 - Making HTTP requests with the $http service
Use $http for application HTTP calls when you need request configuration,
response transforms, interceptors, or integration with AngularTS services.
This guide focuses on workflows. Exact call signatures and exported interfaces live in TypeDoc:
Basic Requests
$http.get<User>('/api/users/42').then(({ data }) => {
this.user = data;
});
Use a full request config when method, URL, headers, params, timeout, response type, or upload handlers need to be assembled together.
$http({
method: 'POST',
url: '/api/users',
data: { name: 'Ada' },
headers: { 'X-Trace': traceId },
}).then(({ data }) => {
this.user = data;
});
Query Parameters
params are appended to the URL using $httpParamSerializer.
$http.get<Article[]>('/api/articles', {
params: {
category: 'news',
tags: ['featured', 'breaking'],
},
});
The default serializer repeats array keys, JSON-encodes object values, sorts
keys alphabetically, and omits null, undefined, and function values.
Request Bodies
Plain objects are JSON serialized by default. FormData, Blob, and File
values are sent as native browser payloads.
const formData = new FormData();
formData.append('avatar', fileInput.files[0]);
$http.post('/api/users/42/avatar', formData, {
uploadEventHandlers: {
progress(event: ProgressEvent) {
this.progress = Math.round((event.loaded / event.total) * 100);
},
},
});
Headers And Defaults
Set application-wide defaults during module configuration:
angular.module('app', []).config(($httpProvider) => {
$httpProvider.defaults.headers.common.Authorization = 'Bearer token';
$httpProvider.defaults.withCredentials = true;
});
Runtime defaults are available through $http.defaults:
angular.module('app').run(($http) => {
$http.defaults.headers.common['X-App-Version'] = '2.1.0';
});
Interceptors
Interceptors centralize cross-cutting request and response behavior such as auth headers, retries, logging, and redirects.
angular.module('app', []).config(($httpProvider) => {
$httpProvider.interceptors.push(() => ({
request(config) {
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
},
responseError(rejection) {
if (rejection.status === 401) {
window.location.href = '/login';
}
return Promise.reject(rejection);
},
}));
});
Error Handling
Rejected requests use the same response object shape as successful requests.
Use status for HTTP errors and xhrStatus for transport outcomes such as
timeouts and aborts.
$http.get<User>('/api/users/99').catch((error) => {
if (error.xhrStatus === 'timeout') {
this.error = 'Request timed out.';
} else if (error.status === 404) {
this.error = 'User not found.';
} else if (error.status === 0) {
this.error = 'Network error.';
}
});
XSRF
$http can read a configured XSRF cookie and send it in a configured request
header. Cross-origin APIs must be listed as trusted origins before AngularTS
sends the token to them.
angular.module('app', []).config(($httpProvider) => {
$httpProvider.xsrfTrustedOrigins.push('https://api.example.com');
});
Next Steps
- HTTP directives for declarative request attributes in HTML.
$restservice for typed CRUD resources built on$http.
2 - Decoupled Messaging
AngularTS has two messaging models:
$eventBusis an application-widePubSubinstance for decoupled communication, including messages from non-Angular code.- Scope events (
$scope.$on,$scope.$emit,$scope.$broadcast) travel through the scope tree and are best for parent/child communication.
Exact $eventBus method signatures live in TypeDoc:
Publish and subscribe
Inject $eventBus when a publisher and subscriber should not know about each other.
class CartService {
static $inject = ["$eventBus"];
constructor(private $eventBus: ng.PubSub) {}
addItem(product: Product, quantity: number) {
this.$eventBus.publish("cart:item-added", { product, quantity });
}
}
class HeaderController {
static $inject = ["$eventBus", "$scope"];
cartCount = 0;
constructor($eventBus: ng.PubSub, $scope: ng.Scope) {
const unsubscribe = $eventBus.subscribe("cart:item-added", () => {
const view = $scope["$ctrl"] as HeaderController;
view.cartCount += 1;
});
$scope.$on("$destroy", unsubscribe);
}
}
subscribe() returns an unsubscribe function. Keep that function and call it during teardown, especially from long-lived services, directive controllers, or manually bootstrapped integrations.
One-time listeners
Use subscribeOnce() for initialization handshakes where only the first event matters.
class AnalyticsBootstrap {
static $inject = ["$eventBus"];
constructor($eventBus: ng.PubSub) {
$eventBus.subscribeOnce("analytics:ready", (sdk) => {
sdk.track("session_start");
});
}
}
window.onAnalyticsReady = (sdk) => {
angular.$eventBus.publish("analytics:ready", sdk);
};
Async delivery
$eventBus schedules delivery with queueMicrotask. publish() returns after scheduling the event, and subscribers run after the current call stack.
$eventBus.subscribe("order:created", (order) => {
console.log("subscriber", order.id);
});
$eventBus.publish("order:created", { id: 42 });
console.log("publisher finished");
This makes $eventBus useful for browser callbacks, WebSocket messages, Web Worker results, and other boundaries where you want the publisher to stay independent of Angular controller timing. When a subscriber changes view state, write through proxied scope or controller state so AngularTS can observe the assignment.
Error handling
If a subscriber throws, $eventBus forwards the error to $exceptionHandler and continues delivering the event to the remaining subscribers.
$eventBus.subscribe("order:created", () => {
throw new Error("failed listener");
});
$eventBus.subscribe("order:created", (order) => {
console.log("still delivered", order.id);
});
This keeps one failing listener from blocking unrelated subscribers.
Scope events
Use scope events when the relationship is already expressed by the scope tree.
| Method | Direction | Use for |
|---|---|---|
$scope.$broadcast(event, args) | Down to descendants | Parent notifying child scopes |
$scope.$emit(event, args) | Up toward $rootScope | Child notifying parents |
$scope.$on(event, handler) | Current scope listener | Local event handling and cleanup |
$scope.$broadcast("filter:changed", { status: "active" });
$scope.$on("filter:changed", (_event, filter) => {
this.applyFilter(filter);
});
$scope.$emit("child:ready");
Scope listeners are synchronous and return a deregistration function. Pair them with $destroy when the listener can outlive the current view.
const off = $scope.$on("filter:changed", handler);
$scope.$on("$destroy", off);
Choosing a messaging model
| Concern | Prefer $eventBus | Prefer scope events |
|---|---|---|
| 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.
Related
3 - Real-Time Communication
AngularTS provides five building blocks for real-time and compute-heavy work:
$ssefor Server-Sent Events.$websocketfor bidirectional WebSocket connections.$webTransportfor HTTP/3 WebTransport sessions with datagrams and streams.ng-workerfor JavaScript work in a Web Worker.ng-wasmfor loading WebAssembly modules.
Exact service and connection signatures live in TypeDoc:
SseServiceSseConfigSseConnectionWebSocketServiceWebSocketConfigWebSocketConnectionWebTransportServiceWebTransportConfigWebTransportConnectionWorkerConfigWorkerConnection
Server-Sent Events
$sse creates a managed EventSource connection. It handles query parameters, JSON message parsing, heartbeat detection, automatic reconnection, and clean shutdown.
class NewsFeedController {
static $inject = ["$sse", "$scope"];
items: NewsItem[] = [];
private connection: ng.SseConnection;
constructor($sse: ng.SseService, $scope: ng.Scope) {
this.connection = $sse("/api/news/stream", {
withCredentials: true,
params: { category: "top" },
retryDelay: 3000,
heartbeatTimeout: 30000,
onMessage: (item: NewsItem) => {
const view = $scope["$ctrl"] as NewsFeedController;
view.items = [item, ...view.items].slice(0, 50);
},
onReconnect: (attempt) => {
console.log("SSE reconnect", attempt);
},
});
$scope.$on("$destroy", () => this.connection.close());
}
}
EventSource does not support arbitrary custom headers in browsers. For authenticated streams, use cookies with withCredentials or include a short-lived token in query params.
WebSockets
$websocket returns a managed WebSocket connection with reconnects, heartbeat handling, message transforms, and send support.
class ChatController {
static $inject = ["$websocket", "$scope"];
messages: ChatMessage[] = [];
private socket: ng.WebSocketConnection;
constructor($websocket: ng.WebSocketService, $scope: ng.Scope) {
this.socket = $websocket("wss://api.example.com/chat", ["v1"], {
retryDelay: 2000,
maxRetries: 20,
onMessage: (message: ChatMessage) => {
const view = $scope["$ctrl"] as ChatController;
view.messages = [...view.messages, message];
},
onClose: (event) => {
console.log("WebSocket closed", event.code);
},
});
$scope.$on("$destroy", () => this.socket.close());
}
send(text: string) {
this.socket.send({ type: "message", text });
}
}
send() serializes values as JSON before passing them to the native WebSocket.
WebTransport
$webTransport opens a browser-native WebTransport session. Use it when an
endpoint can serve HTTP/3 and the client benefits from unreliable datagrams,
reliable streams, or both in the same session.
class TelemetryController {
static $inject = ["$webTransport", "$scope"];
events: string[] = [];
private session: ng.WebTransportConnection;
constructor($webTransport: ng.WebTransportService, $scope: ng.Scope) {
this.session = $webTransport("https://localhost:4433/webtransport", {
reconnect: true,
retryDelay: 500,
maxRetries: 5,
requireUnreliable: true,
transformDatagram: (data) => new TextDecoder().decode(data),
onDatagram: ({ message }) => {
const view = $scope["$ctrl"] as TelemetryController;
view.events = [...view.events, String(message)];
},
onReconnect: ({ connection }) => {
return connection.sendText(JSON.stringify({ subscribe: "telemetry" }));
},
});
}
send(value: string) {
return this.session.sendText(value);
}
}
The service expects the browser WebTransport API to exist and requires an
https: URL with an explicit port. The test backend exposes certificate hash
metadata at /webtransport/cert-hash for local browser tests.
Reconnect is opt-in at the service layer. When enabled, the
WebTransportConnection object stays stable while its native transport
instance is replaced. Use onReconnect as the renegotiation hook for
subscriptions, authentication messages, or other session state that the server
does not remember across HTTP/3 sessions.
For template-level feeds, ng-web-transport connects on load by default and
evaluates lifecycle expressions. data-mode="datagram" is the default;
data-mode="stream" reads server-opened unidirectional streams.
<div
ng-web-transport="transportUrl"
data-config="transportConfig"
data-mode="datagram"
data-transform="json"
data-as="session"
data-reconnect="true"
data-on-message="events.push($message)"
data-on-reconnect="reconnects = $attempt"
data-on-error="error = $error"
></div>
data-transform accepts bytes, text, or json. Message expressions receive
$connection, $data, $message, $event, and $text for text/json modes.
Reconnect is opt-in with data-reconnect="true"; tune it with
data-retry-delay and data-max-retries. data-on-reconnect runs after the
replacement session is ready and receives $attempt, $connection, $error,
and $url.
Web Workers
Use ng-worker when a view action should run CPU-heavy JavaScript outside the main thread.
<button
ng-worker="./workers/compress.js"
data-params="vm.fileBuffer"
data-on-result="vm.compressed = $result"
data-on-error="vm.error = $error"
trigger="click"
>
Compress
</button>
<span
ng-worker="./workers/sensor-reader.js"
interval="5000"
data-on-result="vm.sensorData = $result"
></span>
The directive evaluates data-params, posts the result to the worker, and exposes worker responses as $result in data-on-result. When no result expression is provided, it swaps the result into the element using the configured swap strategy.
A worker module receives values through self.onmessage and returns results through self.postMessage.
self.onmessage = function ({ data: { limit } }) {
const sieve = new Uint8Array(limit + 1).fill(1);
sieve[0] = sieve[1] = 0;
for (let i = 2; i * i <= limit; i++) {
if (sieve[i]) {
for (let j = i * i; j <= limit; j += i) sieve[j] = 0;
}
}
const primes = [];
for (let i = 2; i <= limit; i++) {
if (sieve[i]) primes.push(i);
}
self.postMessage(primes);
};
WebAssembly
ng-wasm loads a .wasm file and exposes its exports on the scope under a configurable name.
<div
ng-wasm
src="/wasm/image-processor.wasm"
as="imageProcessor"
></div>
const result = $scope.imageProcessor.grayscale(pixelBuffer, width, height);
WebAssembly loading is asynchronous. Guard calls until the export object exists or trigger work after the directive has linked.
Choosing A Transport
| Need | Use |
|---|---|
| Server pushes one-way updates | $sse |
| Client and server both send messages | $websocket |
| HTTP/3 datagrams or streams | $webTransport |
| CPU-heavy JavaScript | ng-worker |
| Compiled compute module | ng-wasm |
Related
4 - Typed REST Resources
$rest wraps a REST backend with a small typed resource client. By default it uses $http; pass a custom backend when a resource should read from another data source or compose network and cache behavior.
Exact method signatures live in TypeDoc:
Register a resource
Register shared resources in a config block. The provider stores resource definitions before the application starts, then the $rest factory uses the live $http service at runtime.
class User {
id: number;
name: string;
createdAt: Date;
constructor(data: any) {
this.id = data.id;
this.name = data.name;
this.createdAt = new Date(data.created_at);
}
}
angular.module("demo", []).config(($restProvider: ng.RestProvider) => {
$restProvider.rest("users", "/api/users", User, {
timeout: 5000,
withCredentials: true,
});
});
The resource name is informational in the current API. The URL can be a plain path or an RFC 6570 URI template.
Create a resource at runtime
Inject $rest anywhere you need a resource client. This keeps controllers and services focused on the workflow instead of repeating request setup.
class UserRepository {
static $inject = ["$rest"];
private users: ng.RestService<User, number>;
constructor($rest: ng.RestFactory) {
this.users = $rest<User, number>("/api/users", User);
}
listAdmins() {
return this.users.list({ role: "admin" });
}
getUser(id: number) {
return this.users.get(id);
}
}
Use URI templates
$rest expands RFC 6570 templates before sending a request. Template variables are taken from the params object you pass to list() or get().
const issues = $rest<Issue>(
"/api/repos/{owner}/{repo}/issues{?labels*}",
Issue,
);
const openBugs = await issues.list({
owner: "angular-wave",
repo: "angular.ts",
labels: ["bug", "ui"],
});
Params that are not consumed by the template are forwarded to $http as query params.
Map server data to classes
Pass an entity class when the raw response needs normalization, computed properties, or methods.
class Article {
id: number;
title: string;
publishedAt: Date;
constructor(data: any) {
this.id = data.id;
this.title = data.title;
this.publishedAt = new Date(data.published_at);
}
get isPublished() {
return this.publishedAt.getTime() <= Date.now();
}
}
const articles = $rest<Article, number>("/api/articles", Article);
const article = await articles.get(42);
if (article?.isPublished) {
// article is a real Article instance.
}
If you omit the entity class, $rest returns the parsed response data as-is.
Handle writes
create() sends POST, update() sends PUT, and delete() sends DELETE. The methods intentionally stay close to HTTP semantics so errors and interceptors still flow through $http.
class ArticleController {
static $inject = ["$rest"];
private articles: ng.RestService<Article, number>;
items: Article[] = [];
constructor($rest: ng.RestFactory) {
this.articles = $rest<Article, number>("/api/articles", Article);
}
async publish(draft: Partial<Article>) {
const created = await this.articles.create(draft as Article);
this.items.unshift(created as Article);
}
async rename(id: number, title: string) {
const updated = await this.articles.update(id, { title });
if (updated) {
this.items = this.items.map((item) =>
item.id === id ? (updated as Article) : item,
);
}
}
async remove(id: number) {
if (await this.articles.delete(id)) {
this.items = this.items.filter((item) => item.id !== id);
}
}
}
$rest does not perform framework-property cleanup itself. With the default
HTTP backend, $http deproxies scope payloads before JSON serialization, so
proxy helpers such as $target, $handler, and $proxy do not reach the
server. Generated repeat identity is stored as internal metadata rather than on
your model object, so it is not included in request bodies. Explicit
application-owned properties remain part of the payload.
Use a cached backend
CachedRestBackend wraps a network backend and an async cache store. The default HTTP backend remains available through HttpRestBackend, while cache storage can be memory, IndexedDB, the Cache API, or any object that implements RestCacheStore.
import {
CachedRestBackend,
HttpRestBackend,
} from "@angular-wave/angular.ts/services/rest";
import type {
RestCacheStore,
RestResponse,
} from "@angular-wave/angular.ts/services/rest";
class MapRestCacheStore implements RestCacheStore {
private cache = new Map<string, RestResponse<unknown>>();
async get<T>(key: string): Promise<RestResponse<T> | undefined> {
return this.cache.get(key) as RestResponse<T> | undefined;
}
async set<T>(key: string, response: RestResponse<T>): Promise<void> {
this.cache.set(key, response as RestResponse<unknown>);
}
async delete(key: string): Promise<void> {
this.cache.delete(key);
}
async deletePrefix(prefix: string): Promise<void> {
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
}
const cache = new MapRestCacheStore();
const backend = new CachedRestBackend({
network: new HttpRestBackend($http),
cache,
strategy: "network-first",
});
const articles = $rest<Article, number>("/api/articles", Article, {
backend,
});
Supported read strategies are cache-first, network-first, and stale-while-revalidate. Writes always go to the network backend first; successful writes invalidate cached collection and entity keys for the resource.
Cache keys are generated by CachedRestBackend. A RestCacheStore receives the
final key string in get(), set(), delete(), and deletePrefix() and should
treat that key as opaque. createRestCacheKey() is an internal REST module
helper, not a top-level namespace API.
Write a custom backend
A custom backend implements RestBackend. It receives normalized requests and
returns raw response data for RestService to map.
class IndexedDbRestBackend implements ng.RestBackend {
async request<T>(request: ng.RestRequest): Promise<ng.RestResponse<T>> {
if (request.method === "GET") {
return { data: (await readFromDb(request.url)) as T };
}
throw new Error(`Unsupported method: ${request.method}`);
}
}
const articles = $rest<Article, number>("/api/articles", Article, {
backend: new IndexedDbRestBackend(),
});
Use HttpRestBackend when the backend should delegate to $http, and wrap it
with CachedRestBackend when reads should use one of the cache strategies.
CRUD demo
The demo at /src/services/rest/rest-crud-demo.html uses the Go demo backend
through /api/tasks. It shows list(), get(), create(), update(), and
delete() against a real HTTP endpoint, renders rows with ng-repeat, and
includes a cache strategy toggle for network-first, cache-first, and
stale-while-revalidate.
Related
5 - Client-Side Routing
AngularTS has two routing layers. $location manages the browser URL and history directly. $state works at the application level with named states, parameters, resolves, and transition hooks.
Exact routing API signatures live in TypeDoc:
Work With The URL
Use $location when code needs to inspect or change the raw URL.
$location.path(); // "/dashboard"
$location.search(); // { tab: "overview" }
$location.hash(); // "summary"
$location.url(); // "/dashboard?tab=overview#summary"
$location.absUrl(); // "https://app.example.com/dashboard?tab=overview#summary"
Setter methods return $location, so related URL changes can be chained.
$location
.path("/settings/profile")
.search({ tab: "security" })
.hash("billing-section");
Changes to $location are applied asynchronously. $locationChangeStart and $locationChangeSuccess are broadcast on $rootScope around navigation.
Configure URL Mode
Configure $locationProvider before the application runs.
angular.module("demo", []).config(($locationProvider: ng.LocationProvider) => {
$locationProvider.html5Mode({
enabled: true,
requireBase: false,
rewriteLinks: true,
});
$locationProvider.hashPrefix("!");
});
When requireBase is enabled, the application document must include a <base> tag.
Navigate With $state
Use $state.go() for normal application navigation. It accepts absolute state names, parent-relative names, and sibling-relative names.
$state.go("contacts.detail", { id: 42 });
$state.go("^.list");
$state.go(".detail", { id: 42 });
$state.go($state.current, $state.params, { reload: true });
go() returns a transition promise. Use transition options when you need to control reloads, parameter inheritance, URL updates, or relative navigation.
Generate Links
Use $state.href() when templates or controllers need a URL without starting navigation.
const relative = $state.href("contacts.detail", { id: 42 });
const absolute = $state.href(
"contacts.detail",
{ id: 42 },
{ absolute: true },
);
Check Active States
Use is() for exact matches and includes() for ancestors or glob patterns.
$state.is("contacts.detail");
$state.is("contacts.detail", { id: 42 });
$state.includes("contacts");
$state.includes("*.detail");
These helpers are useful for active navigation styling and conditional UI.
Register States At Runtime
$stateRegistry stores state definitions and can register states after bootstrap, which is useful for lazy-loaded feature modules.
const detail = $stateRegistry.get("contacts.detail");
const allStates = $stateRegistry.get();
$stateRegistry.register({
name: "profile",
url: "/profile",
component: "profilePage",
});
Handle Navigation Events
Listen on $rootScope for URL-level events when you need a broad guard.
angular.module("demo").run(($rootScope, $state, authService) => {
$rootScope.$on("$locationChangeStart", (event, newUrl) => {
if (newUrl.includes("/admin") && !authService.isAuthenticated()) {
event.preventDefault();
$state.go("login", { returnUrl: newUrl });
}
});
});
For state-level lifecycle work, prefer transition hooks.
Example: Programmatic Navigation
class OrderController {
static $inject = ["$state"];
order!: Order;
constructor(private $state: ng.StateService) {}
viewOrder(id: number) {
this.$state.go("orders.detail", { orderId: id });
}
backToList() {
this.$state.go("^");
}
get orderLink(): string | null {
return this.$state.href("orders.detail", { orderId: this.order.id });
}
}
Related
6 - Cookies And Browser Storage
AngularTS provides $cookie for typed, injectable cookie access and $window for direct browser storage access. Prefer injected services over globals so unit tests can replace browser APIs without patching window or document.
Exact cookie API signatures live in TypeDoc:
Read Cookies
$cookie decodes keys and values, parses document.cookie, and caches the parsed cookie map until the browser cookie string changes.
const token = $cookie.get("session_token");
const prefs = $cookie.getObject<UserPreferences>("user_prefs");
const all = $cookie.getAll();
Use get() for raw string values and getObject() only for cookies you control and know contain JSON.
Write Cookies
Use put() for strings and putObject() for JSON-serializable values.
$cookie.put("session_token", "abc123", {
path: "/",
secure: true,
samesite: "Strict",
expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});
$cookie.putObject(
"user_prefs",
{ theme: "dark", fontSize: 14 },
{
path: "/",
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
},
);
Cookie attributes are passed through the CookieOptions object. Common options are path, domain, expires, secure, and samesite.
Remove Cookies
remove() expires the cookie by writing an old expiration date.
$cookie.remove("session_token");
$cookie.remove("session_token", {
path: "/app",
domain: ".example.com",
});
A cookie can only be removed when the path and domain used for removal match the values used when it was created. If a cookie was created with path: "/", pass the same path when removing it.
Provider Defaults
Set defaults once when every cookie should share the same attributes.
angular.module("demo", []).config(($cookieProvider: ng.CookieProvider) => {
$cookieProvider.defaults = {
path: "/",
secure: true,
samesite: "Lax",
};
});
Per-call options are merged on top of provider defaults, so individual writes can still override a field.
Local And Session Storage
AngularTS exposes the browser window object through $window. Inject $window when a service needs localStorage or sessionStorage.
class PreferencesStorage {
static $inject = ["$window"];
constructor(private $window: Window & typeof globalThis) {}
saveTheme(theme: string): void {
this.$window.localStorage.setItem("theme", theme);
}
loadTheme(): string {
return this.$window.localStorage.getItem("theme") ?? "light";
}
saveSessionData(key: string, data: unknown): void {
this.$window.sessionStorage.setItem(key, JSON.stringify(data));
}
loadSessionData<T>(key: string): T | null {
const raw = this.$window.sessionStorage.getItem(key);
if (!raw) return null;
try {
return JSON.parse(raw) as T;
} catch {
return null;
}
}
}
| Concern | localStorage | sessionStorage |
|---|---|---|
| Persistence | Until explicitly cleared | Until the browser tab closes |
| Scope | Shared across same-origin tabs | Isolated to the current tab |
| Typical use | Preferences and cached data | Wizard state and temporary form data |
Storage Events
Listen for storage changes from other tabs through $window.
angular.module("demo").run(($window, $rootScope) => {
$window.addEventListener("storage", (event: StorageEvent) => {
if (event.key === "theme") {
$rootScope.$broadcast("themeChanged", event.newValue);
}
});
});
The browser only fires storage events in other same-origin tabs or windows, not in the tab that made the change.
Example: Remember Me
class AuthService {
static $inject = ["$cookie", "$window"];
constructor(
private $cookie: ng.CookieService,
private $window: Window & typeof globalThis,
) {}
login(token: string, rememberMe: boolean) {
if (rememberMe) {
this.$cookie.put("auth_token", token, {
path: "/",
secure: true,
samesite: "Strict",
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});
} else {
this.$window.sessionStorage.setItem("auth_token", token);
}
}
getToken(): string | null {
return (
this.$cookie.get("auth_token") ??
this.$window.sessionStorage.getItem("auth_token")
);
}
logout() {
this.$cookie.remove("auth_token", { path: "/" });
this.$window.sessionStorage.removeItem("auth_token");
}
}