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

ngrx-fsm

v1.1.0

Published

**NgRx-based finite state machine for Angular component UI state.**

Readme

ngrx-fsm

NgRx-based finite state machine for Angular component UI state.

Register allowed action → state transitions per component. As NgRx actions flow through the store, the machine validates them against that map:

  • Valid → update the component’s state in a dedicated store slice, then optionally forward the action
  • Invalid → drop the action (and emit a blocked-transition signal for telemetry)

This scales better than boolean flags (isLoading, isOpen, canSubmit, …) once components grow multiple UI modes.

Why

At a small scale, booleans are fine. As flows grow (idle → processing → success/retry, expandable panels, parallel cards), booleans become combinatorial and easy to misuse.

ngrx-fsm keeps a single current state per component and a transition table so illegal actions never reach effects/reducers.

Example:

| Current state | Action | Allowed? | Next state | | ------------- | ------- | -------- | ----------- | | Idle | Init | yes | Processing | | Processing | Init | no | (blocked) | | Processing | Success | yes | Completed | | Completed | ReIndex | yes | Processing |

Concepts

| Piece | Role | | ----------------------- | -------------------------------------------------- | | ComponentStateBuilder | Fluent API to declare transitions | | ComponentStateService | Registers / unregisters machines; can set state | | ComponentStateMachine | Custom ActionsSubject that intercepts actions | | componentStateReducer | Store slice keyed by component name | | ComponentStateFacade | Selectors helpers (e.g. processingComponentName) | | ComponentStateEnum | Built-in state labels |

Built-in states

enum ComponentStateEnum {
  Idle = 'idle',
  Processing = 'processing',
  Completed = 'completed',
  Retry = 'retry',
  Maximised = 'maximised',
  Minimised = 'minimised',
  Success = 'success',
  Disabled = 'disabled',
}

You can use these as-is or treat them as conventions for your app.

Transition modes

After a valid transition updates store state:

| Mode | Builder method | Behavior | | --------------- | ------------------ | --------------------------------------------------- | | passthrough | .passThrough() | Forward the original action to reducers/effects | | terminate | .terminate() | Update UI state only; do not forward the action | | transform | .transformTo(fn) | Dispatch a different action instead |

Invalid transitions emit componentStateTransitionBlocked (no store state change).

Installation

yarn add ngrx-fsm @ngrx/store
# or
npm install ngrx-fsm @ngrx/store

Peer dependencies (see package.json for exact ranges): @angular/core, @angular/common, @ngrx/store.

Setup

Replace NgRx’s ActionsSubject with ComponentStateMachine and register the feature reducer:

import { bootstrapApplication } from '@angular/platform-browser';
import { ActionsSubject, provideStore } from '@ngrx/store';
import {
  COMPONENT_STATE_FEATURE_KEY,
  ComponentStateBuilder,
  ComponentStateFacade,
  ComponentStateMachine,
  componentStateReducer,
  ComponentStateService,
} from 'ngrx-fsm';

bootstrapApplication(AppComponent, {
  providers: [
    provideStore({
      [COMPONENT_STATE_FEATURE_KEY]: componentStateReducer,
    }),
    ComponentStateFacade,
    ComponentStateBuilder,
    ComponentStateService,
    { provide: ActionsSubject, useClass: ComponentStateMachine },
  ],
});

The machine must be provided as ActionsSubject so it can intercept actions before they reach the store.

Declare transitions

import {
  ComponentStateBuilder,
  ComponentStateEnum,
  ComponentStateService,
} from 'ngrx-fsm';
import * as UsersActions from './users.actions';

const componentName = 'UsersComponent';

const componentStates = this.componentStateBuilder
  .create(componentName)
  .forAction(UsersActions.initUsers.type)
  .fromState(ComponentStateEnum.Idle)
  .toState(ComponentStateEnum.Processing)
  .passThrough()
  .forAction(UsersActions.loadUsersSuccess.type)
  .fromState(ComponentStateEnum.Processing)
  .toState(ComponentStateEnum.Completed)
  .passThrough()
  .forAction(UsersActions.loadUsersFailure.type)
  .fromState(ComponentStateEnum.Processing)
  .toState(ComponentStateEnum.Idle)
  .passThrough()
  .forAction(UsersActions.reIndexUsers.type)
  .fromState(ComponentStateEnum.Completed)
  .toState(ComponentStateEnum.Processing)
  .passThrough()
  .build();

