/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {APP_ID} from './application/application_tokens'; import {inject} from './di/injector_compatibility'; import {ɵɵdefineInjectable} from './di/interface/defs'; import {DOCUMENT} from './document'; /** * A type-safe key to use with `TransferState`. * * Example: * * ```ts * const COUNTER_KEY = makeStateKey('counter'); * let value = 10; * * transferState.set(COUNTER_KEY, value); * ``` * * @publicApi */ export type StateKey = string & { __not_a_string: never; __value_type?: T; }; /** * Create a `StateKey` that can be used to store value of type T with `TransferState`. * * Example: * * ```ts * const COUNTER_KEY = makeStateKey('counter'); * let value = 10; * * transferState.set(COUNTER_KEY, value); * ``` * * @publicApi */ export function makeStateKey(key: string): StateKey { return key as StateKey; } function createDictionary(): Record { // StateKey can be any string, including names of special Object prototype properties. return Object.create(null); } /** * A key value store that is transferred from the application on the server side to the application * on the client side. * * The `TransferState` is available as an injectable token. * On the client, just inject this token using DI and use it, it will be lazily initialized. * On the server it's already included if `renderApplication` function is used. Otherwise, import * the `ServerTransferStateModule` module to make the `TransferState` available. * * The values in the store are serialized/deserialized using JSON.stringify/JSON.parse. So only * boolean, number, string, null and non-class objects will be serialized and deserialized in a * non-lossy manner. * * @publicApi */ export class TransferState { /** @nocollapse */ static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({ token: TransferState, providedIn: 'root', factory: () => { const transferState = new TransferState(); if (typeof ngServerMode === 'undefined' || !ngServerMode) { transferState.store = retrieveTransferredState(inject(DOCUMENT), inject(APP_ID)); } return transferState; }, }); /** @internal */ store: Record = createDictionary(); private onSerializeCallbacks: {[k: string]: () => unknown | undefined} = createDictionary(); /** * Get the value corresponding to a key. Return `defaultValue` if key is not found. */ get(key: StateKey, defaultValue: T): T { if (!Object.hasOwn(this.store, key)) { return defaultValue; } const value = this.store[key]; return value !== undefined ? (value as T) : defaultValue; } /** * Set the value corresponding to a key. */ set(key: StateKey, value: T): void { this.store[key] = value; } /** * Remove a key from the store. */ remove(key: StateKey): void { delete this.store[key]; } /** * Test whether a key exists in the store. */ hasKey(key: StateKey): boolean { return Object.hasOwn(this.store, key); } /** * Indicates whether the state is empty. */ get isEmpty(): boolean { return Object.keys(this.store).length === 0; } /** * Register a callback to provide the value for a key when `toJson` is called. */ onSerialize(key: StateKey, callback: () => T): void { this.onSerializeCallbacks[key] = callback; } /** * Serialize the current state of the store to JSON. */ toJson(): string { // Call the onSerialize callbacks and put those values into the store. for (const key in this.onSerializeCallbacks) { if (Object.hasOwn(this.onSerializeCallbacks, key)) { try { this.store[key] = this.onSerializeCallbacks[key](); } catch (e) { console.warn('Exception in onSerialize callback: ', e); } } } // Escape script tag to avoid break out of