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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ng-spark/forms-x

v1.0.0

Published

Modern, signal-based form utilities for Angular including reactive array management, dirty state tracking, async validation, and debounced field binding

Downloads

91

Readme

@ng-spark/forms-x

Modern, signal-based form utilities for Angular that embrace reactivity and immutability

npm version License: MIT

📦 What's Inside?

Forms-X is a collection of lightweight, signal-based utilities designed to make form handling in Angular simpler, more reactive, and more maintainable:

🚀 Installation

npm install @ng-spark/forms-x

📖 Quick Start

import { Component, signal } from '@angular/core';
import {
  FormTracker,
  SignalArray,
  SparkAsyncValidator,
  DebounceFieldDirective
} from '@ng-spark/forms-x';

@Component({
  selector: 'app-user-form',
  imports: [DebounceFieldDirective],
  template: `
    <form>
      <input [debounceField]="username" />
      @if (usernameValidator.error()) {
        <span>{{ usernameValidator.error() }}</span>
      }
      <button [disabled]="!tracker.isDirty()">Save</button>
    </form>
  `
})
export class UserFormComponent {
  formModel = signal({ name: '', 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');
}

🔧 Features

SignalArray

Reactive array management with familiar push, remove, and move operations:

const todos = SignalArray.wrap(signal([
  { id: 1, text: 'Learn Angular' }
]));

// Familiar API, automatic reactivity
todos.push({ id: 2, text: 'Build app' });
todos.removeAt(0);
todos.move(0, 1);

// Use in templates
@for (todo of todos.value(); track todo.id) {
  <div>{{ todo.text }}</div>
}

→ Full SignalArray Documentation

FormTracker

Automatic dirty state tracking with baseline comparison:

const formModel = signal({ name: 'John', email: '[email protected]' });
const tracker = new FormTracker(formModel);

// Reactive dirty state
console.log(tracker.isDirty()); // false

formModel.update(f => ({ ...f, name: 'Jane' }));
console.log(tracker.isDirty()); // true

// Get only changed fields
console.log(tracker.dirtyFields()); // { name: 'Jane' }

// After save
await api.save(formModel());
tracker.commit(); // Reset baseline

→ Full FormTracker Documentation

SparkAsyncValidator

Debounced async validation with built-in loading states:

const email = signal('');

const emailValidator = new SparkAsyncValidator({
  source: email,
  validate: async (val) => {
    const exists = await api.checkEmail(val);
    return exists ? 'Email already registered' : null;
  },
  debounce: 400
});

// In template
@if (emailValidator.isLoading()) {
  <span>Checking...</span>
}
@if (emailValidator.error()) {
  <span>{{ emailValidator.error() }}</span>
}

→ Full SparkAsyncValidator Documentation

DebounceField Directive

Two-way binding with configurable debounce:

@Component({
  template: `
    <input
      [debounceField]="searchQuery"
      [debounceTime]="300"
      [transform]="trimAndLowercase"
    />
  `
})
export class SearchComponent {
  searchQuery = signal('');

  trimAndLowercase = (val: string) => val.trim().toLowerCase();
}

→ Full DebounceFieldDirective Documentation

📚 Documentation

Comprehensive documentation with examples and best practices is available at:

https://khvedela.github.io/ng-spark/docs/forms-x

🎯 Requirements

  • Angular >= 19.0.0
  • RxJS >= 7.0.0
  • Node >= 18.0.0

💡 Why Forms-X?

Signal-Native - Built from the ground up for Angular Signals

🎯 Type-Safe - Full TypeScript support with inference

🪶 Lightweight - Zero dependencies, tree-shakeable

🧪 Well-Tested - Comprehensive test coverage

Performance - Optimized for minimal re-renders

🤝 Contributing

Contributions are welcome! Please check out our Contributing Guide.

📄 License

MIT © NgSpark Team

🔗 Links


Made with ❤️ by the NgSpark Team