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

ngfe

v20.0.0

Published

[![npm version](https://badge.fury.io/js/ngfe.svg)](https://www.npmjs.com/package/ngfe) ![CI](https://github.com/navix/ngfe/actions/workflows/ci.yml/badge.svg)

Readme

npm version CI

ngfe | Angular Forms Engine | Template-based Signal Forms

Boosted template-driven Angular forms.

It is an alternative for the Angular Forms of any kind: simpler, more flexible, more powerful, no restrictions.

If your project have complex and dynamic forms this package will save you a lot of time and lines of code.

StackBlitz showcase

Features

  • Focused on template-driven approach.
  • Signal under the hood.
  • Less abstractions, ultimate control.
  • More freedom for developers.
  • Nothing exceptionally new for Angular people.
  • Less boilerplate to write:
    • Simple custom value accessors creation.
    • Simple custom validators creation.
    • Single interface for sync and async validators.
    • No ControlContainer providing for sub-forms.
    • No required name binding.
    • Handy way to display validation errors only on touched fields.
  • Function validators binding.
  • Built-in debounce.
  • Two-way state binding in templates (e.g [(touched)]).
  • Almost all states have reactive alternative (e.g .errors+.errors$).
  • Submit directive which touches all fields and checks validity.
  • Stricter types in controls.
  • SSR support.
  • Zero deps.
  • Reduced bundle size without @angular/forms (~20KB parsed size in prod mode).
  • Does not conflict with the Angular FormsModule.
  • Optional integration with Angular Validator and ValueAccessor interfaces.
  • Works with Angular Material.

Caveats

  • 3rd party lib.
  • Not battle-tested enough yet.
  • Sometimes too much freedom for developers.

Why template forms

  • Angular template is the best DSL for describing forms.
  • Single source of truth for your forms - templates.
  • No structure and binding duplication.
  • Almost all logic written in a declarative manner.
  • Less code to write.
  • https://www.youtube.com/watch?v=L7rGogdfe2Q

Terms

  • Form - tool for displaying and manipulating data in Browser.
  • Model - variable that represents a field of data.
  • Input - HTML element (or custom component) allows you to display and change some state.
  • Control - a bridge between Model and Input.
  • Value accessor - directive or component that connects Input to the Control.
  • Validator - function to check Model or Input values to meet some conditions.
  • Error - returned by Validator if value is invalid.
  • Validity - represents current validation state:
    • pending - one or more async Validators are running,
    • invalid - one or more Validators returned errors,
    • valid - all Validators returned no errors.
  • Touched - Input had interaction with user (was focused for built-in Value accessors).
  • Dirty - Input was changed by user.

Installation

$ npm i ngfe
  • ngfe@13 for Angular@12 and Angular@13. RxJS@7 needed.
  • ngfe@15 no-signals version with [feControl] syntax for Angular@14+.

Usage

Import the module:

import { FeModule } from 'ngfe';
...
imports: [
  FeModule,
  ...
]

All directives are standalone and can be imported separately:

imports: [FeForm, FeModel, FeSubmit, FeInput, FeSelect, FeRequiredValidator, ...]

feImports

A convenience constant containing all directives for quick standalone component setup:

import { feImports } from 'ngfe';

@Component({
  standalone: true,
  imports: [feImports],
  ...
})
export class MyComponent {}

Binding

On the surface [(model)] works exactly like [(ngModel)].

<input [(model)]="field">

Form

FeForm is automatically applied to <form> elements (selector: form:not([noForm]),[feForm]).

It aggregates all child FeModel controls and provides form-level state.

Use the noForm attribute to opt out on a specific <form> element:

<form noForm>
  <!-- No FeForm directive here -->
</form>

Use the [feForm] attribute to create a form group on a non-form element:

<div feForm>
  <input [(model)]="field">
</div>

Example

<form #form="form" [(disabled)]="formDisabled">
  <input [(model)]="name" name="name" required>
  <input [(model)]="email" name="email" email>

  @if (form.invalid()) {
    <p>Form has errors</p>
  }

  <button (validSubmit)="save()">Submit</button>
</form>

Model (FeModel)

FeModel is the core control directive.

Selector: [model]:not([noModel]),[modelChange]:not([noModel]).

Use the noModel attribute to opt out:

<input [model]="value" noModel>

invalidValueStrategy

Controls what happens when input validation fails:

  • 'accept' (default) - update model with the input value even if invalid.
  • 'retain' - keep the previous valid value, do not update model.
  • {value: VALUE} - update model with the provided fallback value.

asyncValidatorsStrategy

Controls when async validators run:

  • 'runAfterSyncValid' (default) - async validators only run if all sync validators pass.
  • 'runAlways' - async validators always run regardless of sync validation results.

Built-in value accessors

Input (FeInput)

Selector: input[model],textarea[model].

Bridges native <input> and <textarea> elements to FeModel.

<input [(model)]="field">
<input [(model)]="field2" type="checkbox">
<input [(model)]="field3" type="radio" value="1">
<input [(model)]="field4" type="date">
<textarea [(model)]="field5"></textarea>

valueType

Force a specific value type regardless of the input element type:

<!-- Force number parsing for a text input -->
<input [(model)]="amount" valueType="number">

<!-- Force Date object from a date input -->
<input [(model)]="date" type="date" valueType="Date">

<!-- Keep string value for a number input -->
<input [(model)]="code" type="number" valueType="string">

updateOn

Control when the model value is updated:

<!-- Default: update on every keystroke -->
<input [(model)]="field" updateOn="change">

<!-- Update only when input loses focus -->
<input [(model)]="field" updateOn="blur">

File inputs

<input (modelChange)="loadFiles($event)" type="file">
import { readFiles } from 'ngfe';
...
loadFiles(files?: FileList) {
  readFiles(files || []).subscribe(loadedFiles => {
    ...
  });
}

Select (FeSelect)

Selector: select[model]. Bridges native <select> elements to FeModel.

<select [(model)]="field">
  <option value="1">ONE</option>
  <option value="2">TWO</option>
</select>

Any type of value available to bind to option[value]:

field: number;
<select [(model)]="field">
  <option [value]="1">ONE</option>
  <option [value]="2">TWO</option>
</select>

Multiple select

<select [(model)]="selectedItems" multiple>
  @for (let item of items) {
    <option [value]="item">{{ item.name }}</option>
  }
</select>

Custom compare function

Useful when option values are objects:

<select [(model)]="selected" [compareFn]="compareById">
  @for (let item of items) {
    <option [value]="item">{{ item.name }}</option>
  }
</select>
compareById = (v1: any, v2: any) => v1?.id === v2?.id;

FeSelectOption

Selector: option.

Automatically connects to the parent FeSelect directive.

Debounce

Define debounce time for values from a value accessor:

<input [(model)]="field" [debounce]="400">

Validation

Works very similar to the default Angular validation.

<input #model="model" [(model)]="field" required>
@if (model.errors(); as errors) {
  @if (errors.required) {
    <span>Required</span>
  }
}

Visible Errors

.visibleErrors() returns the errors object only when the control is touched:

<input #model="model" [(model)]="field" required>
@if (model.visibleErrors(); as errors) {
  @if (errors.required) {
    <span>Required</span>
  }
}

Built-in validators

| Validator | Selector | Key Inputs | Error Key | |-----------|----------|------------|-----------| | FeRequiredValidator | [model][required] | required: boolean (default: true) | {required: {value}} | | FeEmailValidator | [model][email] | email: boolean (default: true) | {email: {value}} | | FeEqualValidator | [model][equal] | equal: any, activeWhenEmpty: boolean | {equal: {equal, value}} | | FeNotEqualValidator | [model][notEqual] | notEqual: any, activeWhenEmpty: boolean | {notEqual: {notEqual, value}} | | FeIsNumberValidator | [model][isNumber] | isNumber: boolean (default: true) | {isNumber: {value}} | | FeLengthValidator | [model][minLength],[model][maxLength] | minLength, maxLength | {minLength: {requiredLength, actualLength, value}}, {maxLength: ...} | | FeMinmaxValidator | [model][min],[model][max] | min, max | {min: {min, value, numberValue}}, {max: ...} | | FePatternValidator | [model][pattern] | pattern: string \| RegExp | {pattern: {pattern, value}} |

All boolean-toggle validators (required, email, isNumber) can be disabled by binding false:

<input [(model)]="field" [required]="isRequired">
<input [(model)]="field" [email]="shouldValidateEmail">

The equal and notEqual validators have an activeWhenEmpty input (default: false). When false, validation is skipped if the value is empty:

<input [(model)]="field" [equal]="expectedValue" activeWhenEmpty>

Custom validator

As a function

Use FeValidator interface to implement a validator. Return errors object FeValidationErrors or undefined if value is valid.

// Invalid if value is not empty and have value "BOOM".
notBoom: FeValidator<string> = value => {
  return value !== 'BOOM'
    ? undefined
    : {notBoom: true};
};

Pass it to [validators] input:

<input #model="model" [(model)]="field" [validators]="[notBoom]">
@if (model.errors()?.notBoom) {
  <span>Value should not be "BOOM"</span>
}

As a directive

Or, create a validator directive:

@Directive({
  selector: '[model][notBoom]',
  standalone: true,
})
export class NotBoomValidatorDirective {
  private model = inject(FeModel<string>);
  private removeFn = this.model.addValidator(value => {
    return value !== 'BOOM'
      ? undefined
      : {notBoom: true};
  });
}
<input [(model)]="field" notBoom>

Async validators

Return from a validation function Observable or Promise with FeValidatorResult:

asyncValidator: FeValidator<string> = (value, control) => {
  return new Observable<FeValidatorResult>(observer => {
    // Async check...
    observer.next(isValid ? undefined : {asyncError: true});
    observer.complete();
  });
};

Forced errors

You can programmatically set errors on a control using forcedErrors:

<input #model="model" [(model)]="field" [forcedErrors]="serverErrors()">
// Set from server response
serverErrors = signal<FeValidationErrors | undefined>(undefined);

onSubmit() {
  this.api.save(this.field).subscribe({
    error: (err) => {
      this.serverErrors.set({serverError: err.message});
    }
  });
}

Set forcedErrors to 'pending' to force pending validity state.

Submit

Two directives that mark all form controls as touched and check validity on submit.

FeSubmit (on button or form)

Selector: button[anySubmit],button[validSubmit],button[invalidSubmit]

<form>
  ...
  <button (anySubmit)="doStuff()">Submit</button>
  <button (validSubmit)="doValidStuff()">Submit</button>
  <button (invalidSubmit)="doInvalidStuff()">Submit</button>
</form>

Selector: form[anySubmit],form[validSubmit],form[invalidSubmit]

<form (anySubmit)="doStuff()" (validSubmit)="doValidStuff()" (invalidSubmit)="doInvalidStuff()">
  ...
</form>

| Output | Type | Description | |--------|------|-------------| | anySubmit | boolean | Emits validity (true/false) on click. | | validSubmit | void | Emits on click when form is valid. | | invalidSubmit | void | Emits on click when form is invalid. |

Both directives call form.touchAll() before emitting, so all validation errors become visible.

Custom Value Accessor

You do not need to implement ValueAccessor interface.

Just inject FeModel and use its properties and methods:

@Component({
  selector: 'app-custom-control',
  ...
})
export class AppCustomControlComponent {
  private model = inject(FeModel);

  onUserAction(value: any) {
    this.model.input(value);
  }

  onFocus() {
    this.model.touch();
  }
}
<app-custom-control [(model)]="field" />

You can use any signal or subscribe to any observable of the model and define any state.

Util

Set of functions useful for working with forms.

ensureNumber

Convert a string value to number if possible.

Returns undefined for empty string or non-numeric values.

import { ensureNumber } from 'ngfe';

ensureNumber('42');        // 42
ensureNumber('abc');       // undefined
ensureNumber('');          // undefined
ensureNumber(undefined);   // undefined

readFiles

Read file data from File[] or FileList (typically from file inputs).

import { readFiles } from 'ngfe';

readFiles(fileList, 'DataURL').subscribe((loadedFiles: FeLoadedFile[]) => {
  loadedFiles.forEach(f => {
    console.log(f.file.name, f.data);
  });
});

@angular/forms adapter

Enables an easy transition from Angular forms to ngfe.

Install package:

$ npm i ngfe-ng-adapter

Import module:

imports: [
  ...
  FeModule,
  FeNgAdapterModule,
]

After that you can use Angular ValueAccessors and Validators with [(model)].

Also, with this package, FeModel provides NgControl and allows you to use ngfe with Material components or other UI libs.

LICENSE

MIT

TODO

  • Fix tests
  • Bind state classes for control/form
  • Any validator should be possible to switch off
  • Programmatic validation using Component: validate(SomeFormComponent, {formState}): Observable<FeValidationResult>
  • Clean up logs