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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hermiforge-decorix/angular-signal

v0.4.0

Published

Angular Signal Forms adapter for Decorix metadata.

Downloads

1,011

Readme

@hermiforge-decorix/angular-signal

Decorix

Angular Signal Forms adapter for Decorix metadata. toSignalForm maps Decorix constraints onto Angular's own native validators and calls the real form() from @angular/forms/signals — it returns Angular's own FieldTree, not a Decorix-specific facade. Bind it with [formField], read form.field().errors()/.valid(), and submit with the real submit() function, exactly as the Angular Signal Forms docs describe.

Full usage guide: docs/ (narrative walkthrough beyond this package's API reference).

Install

pnpm add @hermiforge-decorix/core @hermiforge-decorix/angular-signal @angular/core @angular/forms

Peer dependencies: @angular/[email protected], @angular/[email protected].

Unlike other Decorix adapters, this one does not need a ValidatorAdapter/Zod: constraints are mapped directly onto Angular's native validators (or validate()/validateAsync() fallbacks) through Decorix's constraint registry, the same way @hermiforge-decorix/angular-reactive does.

Decorated Class

import {Email, Label, MinLength, Model, Required} from '@hermiforge-decorix/core';
import {toSignalForm} from '@hermiforge-decorix/angular-signal';
import {FormField, submit} from '@angular/forms/signals';

// A model field literally named `name` collides with `FieldTree`'s own
// `Function.prototype.name` at the type level — use another name (Angular's own docs
// avoid it the same way, e.g. `first`/`last` instead of `name`).
@Model('SignupDto')
class SignupDto {
  @Required('Full name is required')
  @MinLength(2, 'Name is too short')
  @Label('Name')
  fullName!: string;

  @Required('Email is required')
  @Email('Invalid email')
  email!: string;
}

@Component({
  imports: [FormField],
  template: `
    <input [formField]="signupForm.fullName" />
    <input [formField]="signupForm.email" />
    @if (signupForm.email().invalid()) {
      <span>{{ signupForm.email().errors()[0].message }}</span>
    }
  `
})
class SignupComponent {
  // `toSignalForm` must run in an Angular injection context — a component field
  // initializer (like here) always is one. `TModel` is inferred straight from
  // `SignupDto` — no separate type or explicit `toSignalForm<...>` needed.
  signupForm = toSignalForm(SignupDto, {
    initialValue: {fullName: 'Ada', email: '[email protected]'}
  });

  onSubmit() {
    submit(this.signupForm, {action: async () => console.log(this.signupForm().value())});
  }
}

Builder Model

import {model, stringField} from '@hermiforge-decorix/core';
import {toSignalForm} from '@hermiforge-decorix/angular-signal';

const SignupDto = model('SignupDto', {
  fullName: stringField().required('Full name is required').minLength(2, 'Name is too short').label('Name'),
  email: stringField().required('Email is required').email('Invalid email')
});

const signupForm = toSignalForm<{fullName: string; email: string}>(SignupDto, {
  initialValue: {fullName: 'Ada'}
});

Unlike the decorated-class example above, TModel isn't inferred here — a builder-declared model (model()) is metadata, not a TypeScript type, so there's no class reference for TypeScript to infer from; pass the shape explicitly when you need it typed.

Async Validation

Custom async constraints are wired through Angular's own validateAsync()/resource(). Since resource() needs an injection context too, pass options.injector when toSignalForm isn't called from a component/service field initializer (e.g. in a plain factory function):

const signupForm = toSignalForm(SignupDto, {initialValue, injector: inject(Injector)});

Constraint Mapping

Native Angular validators are used when available: required, minLength, maxLength, min, max, email, pattern. Every other constraint (custom, cross-field like EqualsField) runs through Angular's validate(); async constraints run through validateAsync()/resource().

Known scope: cross-field constraints are resolved at the top level of the model only (no nested object/array cross-field validation); disabled()/hidden() dynamic logic isn't generated (declare those yourself in the schema if needed).

License

LGPL-3.0-or-later — see the repository LICENSE.