MCPcopy Index your code
hub / github.com/DanWahlin/Observable-Store

github.com/DanWahlin/Observable-Store @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
372 symbols 774 edges 79 files 27 documented · 7% 1 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Node.js CI npm version

Observable Store - State Management for Front-End Applications (Angular, React, Vue.js, or any other)

Observable Store is a front-end state management library that provides a simple yet powerful way to manage state in front-end applications. Front-end state management has become so complex that many of us spend more hours working on the state management code than on the rest of the application. Observable Store has one overall goal - "keep it simple".

The goal of observable store is to provide a small, simple, and consistent way to manage state in any front-end application (Angular, React, Vue.js or any other) while achieving many of the key goals offered by more complex state management solutions. While many front-end frameworks/libraries provide state management functionality, many can be overly complex and are only useable with the target framework/library. Observable Store is simple and can be used with any front-end JavaScript codebase.

Using Observable Store

Key Goals of Observable Store:

  1. Keep it simple!
  2. Single source of truth for state
  3. Store state is immutable
  4. Provide state change notifications to any subscriber
  5. Track state change history
  6. Easy to understand with a minimal amount of code required to get started
  7. Works with any front-end project built with JavaScript or TypeScript (Angular, React, Vue, or anything else)
  8. Integrate with the Redux DevTools (Angular and React currently supported)

Compatibility

  • Angular 17+ — use Observable Store v3 (npm install @codewithdan/observable-store)
  • Angular 14–16 — use Observable Store v2.2.15 (npm install @codewithdan/observable-store@2.2.15)
  • React, Vue, and vanilla JS — v3 works with any modern setup

Development Setup

To run samples locally from this repo:

  1. Build the core modules first:

    bash npm run build

  2. Then go into any sample and install + start:

    bash cd samples/angular-store npm install npm start

Steps to use Observable Store

Here's a simple example of getting started using Observable Store. Note that if you're using TypeScript you can provide additional details about the store state by using an interface or class (additional examples of that can be found below).

  1. Install the Observable Store package:

    npm install @codewithdan/observable-store

  2. Install RxJS - a required peer dependency if your project doesn't already reference it:

    npm install rxjs

  3. Create a class that extends ObservableStore. Optionally pass settings into super() in your class's constructor (view Observable Store settings). While this shows a pure JavaScript approach, ObservableStore also accepts a generic that represents the store type. See the Angular example below for more details.

    ``` javascript export class CustomersStore extends ObservableStore {

    constructor() {
        super({ /* add settings here */ });
    }
    

    } ```

  4. Update the store state using setState(state, action).

    javascript addCustomerToStore(newCustomer) { this.setState({ customer: newCustomer }, 'add_customer'); }

  5. Retrieve store state using getState().

    javascript getCustomerFromStore() { this.getState().customer; }

  6. Subscribe to store changes in other areas of the application by using the store's stateChanged observable.

    ``` javascript // Create CustomersStore object or have it injected if platform supports that

    init() { this.storeSub = this.customersStore.stateChanged.subscribe(state => { if (state) { this.customer = state.customer; } }); }

    // Note: Would need to unsubscribe by calling this.storeSub.unsubscribe() // as the target object is destroyed ```

  7. Access store state history in CustomersStore by calling the stateHistory property (this assumes that the trackStateHistory setting is set to true)

    javascript console.log(this.stateHistory);

API and Settings

Observable Store API

Observable Store Settings

Observable Store Global Settings

Observable Store Extensions

Running the Samples

Open the samples folder available at the Github repo and follow the instructions provided in the readme file for any of the provided sample projects.

Sample Applications

Using Observable Store with Angular

