/** * @license * Copyright Google Inc. 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.io/license */ import {APP_ID, Injectable, NgModule} from '@angular/core'; import {DOCUMENT} from '../dom/dom_tokens'; export function escapeHtml(text: string): string { const escapedText: {[k: string]: string} = { '&': '&a;', '"': '&q;', '\'': '&s;', '<': '&l;', '>': '&g;', }; return text.replace(/[&"'<>]/g, s => escapedText[s]); } export function unescapeHtml(text: string): string { const unescapedText: {[k: string]: string} = { '&a;': '&', '&q;': '"', '&s;': '\'', '&l;': '<', '&g;': '>', }; return text.replace(/&[^;]+;/g, s => unescapedText[s]); } /** * A type-safe key to use with `TransferState`. * * Example: * * ``` * const COUNTER_KEY = makeStateKey('counter'); * let value = 10; * * transferState.set(COUNTER_KEY, value); * ``` * * @experimental */ export type StateKey = string & {__not_a_string: never}; /** * Create a `StateKey` that can be used to store value of type T with `TransferState`. * * Example: * * ``` * const COUNTER_KEY = makeStateKey('counter'); * let value = 10; * * transferState.set(COUNTER_KEY, value); * ``` * * @experimental */ export function makeStateKey(key: string): StateKey { return key as StateKey; } /** * A key value store that is transferred from the application on the server side to the application * on the client side. * * `TransferState` will be available as an injectable token. To use it import * `ServerTransferStateModule` on the server and `BrowserTransferStateModule` on the client. * * 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 deserialzied in a * non-lossy manner. * * @experimental */ @Injectable() export class TransferState { private store: {[k: string]: {} | undefined} = {}; private onSerializeCallbacks: {[k: string]: () => {} | undefined} = {}; /** @internal */ static init(initState: {}) { const transferState = new TransferState(); transferState.store = initState; return transferState; } /** * Get the value corresponding to a key. Return `defaultValue` if key is not found. */ get(key: StateKey, defaultValue: T): T { return this.store[key] !== undefined ? this.store[key] 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) { return this.store.hasOwnProperty(key); } /** * 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 (this.onSerializeCallbacks.hasOwnProperty(key)) { try { this.store[key] = this.onSerializeCallbacks[key](); } catch (e) { console.warn('Exception in onSerialize callback: ', e); } } } return JSON.stringify(this.store); } } export function initTransferState(doc: Document, appId: string) { // Locate the script tag with the JSON data transferred from the server. // The id of the script tag is set to the Angular appId + 'state'. const script = doc.getElementById(appId + '-state'); let initialState = {}; if (script && script.textContent) { try { initialState = JSON.parse(unescapeHtml(script.textContent)); } catch (e) { console.warn('Exception while restoring TransferState for app ' + appId, e); } } return TransferState.init(initialState); } /** * NgModule to install on the client side while using the `TransferState` to transfer state from * server to client. * * @experimental */ @NgModule({ providers: [{provide: TransferState, useFactory: initTransferState, deps: [DOCUMENT, APP_ID]}], }) export class BrowserTransferStateModule { }