Modern, signal-based utilities for Angular - Embrace reactivity, immutability, and type safety
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.
Modern, signal-based form utilities that make form handling simpler, more reactive, and more maintainable.
npm install @ng-spark/forms-xFeatures:
- 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
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-devFeatures:
- 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
Built from the ground up for Angular Signals, not retrofitted from older patterns. Every utility embraces Angular's modern reactive primitives.
Full TypeScript support with excellent type inference. Catch errors at compile time, not runtime.
Zero external dependencies (except peer dependencies), tree-shakeable, and optimized for minimal bundle size impact.
Comprehensive test coverage ensures reliability in production environments.
Designed for minimal re-renders and optimal performance in large-scale applications.
Intuitive APIs that feel natural and reduce boilerplate while maintaining flexibility.
- Angular: >= 19.0.0
- Node.js: >= 18.0.0
- TypeScript: >= 5.9.0
@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
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-devimport { 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);
}
}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 });
});
});Comprehensive documentation with examples, best practices, and API references:
This repository uses Nx for monorepo management.
# Clone the repository
git clone https://github.com/khvedela/ng-spark.git
cd ng-spark
# Install dependencies
npm install# 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 graphTo version and release the libraries:
# Dry run (preview changes)
npx nx release --dry-run
# Publish release
npx nx releaseContributions are welcome! We appreciate your interest in making NgSpark better.
- 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
- 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
Found a bug or have a feature request? Please open an issue on GitHub:
MIT © NgSpark Team
See LICENSE for more information.
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