Angular Undo Redo History Store | ng-hub-ui-history

Angular undo and redo history store with Signals-friendly APIs, transactions, limits and reactive form support.

Last updated Sep 28, 2026

Overview

Why teams search for this library

Use this Angular history store when you need undo and redo on complex state without introducing a full external state library.

Install

npm install ng-hub-ui-history

Jump to

Ideal for

  • editors
  • form builders
  • low-code tools
  • configuration UIs

About history

ng-hub-ui-history is especially useful in Angular editors, form builders and configuration screens where the user expects to go backwards safely. It brings undo and redo patterns to stateful workflows without forcing a heavyweight architecture decision.

Feature guides

Manual Commits and Time Travel

Track state changes and travel backwards/forwards through linear history.

Examples:
Basic History

Manual commit, undo and redo over a single tracked object.

{
  "title": "Getting Started with History",
  "description": "Edit and commit to create undo/redo entries."
}

Nested Objects and Arrays

Use patch-based tracking for deep object graphs and list mutations.

Examples:
Nested Object History

Nested properties and list updates recorded as diff patches.

{
  "id": "invoice-1",
  "customer": {
    "name": "Acme Corp",
    "city": "Madrid"
  },
  "lines": [
    {
      "name": "Support plan",
      "quantity": 1,
      "unitPrice": 150
    }
  ]
}

Automatic Form Tracking

Auto-commit form value changes with built-in watch helpers.

Examples:
Reactive Form Watch

watchForm() committing every FormGroup value change on its own.

{
  "name": "Maria",
  "email": "maria@demo.local",
  "role": "Editor"
}

Transactions and Retention

Group multiple operations into one entry and trim old entries by configured limits.

Examples:
Transactions + Limits

Commits collapsed by a transaction, under entry and byte limits.

Pointer: -1 / Entries: 0 / Bytes: 0

{
  "id": "transaction-editor",
  "title": "Release notes draft",
  "content": "Initial draft content.",
  "tags": [
    "draft"
  ]
}

Key features

Recent changes

Version 22.0.8 - 9/28/26, 12:00 AM

changed: The family list in both READMEs names `ng-hub-ui-spreadsheet`. The sheet joined the family and the other lists had not caught up. Documentation only: no code, types or styles change.

Version 22.0.7 - 9/23/26, 12:00 AM

changed: The Angular peer range now says what the code needs. It asked for >=18.0.0, which nothing in this package justified: the newest Angular API the source uses is signals, which shipped in 16.0, and the compiled output carries no linker marker above it. The range is >=16.0.0, and npm run peers:floors derives and checks that floor from now on.

Version 22.0.6 - 9/20/26, 12:00 AM

changed: The npm keywords name the problem the package solves — undo-redo, time-travel, state-management, store, transactions, diff, patch, reactive-forms — which is what somebody looking for it would type. Metadata only: no code, types or styles change.

Version 22.0.5 - 9/16/26, 12:00 AM

changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.

Version 22.0.4 - 9/6/26, 12:00 AM

fixed: The published manifest no longer declares a main entry. It pointed at src/public-api.ts, the ng-packagr entry file, which never travels inside the tarball, so any resolver that ignores the exports map (older bundlers, legacy Jest resolution, plain require) followed it straight to a file that is not there. Dropping the field leaves exports, module and typings as the only entry points, the shape ng-packagr emits for every other library in the monorepo.

fixed: A commit that only moves a Date (or a Set, Map or RegExp) is recorded again. The default diff walked every object key by key, and those types keep their payload outside their own enumerable keys, so the comparison found nothing and produced an empty patch. An empty patch reads as no change: commit() returned false, the tracked state kept the old value that getState() and states() then handed back, and on a mixed commit undo() restored the other fields while leaving the new date in place, all without an error. Those values are now compared and replaced as a whole, which is what structuredClone already round-trips; an instance rebuilt with the same content still records no entry.

Version 22.0.3 - 9/1/26, 12:00 AM

changed: The homepage in the manifest points at this library's own documentation page rather than at the site root. It is the link a registry shows beside the package and the one a reader clicks from it, and landing on a front page they then have to search is a worse answer than landing on the reference for the package they were already looking at. Metadata only — no code, no types, no styles change, and nothing a consumer imports is affected.

Version 22.0.2 - 8/8/26, 12:00 AM

fixed: Documentation links now point at the canonical localized URLs. The README linked to https://hubui.dev/<path> with no locale prefix and no trailing slash, and both forms are 301-redirected, so every reader arriving from npm or GitHub landed on a redirect instead of the canonical page.

Version 22.0.1 - 7/28/26, 12:00 AM

added: Test suite for the diff/patch/clone utilities and multi-object undo/redo: cloneDeep isolation (nested structures, Dates), createDefaultDiff/applyDefaultPatch round-trips across representative shapes (nested objects, array insert/remove/reorder, key deletion, null transitions, deep nesting), estimateBytes sanity, and store-level coverage for independent per-object timelines, no-op transactions and redo invalidation after transactional commits. No runtime changes.

Version 22.0.0 - 6/17/26, 12:00 AM

changed: Aligned with Angular 22.

changed: README documentation standardized.

Version 0.1.0 - 6/17/26, 12:00 AM

added: Initial release with multi-object linear history.

added: Undo/redo, transactions, and maxEntries/maxBytes retention limits.

added: Reactive Forms auto-commit integration via watchForm().

Frequently asked questions

How do I add undo and redo to an Angular application?

createHistoryStore() gives you a store. Call registerObject(id, initialState) once, commit(id, nextState, { label }) on every change, and then undo(id) and redo(id), with canUndo(id) and canRedo(id) driving the buttons. states is a signal holding the current value of everything tracked, so a template reads it straight. There is no component and no directive in this package: that factory and its types are the whole surface.

Can it track a reactive form?

watchForm(id, form) subscribes to valueChanges and commits each change for you, and it returns the function that stops it — it does not clean up by itself, so keep that callback and call it when the component goes away. skipInitial ignores the first emission. It commits form.value rather than getRawValue(), so a disabled control is not in the snapshot and an undo will not restore it. beginTransaction and endTransaction fold a burst of edits into one undo step.

How much history does it keep?

maxEntries defaults to 100 and maxBytes to 512000, counted per tracked object, and the oldest entries fall off first; history(id) returns the metadata and clearHistory(id) starts over. The timeline is linear and per object: one step at a time, no branching, no undo across several objects at once, and nothing is written to storage, so a reload starts empty.

Does it bind Ctrl+Z for me?

No. The package has no DOM at all — no keyboard shortcut, no toolbar, no ARIA — so you bind Ctrl+Z to undo(id) yourself. Two more things surprise people. The store is a plain factory, not an injectable service, so sharing one between components means providing it yourself. And calling commit or undo with an id you never registered throws rather than returning false, which is deliberate: a typo in an id would otherwise fail silently.