Skip to content

Instantly share code, notes, and snippets.

@neatorobito
Created May 26, 2020 02:51
Show Gist options
  • Save neatorobito/5c0748641cfa7e60aa4698deb4a62c95 to your computer and use it in GitHub Desktop.
Save neatorobito/5c0748641cfa7e60aa4698deb4a62c95 to your computer and use it in GitHub Desktop.
State Management for Vue based on almy
import _Vue from 'vue';
export namespace StateService
{
export const StorePlugin = {
install(Vue : typeof _Vue, options : { stateModel : any })
{
let store = new Store();
if(!options || !options.stateModel)
{
throw('Please pass an initial state to the store.');
}
store._internal= _Vue.observable(options.stateModel);
Vue.prototype.$store = store;
}
};
export class Store {
public _internal : { [key : string] : any } = null;
private _listeners : { [key : string] : Array<(value : any) => void> } = null;
constructor()
{
this.initialize();
}
/**
* Initializes the state store.
* @returns None
*/
initialize(): void
{
this._internal = {};
this._listeners = {};
}
/**
* Creates or updates the value in the state store and dispatches notifications to any subscribed handlers.
* Listeners are always notified before the value has been updated because they will receive the new value via callback.
* @param key - The name of the value
* @param value - The value
*
*/
dispatch(key : string, value : any, doNotOptimize? : boolean, doNotChainDispatch? : boolean): void
{
if (this._internal[key] === value && !doNotOptimize)
{
return;
}
if (this._listeners[key])
{
for (let i = 0; i < this._listeners[key].length; ++i)
{
this._listeners[key][i](value);
}
}
this._internal[key] = value;
if (typeof value === 'object' && !doNotChainDispatch)
{
for (let prop in value) {
// eslint-disable-next-line no-prototype-builtins
if (value.hasOwnProperty(prop)) {
this.dispatch(key + '->' + prop, value[prop], doNotOptimize, true);
}
}
}
if (/->/.test(key) && !doNotChainDispatch)
{
let parentAndChild = key.split('->');
let parent = parentAndChild[0];
let child = parentAndChild[1];
if (!this._internal[parent])
{
this._internal[parent] = {};
}
this._internal[parent][child] = value;
this.dispatch(parent, this._internal[parent], true, true);
}
}
/**
* Retrieves a value from the state store.
*
* @param key - The name of the value
* @returns The value
*
*/
state<T>(key : string): T
{
let foundObj = null;
if(key && Object.keys(this._internal).includes(key))
{
foundObj = this._internal[key];
}
else
{
throw('The item ' + key + ' could not be found in the state store.');
}
return foundObj;
}
/**
* Subscribes to dispatched events.
*
* @param key - The name of the value
* @param callback - A function that is called when the value is updated
*
* @note If a event has been dispatched before, the callback will be called immediately after subscribing.
*/
subscribe(key : string, callback : (value : any) => void): void
{
if (!this._listeners[key])
{
this._listeners[key] = [];
}
this._listeners[key].push(callback);
if (this._internal[key])
{
callback(this._internal[key]);
}
}
}
}
declare module 'vue/types/vue' {
interface Vue {
$store: StateService.Store;
}
}
@neatorobito
Copy link
Author

Sample model

export class AppStateModel {
  message : string;

  constructor()
  {
    this.message = "Hello world!";
  }
}

@neatorobito
Copy link
Author

Plugging into Vue in main.ts:

import { StateService } from './services/State.Service';
Vue.use(StateService.StorePlugin, { stateModel : new AppStateModel() });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment