npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@angular-libs/store

v0.2.2

Published

An OOP-based Angular Signal state management solution. A lightweight alternative to stores like NgRx SignalStore. Featuring built-in entity management, native resource API integration (resource adapter), undo/redo history adapters, and cross-tab synchroni

Readme

Reactive state management library powered by Angular Signals.

Stackblitz demo

Features

  • Store & Storage: In-memory ALStore and persistent ALStorage (localStorage/sessionStorage/custom).
  • Cross-Tab Sync: Out-of-the-box state synchronization across tabs via BroadcastChannel or native storage events.
  • Adapters:
    • EntityAdapter: Native CRUD for collections.
    • ResourceAdapter / RxResourceAdapter: Async/HTTP state tracking.
    • HistoryAdapter: Undo/redo time-travel.
  • Derived State: Compute derived state easily with .select().

Quick Start

import { Component, inject, Injectable } from '@angular/core';
import { ALStore, createEntityAdapter } from '@angular-libs/store';

interface User {
  id: number;
  name: string;
  isActive: boolean;
}

interface AppState {
  users: User[];
  filter: 'all' | 'active';
}

const initialState: AppState = { users: [], filter: 'all' };

@Injectable({ providedIn: 'root' })
export class UserStore extends ALStore<AppState> {
  // Bind an EntityAdapter for CRUD array operations
  users = createEntityAdapter(this.storeRef, 'users', { idField: 'id' });

  // Computed derived state
  filteredUsers = this.select((state) =>
    state.filter === 'active' ? state.users.filter((u) => u.isActive) : state.users,
  );

  constructor() {
    // Pass { syncChannel: '...' } to enable cross-tab sync!
    super(initialState, { syncChannel: 'user_store_sync' });
  }
}

@Component({
  standalone: true,
  template: `
    <button (click)="store.set('filter', 'all')">Show All</button>
    <button (click)="store.set('filter', 'active')">Show Active</button>
    <button (click)="store.users.upsertOne({ id: Date.now(), name: 'Alice', isActive: true })">
      Add Active User
    </button>

    <!-- Iterate over the reactive derived state: -->
    @for (user of store.filteredUsers(); track user.id) {
      <div>{{ user.name }} ({{ user.isActive ? 'Active' : 'Inactive' }})</div>
    }
  `,
})
export class AppComponent {
  store = inject(UserStore);
}

API Overview

ALStore / ALStorage Core Methods

  • getSignal(key): Retrieves a reactive readonly Angular Signal for a specific property.
  • get(key): Retrieves the current synchronous snapshot value without reactivity.
  • set(key, val): Updates a value directly and triggers reactive updates.
  • update(key, fn): Safely mutates a value using a callback based on the previous state.
  • patchState(stateOrUpdater): Modifies multiple state object properties simultaneously.
  • select(projector): Creates a reactive, memoized derived state Signal using a proxy-based callback.
  • snapshot(): Retrieves a synchronous snapshot of the entire current state object.
  • reset(key?): Resets a specific key (or the entire store if omitted) back to its initialState.

Adapters

  • createEntityAdapter: Simplifies array management with built-in CRUD operations (all(), addOne(entity), addMany(entities), upsertOne(entity), upsertMany(entities), updateOne(update), removeOne(id), removeMany(ids), setAll(entities), removeAll()).
  • createResourceAdapter: Manages Promise-based fetching with automatic cancellation, exposing value(), isLoading(), and reload() signals.
  • createRxResourceAdapter: Seamlessly bridges RxJS Observables into your synchronous state (via @angular-libs/store/rxjs-interop).
  • createHistoryAdapter: Adds instant time-travel capabilities (undo(), redo(), canUndo(), canRedo()) to any property in your store.

🤖 AI / GitHub Copilot Instructions

Want to generate your stores automatically? Copy the prompt below and paste it into GitHub Copilot, or save it to your project's AI rules file (such as .cursorrules, .windsurfrules, or .github/copilot-instructions.md) to teach your AI assistant how to use this library across your entire project.

# State Management AI Rules

When creating or interacting with state management in this project, ALWAYS use `@angular-libs/store`.
Do NOT use NgRx, Akita, or plain BehaviorSubjects for stores.

## 1. Creating a Store

- **Base Class**: Extend `ALStore<AppState>` (in-memory) or `ALStorage<AppState>` (persistent).
- **Setup**: Define an interface `AppState` and a strongly-typed `initialState` object. Call `super(initialState)` in the `@Injectable({ providedIn: 'root' })` constructor.
- **Cross-Tab Sync**: Pass `{ syncChannel: 'channel_name' }` as the second argument to `super()` to enable native cross-tab synchronization.

## 2. Adapters & APIs

- **EntityAdapter (Arrays/CRUD)**: `myEntities = createEntityAdapter(this.storeRef, 'key', { idField: 'id' })`
  - _Methods_: `addOne(entity)`, `upsertOne(entity)`, `removeOne(id)`, `updateOne({ id, changes })`, `setAll(entities)`, `removeAll()`
  - _State_: `this.store.myEntities.all()`, `this.store.myEntities.total()`
- **ResourceAdapter (Promises/Fetch)**: `myRes = createResourceAdapter(this.storeRef, 'key', { params: () => ({ id: this.getSignal('id')() }), loader: async ({ params, abortSignal }) => fetch(...) })`
  - _State_: `this.store.myRes.value()`, `this.store.myRes.isLoading()`, `this.store.myRes.reload()`
- **RxResourceAdapter (RxJS/Observables)**: Must import from `@angular-libs/store/rxjs-interop`.
  - _Usage_: `myRx = createRxResourceAdapter(this.storeRef, 'key', { params: () => ..., loader: ({ params }) => this.http.get(...) })`
- **HistoryAdapter (Undo/Redo)**: `myHistory = createHistoryAdapter(this.storeRef, 'key', { limit: 10 })`
  - _Methods_: `undo()`, `redo()`, `canUndo()`, `canRedo()`

## 3. Consuming & Mutating in Components

- Access primitive state natively as Signals: `myVal = this.store.getSignal('key')`.
- Access derived state (selectors): `activeTodos = this.store.select(state => state.todos.filter(t => t.done))`.
- **Mutations**: Call `this.store.set('key', value)`, `this.store.update('key', state => newState)`, or `this.store.patchState({ ... })` directly.
- **Immutability Requirement**: Use spread syntax or array methods that return new references.

## 4. Reference Example

```typescript
import { Component, inject, Injectable } from '@angular/core';
import {
  ALStore,
  createEntityAdapter,
  createHistoryAdapter,
  createResourceAdapter,
} from '@angular-libs/store';
import { createRxResourceAdapter } from '@angular-libs/store/rxjs-interop';

interface Todo {
  id: number;
  title: string;
  done: boolean;
}
interface AppState {
  currentUserId: number;
  todos: Todo[];
  filter: 'all' | 'pending' | 'completed';
}

const initialState: AppState = { currentUserId: 1, todos: [], filter: 'all' };

@Injectable({ providedIn: 'root' })
export class TodoStore extends ALStore<AppState> {
  // 1. Entity Adapter for array CRUD
  todos = createEntityAdapter(this.storeRef, 'todos', { idField: 'id' });

  // 2. History Adapter for instant Undo/Redo tracking
  todoHistory = createHistoryAdapter(this.storeRef, 'todos', { limit: 20 });

  // 3. Resource Adapter for Async/Fetch integration (syncs directly into the 'todos' array!)
  todoResource = createResourceAdapter(this.storeRef, 'todos', {
    params: () => ({ userId: this.getSignal('currentUserId')() }),
    loader: async ({ params, abortSignal }) => {
      const res = await fetch(`/api/todos?userId=${params.userId}`, { signal: abortSignal });
      return res.json();
    },
  });

  // 4. Reactive Selectors
  derivedTodos = this.select((state) => {
    if (state.filter === 'pending') return state.todos.filter((t) => !t.done);
    if (state.filter === 'completed') return state.todos.filter((t) => t.done);
    return state.todos;
  });

  stats = this.select((state) => {
    const total = state.todos.length;
    const pending = state.todos.filter((t) => !t.done).length;
    return { total, pending, completed: total - pending };
  });

  constructor() {
    // Optional: Pass `{ syncChannel: 'todos_sync' }` here to synchronize state across tabs automatically!
    super(initialState);
  }

  // 5. Custom Actions (Mutations)
  addTodo(title: string) {
    this.todos.upsertOne({ id: Date.now(), title, done: false });
  }

  toggleFilter() {
    this.update('filter', (current) => (current === 'all' ? 'pending' : 'all'));
  }
}
```

</details>

## License

MIT