NgSpark
November 27, 2025 · View on GitHub
Modern, signal-based utilities for Angular - Embrace reactivity, immutability, and type safety
Overview
NgSpark is a collection of lightweight, production-ready libraries designed to enhance your Angular development experience with modern signal-based patterns. Built from the ground up for Angular 19+, these libraries embrace reactivity, type safety, and developer ergonomics.
Packages
@ng-spark/forms-x
Modern, signal-based form utilities that make form handling simpler, more reactive, and more maintainable.
npm install @ng-spark/forms-x
Features:
- SignalArray - Reactive array management with familiar mutable-style API
- FormTracker - Automatic dirty state tracking for forms
- SparkAsyncValidator - Debounced async validation with loading states
- DebounceFieldDirective - Two-way binding with configurable debounce
import { SignalArray, FormTracker, SparkAsyncValidator } from '@ng-spark/forms-x';
// Reactive array management
const todos = SignalArray.wrap(signal([{ id: 1, text: 'Learn Angular' }]));
todos.push({ id: 2, text: 'Build app' });
// Automatic dirty tracking
const formModel = signal({ name: 'John', email: 'john@example.com' });
const tracker = new FormTracker(formModel);
console.log(tracker.isDirty()); // false
// Async validation with debouncing
const emailValidator = new SparkAsyncValidator({
source: email,
validate: async (val) => {
const exists = await api.checkEmail(val);
return exists ? 'Email already registered' : null;
}
});
→ Full @ng-spark/forms-x Documentation
@ng-spark/signal-store-testing
Type-safe testing utilities for NgRx Signal Store. Test your Signal Stores with intuitive APIs for state management, computed signals, and method calls.
npm install @ng-spark/signal-store-testing --save-dev
Features:
- State Management - Read and manipulate Signal Store state with ease
- Computed Signals - Test computed signal values
- Method Calls - Invoke store methods in tests
- Async Waiting - Wait for state or computed signal conditions
- State History - Record and navigate state changes (time travel debugging)
- Type-Safe - Full TypeScript support with automatic type inference
import { createSignalStoreTester } from '@ng-spark/signal-store-testing';
const store = TestBed.inject(CounterStore);
const tester = createSignalStoreTester(store);
// Test state
expect(tester.state.count).toBe(0);
tester.patchState({ count: 5 });
tester.expectState({ count: 5 });
// Test computed signals
tester.expectComputed('doubleCount', 10);
// Call methods
tester.callMethod('increment');
expect(tester.state.count).toBe(6);
// Wait for async operations
await tester.waitForState({ loading: false });
// Time travel debugging
const history = tester.startRecording();
tester.patchState({ count: 10 });
history.goBack();
→ Full @ng-spark/signal-store-testing Documentation
Why NgSpark?
Signal-Native
Built from the ground up for Angular Signals, not retrofitted from older patterns. Every utility embraces Angular's modern reactive primitives.
Type-Safe
Full TypeScript support with excellent type inference. Catch errors at compile time, not runtime.
Lightweight
Zero external dependencies (except peer dependencies), tree-shakeable, and optimized for minimal bundle size impact.
Well-Tested
Comprehensive test coverage ensures reliability in production environments.
Performance Optimized
Designed for minimal re-renders and optimal performance in large-scale applications.
Developer Experience
Intuitive APIs that feel natural and reduce boilerplate while maintaining flexibility.
Requirements
- Angular: >= 19.0.0
- Node.js: >= 18.0.0
- TypeScript: >= 5.9.0
Additional Requirements by Package
@ng-spark/forms-x:
- RxJS >= 7.0.0
- @angular/forms >= 19.0.0
@ng-spark/signal-store-testing:
- @ngrx/signals >= 19.0.0
- Jest >= 29.0.0 OR Vitest >= 1.0.0
Installation
Install the packages you need:
# For form utilities
npm install @ng-spark/forms-x
# For Signal Store testing utilities (dev dependency)
npm install @ng-spark/signal-store-testing --save-dev
Quick Start
Forms-X Example
import { Component, signal } from '@angular/core';
import { FormTracker, SignalArray, SparkAsyncValidator } from '@ng-spark/forms-x';
@Component({
selector: 'app-user-form',
template: `
<form>
<input [debounceField]="username" />
@if (usernameValidator.error()) {
<span class="error">{{ usernameValidator.error() }}</span>
}
<button [disabled]="!tracker.isDirty()">Save Changes</button>
</form>
`
})
export class UserFormComponent {
formModel = signal({ name: '', email: '', tags: [] });
username = signal('');
// Track form dirty state
tracker = new FormTracker(this.formModel);
// Async validation with debouncing
usernameValidator = new SparkAsyncValidator({
source: this.username,
validate: async (val) => {
const exists = await this.checkUsername(val);
return exists ? 'Username taken' : null;
}
});
// Reactive array management
tags = SignalArray.from(this.formModel, 'tags');
addTag(tag: string) {
this.tags.push(tag);
}
}
Signal Store Testing Example
import { TestBed } from '@angular/core/testing';
import { createSignalStoreTester } from '@ng-spark/signal-store-testing';
import { CounterStore } from './counter.store';
describe('CounterStore', () => {
let tester: ReturnType<typeof createSignalStoreTester<InstanceType<typeof CounterStore>>>;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [CounterStore],
});
const store = TestBed.inject(CounterStore);
tester = createSignalStoreTester(store);
});
it('should manage counter state', async () => {
// Initial state
expect(tester.state.count).toBe(0);
tester.expectComputed('doubleCount', 0);
// Update state
tester.patchState({ count: 5 });
tester.expectState({ count: 5 });
tester.expectComputed('doubleCount', 10);
// Call methods
tester.callMethod('increment');
expect(tester.state.count).toBe(6);
// Wait for async operations
setTimeout(() => tester.callMethod('setCount', 100), 100);
await tester.waitForState({ count: 100 });
});
});
Documentation
Comprehensive documentation with examples, best practices, and API references:
Development
This repository uses Nx for monorepo management.
Getting Started
# Clone the repository
git clone https://github.com/khvedela/ng-spark.git
cd ng-spark
# Install dependencies
npm install
Common Commands
# Build all packages
npx nx run-many -t build
# Build specific package
npx nx build forms-x
npx nx build signal-store-testing
# Run tests
npx nx run-many -t test
# Test specific package
npx nx test forms-x
npx nx test signal-store-testing
# Lint
npx nx run-many -t lint
# Deploy documentation
npm run deploy:docs
# Visualize project graph
npx nx graph
Versioning and Releasing
To version and release the libraries:
# Dry run (preview changes)
npx nx release --dry-run
# Publish release
npx nx release
Contributing
Contributions are welcome! We appreciate your interest in making NgSpark better.
How to Contribute
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes and add tests
- Ensure tests pass (
npx nx run-many -t test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Guidelines
- Follow the existing code style and conventions
- Write tests for new features and bug fixes
- Update documentation as needed
- Keep commits atomic and write clear commit messages
- Ensure all tests pass before submitting PR
Reporting Issues
Found a bug or have a feature request? Please open an issue on GitHub:
License
MIT © NgSpark Team
See LICENSE for more information.
Acknowledgments
Built with:
- Angular - The modern web developer's platform
- Nx - Smart monorepos, fast CI
- TypeScript - JavaScript with syntax for types
- NgRx Signals - Reactive state management
Made with ❤️ by the NgSpark Team