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

@rytrox/form-signals

v0.5.0

Published

Simple Reimplementation of [Angular Forms](https://www.npmjs.com/package/@angular/forms) in Signals. Focused on simplicity, with full [Angular Material](https://www.npmjs.com/package/@angular/material) support.

Readme

Angular Form-Signals

Simple Reimplementation of Angular Forms in Signals. Focused on simplicity, with full Angular Material support.

Table of Contents

Installation

Install the library by using npm, pnpm or yarn:

npm install @rytrox/form-signals
pnpm install @rytrox/form-signals
yarn add @rytrox/form-signals

Compatibility List

| Angular Version | Library Version | |:---------------:|:---------------:| | ^19.2.0 | < 0.5.0 | | ^20.0.0 | ^0.5.0 |

Usage

FormControl

The popular FormControl in Angular is now a WritableSignal. It does not allow null if not explicitly specified in you declaration. You can create a FormControl by using the following function

import {formControl} from '@rytrox/form-signals';

@Component({
    selector: 'app-root',
    standalone: true,
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss'
})
export class AppComponent {
    
    // Creates a FormControl that allows null
    protected readonly stringControl = formControl<string | null>('Hello');
    
    // Creates a FormControl that only allows string, but with validators
    protected readonly controlWithValidators = formControl({
        value: 'World',
        disabled: false, // optional, default value is false
        validators: [
            (val) => val.length > 0 ? {required: 'This field is required!'} : null,
        ]
    });
}

ValidatorFn is now type-safe. You can validate against the current value of the form.

Binding your FormControl in Templates

By default, we provide Directives for any Material- and HTML-Component that is interactable. Those Directives are strictly typed. You can bind FormControl by using the [form]-Directive

<input type="text" [form]="form">
<!-- or. -->
<select [form]="form">
    ...
</select>

This table shows supported types on certain Components:

| Directive | Component | Supported FormControls | |-------------------------------|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------| | InputCheckboxDirective | <input type="checkbox" ...> | FormControl<boolean> | | InputDateDirective | <input type="date" ...>, also type="datetime-local" and type="time" | FormControl<Date> | | InputFileDirective | <input type="file" ...> | FormControl<File \| null>, FormControl<File[]> when input accepts multiple files | | InputNumberDirective | <input type="number" ...> | FormControl<number> | | InputRadioDirective | <input type="radio" value="..." ...> | FormControl<string> | | InputRangeDirective | <input type="range" ...> | FormControl<number> | | InputTextDirective | Fallback for any other input and <textarea ...> | FormControl<string> | | MatButtonToggleGroupDirective | MatButtonToggleGroup | FormControl<T> | | MatCheckboxDirective | MatCheckbox | FormControl<boolean> | | MatDatepickerDirective | <input [matDatepicker]="..." ...> | FormControl<D \| null> | | MatRadioGroupDirective | MatRadioGroup | FormControl<T> | | MatSelectDirective | MatSelect | FormControl<T \| null>, FormControl<T[]> on multiple select | | MatSlideToggleDirective | MatSlideToggle | FormControl<boolean> | | MatSliderRangeThumbDirective | MatSliderRangeThumb | FormControl<number> | | MatSliderThumbDirective | MatSliderThumb | FormControl<number> | | SelectDirective | <select ...> | FormControl<string> |

Usage of those Form-Directives are identical to Angular-FormControl / ngModel-Directives. For more information, have a look at the official Angular Material Documentation

Reacting to FormChanges

Since all Forms are now Signals, you can use effect, computed etc. to react to value changes inside Forms.

import {formControl} from '@rytrox/form-signals';

@Component({
    selector: 'app-root',
    standalone: true,
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss'
})
export class AppComponent {

    protected readonly form = formControl<string | null>('Hello');

    public constructor() {
        effect(() => {
            const val = form();
            
            // Interact with the value itself...
            form.set(`${val} World`);
        });
    }
}

Validators

Validators are now Functions that are bound to the FormControl's value. You can either create a FormControl with Validators or you can add them by using the addValidator or removeValidator methods.

Via FormControl#validators, you have access to a signal of all validators of a form. You can

FormGroup

FormGroup is a record of multiple forms that creates a computed value of multiple forms. You can declare a Builder-Function of a FormGroup by using the Factory-Function.

Let's assume, we have a Model like this:

interface Foo {
    name: string;
    id: number | null;
    date?: Date;
}

By using the formGroupFactory-Function, we create a Signal-Builder that creates our FormGroup:

export const FooGroup = formGroupFactory((val?: Foo) => {
    return {
        name: formControl(val?.name ?? ''),
        id: formControl(val?.id ?? null),
        date: formControl(val?.date)
    };
});

If a key is declared as optional, you can remove its control at any time by using setControl(null). However, we must declare them inside our factory.

Now, we can create a FormGroup like this:

@Component({
    selector: 'app-root',
    standalone: true,
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss'
})
export class AppComponent {

    protected readonly form: FormFactoryType<typeof FooGroup> = FooGroup();

    public constructor() {
        effect(() => {
            const val: Foo = form();
            
            // Interact with the value itself...
        });
    }
}

Access children of the group

You can access every field inside our FormGroup by using its key like this:

const form = FooGroup();

const nameControl = form.name;

Properties of FormGroups cannot be named like errors, validators, addValidators, removeValidators, setControl, hasValidator.

Error Handling

You can access errors of all child forms by using the errors Signal of the group. For example a typical form error looks like this:

{
    "this": {
        "validator1": "Group-Validator #1",
        "validator2": "Group-Validator #2"
    },
    "controls": {
        "name": {
            "required": "This field is required!"
        },
        "id": {
            "required": "This field is required!"
        }
    }
}

FormArray

A FormArray is an array of the same form. Like FormGroup it creates a computed array of multiple forms.

You can create a new FormArray-Build by using the formArrayFactory-Function like this:

import {formArrayFactory} from "./form-array";

const FooArray = formArrayFactory((foo: Foo) => FooGroup(foo));

After that, you can create an Instance by using the builder just like before:

@Component({
    selector: 'app-root',
    standalone: true,
    templateUrl: './app.component.html',
    styleUrl: './app.component.scss'
})
export class AppComponent {

    protected readonly form: FormFactoryType<typeof FooArray> = FooArray([]);

    public constructor() {
        effect(() => {
            const val: Foo[] = form();

            // Interact with the value itself...
        });
    }
}

In contrast to Angular's FormArray, this one will not throw errors if you set a new Array with a different length. Instead, it will automatically create new Forms for each value by using the factory function you declared earlier.

Iterating over the array

You can iterate though the FormArray like any other array by using for-of loop:

const form = FooArray([]);
for (const control of form) {
    
}

Accessing the child

You can access the child by using its index:

const form = FooArray([]);
const control: FormGroup | undefined = form[0];

It will return undefined, if the index is not set.

Error-Handling

Similar to FormGroup, you can access every error of each child form by using the error-Signal. The Error will look like this:

{
  "this": {
    "validator1": "Array Validator #1"
  },
  "controls": [
    {
      "this": {
        "validator1": "Group Validator #1"
      },
      "controls": {
        "name": {
          "required": "This field is required"
        },
        "id": {
          "required": "This field is required"
        }
      }
    },
    null,
    {
      "this": null,
      "controls": {
        "name": {
          "required": "This field is required"
        }
      }
    }
  ]
}

Contributing

Please feel free to contribute to this project
by creating issues or pull requests on our mirror project

License

Copyright (c) 2025 Team Rytrox
Licensed under the MIT license