this.componentStateService.addComponentStates(componentStates);

Unregister when the component is destroyed:

ngOnDestroy(): void {
  this.componentStateService.removeComponentStates(componentName);
}

Builder API

create(name: string)
withId(id: string | number)           // multi-instance machines
disableWhenProcessing()               // advisory flag for UI
showProgressBar(show: boolean)        // advisory flag for UI
forAction(actionType: string)
fromState(state: ComponentStateEnum)
toState(state: ComponentStateEnum)
passThrough() | terminate() | transformTo(fn)
build()

Each fromState entry must end with passThrough(), terminate(), or transformTo(...).

Multiple fromStates for the same action — call fromState again without calling forAction again (calling forAction resets that action’s map):

.forAction(lockPanel.type)
.fromState(ComponentStateEnum.Maximised)
.toState(ComponentStateEnum.Disabled)
.terminate()
.fromState(ComponentStateEnum.Minimised)
.toState(ComponentStateEnum.Disabled)
.terminate()

Initial state

Default current state is Idle when unset. For UI that starts elsewhere (e.g. minimised panel), set state after registration — addComponentStates() clears any prior store entry for that name:

this.componentStateService.addComponentStates(states);
this.componentStateService.updateComponentState(
  componentName,
  ComponentStateEnum.Minimised
);

Multi-instance machines (withId)

When several instances share action types (e.g. three cards), give each a unique machine name and an id:

this.componentStateBuilder
  .create(`UserCard-${id}`)
  .withId(id)
  .forAction(loadCard.type)
  .fromState(ComponentStateEnum.Idle)
  .toState(ComponentStateEnum.Processing)
  .passThrough()
  // ...
  .build();

Dispatch actions with a matching componentStateId:

store.dispatch(loadCard({ componentStateId: id }));

Only the machine whose withId matches will transition.

Observe UI state

// Via facade
this.facade.processingComponentName('UsersComponent'); // Observable<boolean>
this.facade.componentState$; // full slice

// Or select the slice directly
this.store.select((s) => s.componentState?.['UsersComponent']);

Use that to drive progress bars, disabled buttons, expanded panels, etc.

Telemetry hooks

Successful transitions dispatch updateComponentState with metadata:

  • previousState, componentState
  • triggeredBy (action type)
  • mode: passthrough | terminate | transform | …
  • componentStateId (when applicable)

Blocked transitions dispatch componentStateTransitionBlocked (state unchanged).

Listen with @ngrx/effects if you want logging, analytics, or a live debug panel (see the showcase app).

Demo

This repo includes a showcase app with interactive use cases and a live FSM telemetry panel:

| Route | Demonstrates | | -------- | ---------------------------------------------- | | /users | Idle → Processing → Completed | | /cards | Parallel machines + withId | | /form | Retry + disable while processing | | /panel | Maximised / Minimised / Disabled + terminate |

yarn
yarn start
# → http://localhost:4200

How the machine works (summary)

dispatch(action)
       │
       ▼
ComponentStateMachine (ActionsSubject)
       │
       ├─ action not registered → forward as usual
       │
       └─ registered → for each interested component:
              │
              ├─ current state allows transition + id matches
              │     → updateComponentState(...)
              │     → passThrough | terminate | transform
              │
              └─ otherwise
                    → componentStateTransitionBlocked(...)
                    → original action not forwarded

API exports

// Feature
COMPONENT_STATE_FEATURE_KEY;
componentStateReducer;

// Actions
updateComponentState;
componentStateTransitionBlocked;
passthroughComponentState;
deleteComponentState;

// Runtime
ComponentStateBuilder;
ComponentStateService;
ComponentStateMachine;
ComponentStateFacade;
ComponentStateEnum;

License

See the repository root for license information.