README
ExpData
Asynchronous Typescript library for storing data in a directory-like key structure for a specified period of time with a common interface for localStorage and indexedDB.
Overview and Storage Types
While this library is being used by its creators on Angular 2 projects, the implementation is can be used on any type of site. It's strength is the common functionality between all the implementations.
When you start a project you might think you will be storing a small amount of data, so you opt for a quick localStorage solution. As the project progresses you find your storage needs increase dramatically and indexedDB will be required. Changing over is as simple as changing the type of your storage provider from ExpiringLocalData to ExpiringIndexedData. All of the methods are the same in both classes, so the rest of the code will not need to be changed. Here is an overview of the classes:
ExpiringData
is the abstract base class for the three built-in data providers. They are ExpiringLocalData
,
ExpiringIndexedData
and ExpiringEmptyData
. ExpiringData
provides three public member functions for
saving, retrieving and removing data using specialized directory-like keys. Data is not editable but can be overwritten.
ExpiringLocalData
ExpiringLocalData
extends ExpiringData
and stores data in localStorage. If localStorage is disabled, it
will fall back to an in-memory mock localStorage implementation named MockLocalStorage
.
ExpiringIndexedData
ExpiringIndexedData
extends ExpiringData
and stores data in indexedDB. If indexedDB is disabled, it will
not store or retrieve any data but will always return true
ExpiringEmptyData
ExpiringEmptyData
does not store or retrieve any data. It is included for implementations that require an
ExpiringData
object, but you do not want to actually store any data. This is handy during development when you
are simultaneously working on the frontend and backend api and do not want the data cached.
Storing Data
ExpiringData.setData(key: string, data: any, lifetime?: Date|number|string) : Observable<boolean>
Parameters:
key
Must be an item based key of the form 'element' or 'group1|group2|...|groupN|element'. Examples would be 'favorites|color' or 'myFavoriteColor'. The only restrictions are that it cannot end in a pipe character ('|') or an asterisk ('*').data
The data you want to store. Can be any object, array, string, number, boolean. It cannot store data structures with functions. The functions are removed in the JSON.stringify() process.lifetime
(optional)- If null or empty, data will never expire.
- If a Javascript Date object, this represents the date and time that the data will expire on.
- If a number, this is the getTime() representation (in milliseconds) of a javascript Date object on which the data will expire.
- If a string, should be of the form '[integer][time unit]'. Valid time units are 's' for seconds, 'm' for minutes,
'h' for hours and 'd' for days. Examples:
- '3h' = Expires 3 hours from now
- '5d' = Expires 5 days from now
There is a maximum lifetime of roughly 50 years past current time. Any lifetime received greater than this maximum will be set to the maximum lifetime.
Examples:
import { ExpiringLocalData } from "expdata";
var local = new ExpiringLocalData();
local.setData('favorites|color', 'red', '3d'); // store for 3 days
local.setData('favorites|numbers', [7, 22], '2h'); // store array for 2 hours
var favWebsite = {name: 'NPM', url: 'https://www.npmjs.com/'};
local.setData('favorites|website', favWebsite, '30m'); // store object for 30 minutes
// full example, with event handling
local.setData('favorites|fruit', 'apple', '30d')
.subscribe(
() => { console.log("Data successfully set..."); },
e => { console.log("Error setting expiring data ...", e); },
() => { console.log("Setting expiring data completed."); }
);
Retrieving Data
ExpiringData.getData(key: string, defaultData: any) : Observable<any>;
Parameters:
key
Must be Elemental of the form 'elementName' or 'group1|...|groupN|elementName'. Examples would be 'myFavoriteColor' or 'favorites|color'. The only restrictions are that it cannot end in a pipe character ('|') or an asterisk ('*').defaultData
(optional) The data you would like reflected back if no cached data is found. If not provided, a null value becomes the default.
Examples:
import { ExpiringIndexedDb } from "expdata";
var indexed = new ExpiringIndexedDb();
var defaultDataGridSettings = {sortColumn: "ID", sortOrder: "descending"};
var myDataGridSettings = {sortColumn: "LastName", sortOrder: "ascending"};
indexed.setData("myComponent|gridSettings", myDataGridSettings, "30m")
.subscribe(
() => {... data successfully set ...},
() => {... error setting data ...}
);
indexed.getData("myComponent|gridSettings", defaultDataGridSettings);
// returns {sortColumn: "LastName", sortOrder: "ascending"}
// ... wait 30 minutes for data to expire ...
indexed.getData("myComponent|gridSettings", defaultDataGridSettings);
// returns {sortColumn: "ID", sortOrder: "descending"}
Removing Data
ExpiringData.removeData(key: string);
Parameters:
key
Can be Elemental or Group key. Elemental keys are of the form 'elementName' or 'group1|...|groupN|elementName'. Group keys are of the form 'group1|...|groupN|*'. Elemental keys remove a single element from storage while group keys remove the entire group and the root element. Special key '*' removes all data in storage.
import { ExpiringLocalData } from "expdata";
var local = new ExpiringLocalData();
// ********* All functions return via Observable.subscribe() *******************
// set some test data
local.setData('a', 'data-a');
local.setData('b', 'data-b');
local.setData('c', 'data-c');
local.setData('a|a', 'data-aa');
local.setData('a|b', 'data-ab');
local.setData('a|c', 'data-ac');
local.setData('b|a', 'data-ba');
local.setData('b|a|a', 'data-baa');
local.setData('b|a|b', 'data-bab');
// remove a single element
local.removeData('a|c');
local.getData('a|c'); // -> null
local.getData('a|b'); // -> 'data-ab'
// remove a level 1 group
local.removeData('a|*');
local.getData('a'); // -> null
local.getData('a|a'); // -> null
local.getData('a|b'); // -> null
// remove a level 2 group
local.removeData('b|a|*');
local.getData('b|a'); // -> null
local.getData('b|a|a'); // -> null
local.getData('b|a|b'); // -> null
local.getData('c'); // -> 'data-c'
// remove all remaining elements
local.removeData('*');
local.getData('b'); // -> null
local.getData('c'); // -> null
What is this for?
The most common implementations would be to store user preferences or component specific data. The directory-like structure of the keys helps with this. For instance, when a user signs in you can get their userId and store their preferences in the 'user|100|...' group (e.g. 'user|100|gridSettings'). If a different user signs in, they can be stored in the 'user|101|...' group. The data can be segregated for multiple users and deleting all the data for an individual user can be accomplished with removing group 'user|N|*'.
What's Next
While the library is handy to store component data on an individual basis, the kind of data you will most likely be storing are responses from an http/xhr request. It sure would be nice if this library could be coupled with an xhr wrapper to simplify this process. For Angular 2, there is! It's called exphttp (currently in progress).