See the samples folder in the Github repo for examples of using Observable Store with Angular.

  1. Create an Angular application using the Angular CLI or another option.

  2. Install @codewithdan/observable-store:

    npm install @codewithdan/observable-store

  3. Add an interface or model object that represents the shape of the data you'd like to add to your store. Here's an example of an interface to store customer state:

    typescript export interface StoreState { customers: Customer[]; customer: Customer; }

  4. Add a service (you can optionally call it a store if you'd like) that extends ObservableStore. Pass the interface or model class that represents the shape of your store data in for T as shown next:

    ``` typescript @Injectable({ providedIn: 'root' }) export class CustomersService extends ObservableStore {

    } ```

  5. In the constructor add a call to super(). The store allows you to turn tracking of store state changes on and off using the trackStateHistory property. See a list of Observable Store Settings.

    typescript constructor() { super({ trackStateHistory: true }); }

  6. Add functions into your service/store to retrieve, store, sort, filter, or perform any actions you'd like. To update the store call setState() and pass the action that is occurring as well as the store state. To get the state out of the store call getState(). Note that store data is immutable and getState() always returns a clone of the store data. Here's a simple example:

    ``` typescript @Injectable({ providedIn: 'root' }) export class CustomersService extends ObservableStore { sorterService: SorterService;

    constructor(sorterService: SorterService) { 
        const initialState = {
            customers: [],
            customer: null
        }
        super({ trackStateHistory: true });
        this.setState(initialState, 'INIT_STATE');
        this.sorterService = sorterService;
    }
    
    get() {
        const { customers } = this.getState();
        if (customers) {
            return of(customers);
        }
        // call server and get data
        // assume async call here that returns observable
        return asyncData;
    }
    
    add(customer: Customer) {
        let state = this.getState();
        state.customers.push(customer);
        this.setState({ customers: state.customers }, 'ADD_CUSTOMER');
    }
    
    remove() {
        let state = this.getState();
        state.customers.splice(state.customers.length - 1, 1);
        this.setState({ customers: state.customers }, 'REMOVE_CUSTOMER');
    }
    
    sort(property: string = 'id') {
        let state = this.getState();
        const sortedState = this.sorterService.sort(state.customers, property);
        this.setState({ customers: sortedState }, 'SORT_CUSTOMERS');
    }
    

    } ```

    While strings are used for actions in the prior examples, you can use string enums (a TypeScript feature) as well if you want to have a set list of actions to choose from:

    ``` typescript export enum CustomersStoreActions { AddCustomer = 'ADD_CUSTOMER', RemoveCustomer = 'REMOVE_CUSTOMER', GetCustomers = 'GET_CUSTOMERS', SortCustomers = 'SORT_CUSTOMERS' }

    // Example of using the enum in a store
    add(customer: Customer) {
        let state = this.getState();
        state.customers.push(customer);
        this.setState({ customers: state.customers }, CustomersStoreActions.AddCustomer);
    }
    

    ```

  7. If you want to view all of the changes to the store you can access the stateHistory property:

    typescript console.log(this.stateHistory);

  8. An example of the state history output is shown next:

    typescript // example stateHistory output [ { "action": "INIT_STATE", "beginState": null, "endState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null } }, { "action": "ADD_CUSTOMER", "beginState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null }, "endState": { "customers": [ { "id": 1545847909628, "name": "Jane Doe", "address": { "street": "1234 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } }, { "id": 1545847921260, "name": "Fred", "address": { "street": "1545847921260 Main St.", "city": "Phoenix", "state": "AZ", "zip": "85258" } } ], "customer": null } } ]

  9. Any component can be notified of changes to the store state by injecting the store and then subscribing to the stateChanged observable:

    ``` typescript customers: Customer[]; storeSub: Subscription;

    constructor(private customersService: CustomersService) { }

    ngOnInit() { // If using async pipe (recommend renaming customers to customers$) // this.customers$ = this.customersService.stateChanged;

    // Can subscribe to stateChanged observable of the store
    this.storeSub = this.customersService.stateChanged.subscribe(state => {
        if (state) {
            this.customers = state.customers;
        }
    });
    
    // Can call service/store to get data directly 
    // It won't fire when the store state changes though in this case
    //this.storeSub = this.customersService.get().subscribe(custs => this.customers = custs);
    

    } ```

    Unsubscribe when the component is destroyed to avoid memory leaks:

    typescript ngOnDestroy() { if (this.storeSub) { this.storeSub.unsubscribe(); } }

Using Observable Store with React

See the samples/react-store folder in the Github repo for a complete example.

  1. Create a React application using Vite or another tool:

    bash npm create vite@latest my-app -- --template react

  2. Install @codewithdan/observable-store and RxJS:

    bash npm install @codewithdan/observable-store rxjs

  3. Create a store class that extends ObservableStore:

    ``` javascript import { ObservableStore } from '@codewithdan/observable-store';

    class CustomersStore extends ObservableStore {

    constructor() {
        super({ trackStateHistory: true });
    }
    
    fetchCustomers() {
        return fetch('/customers.json')
            .then(response => response.json())
            .then(customers => {
                this.setState({ customers }, 'GET_CUSTOMERS');
                return customers;
            });
    }
    
    getCustomers() {
        const state = this.getState();
        if (state && state.customers) {
            return Promise.resolve(state.customers);
        }
        return this.fetchCustomers();
    }
    

    }

    export default new CustomersStore();

Extension points exported contracts — how you extend this code

ObservableStoreExtension (Interface)
(no doc) [2 implementers]
modules/observable-store-extensions/interfaces.ts
Db (Interface)
In-memory database data
samples/angular-store-edits/src/app/core/in-memory-data.service.ts
ObservableStoreExtension (Interface)
(no doc) [1 implementers]
modules/observable-store/interfaces.ts
StoreState (Interface)
(no doc)
samples/angular-store/src/app/core/store/store-state.ts
StoreState (Interface)
(no doc)
samples/angular-simple-store/src/app/core/store/customers.service.ts
StoreState (Interface)
(no doc)
samples/angular-stateChanged/src/app/core/customers.service.ts
CustomReduxDevtoolsRouteNavigator (Interface)
(no doc) [1 implementers]
modules/observable-store-extensions/interfaces.ts
Customer (Interface)
(no doc)
samples/angular-store-edits/src/app/core/model/customer.ts

Core symbols most depended-on inside this repo

deepClone
called by 51
modules/observable-store/utilities/cloner.service.ts
setState
called by 39
modules/observable-store/observable-store.ts
getState
called by 27
modules/observable-store/observable-store.ts
subscribe
called by 27
modules/observable-store-extensions/interfaces.ts
updateProp1
called by 24
modules/observable-store/tests/mocks.ts
updateUser
called by 20
modules/observable-store/tests/mocks.ts
unsubscribe
called by 18
modules/observable-store-extensions/interfaces.ts
destroy
called by 17
modules/observable-store/observable-store.ts

Shape

Method 223
Class 92
Interface 29
Function 20
Enum 8

Languages

TypeScript100%

Modules by API surface

modules/observable-store/utilities/cloner.service.spec.ts28 symbols
modules/observable-store/tests/mocks.ts25 symbols
modules/observable-store/observable-store.ts22 symbols
modules/observable-store-extensions/redux-devtools.extension.ts17 symbols
samples/angular-store-edits/src/app/customers/customers.service.ts12 symbols
modules/observable-store-extensions/interfaces.ts11 symbols
samples/angular-store-edits/src/app/customers/customers-edit/customers-edit.component.ts10 symbols
modules/observable-store/utilities/cloner.service.ts10 symbols
modules/observable-store/observable-store-base.ts10 symbols
samples/react-store/src/stores/CustomersStore.js9 symbols
samples/javascript-demo/src/customers-store.js9 symbols
samples/angular-store/src/app/customers/customers-list/customers-list.component.ts9 symbols

Used by 1 indexed graphs manifest dependencies, hub-wide

For agents

$ claude mcp add Observable-Store \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page