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

ngx-digits-only

v1.1.1

Published

A dependency-free, standalone Angular directive for smart numeric inputs — digit filtering, formatting, and validation without pulling in a full masking library like `ngx-mask`.

Readme

ngx-digits-only

A dependency-free, standalone Angular directive for smart numeric inputs — digit filtering, formatting, and validation without pulling in a full masking library like ngx-mask.

What does it do?

Turns any <input> into a smart numeric field:

  • Blocks letters, symbols, and anything that isn't a digit — on every keystroke and paste
  • Formats the display with thousands separators, prefix, suffix, and pattern masks
  • Strips all display decoration before sending the value to your model
  • Validates min, max, maxLength, and pattern completeness automatically
  • Converts Arabic / Persian digits to Western digits silently

Installation

npm install ngx-digits-only

Quick start

This is a standalone directive — no module to import, just the directive itself.

import { Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { DigitsOnlyDirective } from 'ngx-digits-only';

@Component({
  standalone: true,
  imports: [
    ReactiveFormsModule,  // or FormsModule if you use ngModel
    DigitsOnlyDirective,
  ],
  template: `<input digitsOnly formControlName="quantity" />`,
})
export class ExampleComponent {}

In an NgModule-based app, standalone directives can be added directly to that module's imports array too:

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule,
    DigitsOnlyDirective,
  ],
  declarations: [MyComponent],
})
export class MyFeatureModule {}

Your first input

<input digitsOnly />

That's it. The field now only accepts digits 0–9. Nothing else gets through.

<input digitsOnly formControlName="quantity" />
<input digitsOnly [(ngModel)]="quantity" name="quantity" />

All inputs at a glance

| Input | Type | Default | What it does | |---|---|---|---| | decimalPlaces | number \| null | 0 | Decimal places: 0 = integers, N = exactly N, null = unlimited | | thousandSeparator | '' \| ',' \| '.' \| ' ' \| '_' | '' | Visual grouping character | | prefix | string | '' | Text shown before the number (e.g. $) | | suffix | string | '' | Text shown after the number (e.g. %) | | allowNegative | boolean | false | Allow a leading minus sign | | leadingZeros | boolean | false | Preserve leading zeros like 007 | | maxLength | number \| null | null | Max raw digit count | | min | number \| null | null | Minimum value (validation) | | max | number \| null | null | Maximum value (validation) | | outputType | 'number' \| 'string' | 'number' | Type emitted to the model | | pattern | NamedPattern \| string | '' | Display format mask | | convertEasternNumerals | boolean | true | Convert Arabic/Persian digits |

decimalPlaces

Controls how many digits are allowed after the decimal point.

<!-- 0 (default) — integers only, decimal point blocked -->
<input digitsOnly formControlName="quantity" />
<!-- user types: 1234   model: 1234 -->

<!-- N — exactly N decimal places -->
<input digitsOnly [decimalPlaces]="2" formControlName="price" />
<!-- user types: 99.99   model: 99.99 -->

<!-- null — unlimited decimal places, only single-dot rule enforced -->
<input digitsOnly [decimalPlaces]="null" formControlName="exchangeRate" />
<!-- user types: 1.23456789   model: 1.23456789 -->

A keystroke that would exceed the allowed decimal places is blocked outright. When null, any number of decimal digits is accepted but only one decimal point is allowed.

null follows the same "no limit" convention used by maxLength, min, and max.

thousandSeparator

Display-only formatting — the model always gets the plain number.

<input digitsOnly thousandSeparator="," formControlName="salary" />
<!-- display: 1,234,567   model: 1234567 -->

<input digitsOnly thousandSeparator="." [decimalPlaces]="2" formControlName="amount" />
<!-- display: 1.234.567,89   model: 1234567.89 -->
<!-- setting "." as the thousands separator auto-switches the decimal char to "," -->

Also accepts ' ' (space) and '_' (underscore).

prefix and suffix

<input digitsOnly prefix="$" formControlName="price" />
<!-- display: $1234   model: 1234 -->

<input digitsOnly prefix="€" suffix=" EUR" [decimalPlaces]="2" formControlName="amount" />
<!-- display: €1,234.56 EUR   model: 1234.56 -->

Both are stripped before the value reaches your model.

Angular Material note: The label correctly floats only when there is actual numeric content. An empty field always writes "" to the native input so MatInput's empty check passes correctly — even when prefix or suffix is set.

allowNegative

<input digitsOnly [allowNegative]="true" formControlName="temperature" />
<!-- user can type: -42   model: -42 -->

The minus sign is only valid as the first character, and works correctly even with prefix set.

leadingZeros

By default, leading zeros are stripped in number mode.

<input digitsOnly outputType="string" [leadingZeros]="true" [maxLength]="5" formControlName="zip" />
<!-- user types: 01234   model: '01234' (not 1234) -->

When pattern is set, leading zeros are always preserved automatically.

maxLength

<input digitsOnly [maxLength]="5" formControlName="pin" />

When pattern is set, maxLength is derived automatically from the number of 0 slots — any manual value is ignored.

min and max

Only apply in outputType="number" mode.

<input digitsOnly [min]="0" [max]="999" formControlName="score" #scoreCtrl="ngModel" />

<p *ngIf="scoreCtrl.errors?.['min']">Minimum value is {{ scoreCtrl.errors?.['min'].min }}</p>
<p *ngIf="scoreCtrl.errors?.['max']">Maximum value is {{ scoreCtrl.errors?.['max'].max }}</p>

Error payloads: { min: 0, actual: -5 } / { max: 999, actual: 1200 }.

outputType — the most important decision

outputType="number" (default) — model gets a JS number or null. Use for prices, quantities, anything you'll do arithmetic with.

outputType="string" — model gets a JS string or null. Use for card numbers, phone numbers, postal codes, SSNs — identifiers, not quantities.

Why it matters:

  • Leading zeros are lost in number mode: 01234 → 1234
  • 16-digit card numbers exceed Number.MAX_SAFE_INTEGER (9007199254740991) and get silently corrupted if stored as a number
// WRONG
cardNumber: number = 4111111111111111; // stored as 4111111111111112 — last digit wrong

// CORRECT
cardNumber: string = '4111111111111111'; // exact

pattern — auto formatting

<input digitsOnly pattern="0000 0000 0000 0000" formControlName="card" />
<!-- display: 4111 1111 1111 1111   model: '4111111111111111' -->

Setting pattern automatically:

| Effect | Why | |---|---| | Forces outputType="string" | Patterns are for identifiers, never arithmetic | | Derives maxLength from slot count | No need to set it separately | | Forces leadingZeros="true" | Every digit position is significant | | Disables thousandSeparator | Patterns have their own separators | | Disables decimalPlaces | Patterns are for integers/identifiers | | Disables allowNegative | Identifiers are never negative |

Named patterns

| Name | Slots | Example | |---|---|---| | card | 16 | 4111 1111 1111 1111 | | card-amex | 15 | 3782 822463 10005 | | expiry | 4 | 12/26 | | cvv | 3 | 123 | | cvv-amex | 4 | 1234 | | phone-us | 10 | (555) 867-5309 | | ssn | 9 | 123-45-6789 | | date | 8 | 01/01/1990 | | time | 4 | 09:30 | | sort-code | 6 | 20-00-00 | | bsb | 6 | 062-000 |

<input digitsOnly pattern="card" formControlName="cardNumber" />
<input digitsOnly pattern="expiry" formControlName="expiry" />
<input digitsOnly pattern="ssn" formControlName="ssn" />

Custom patterns

Any string not in the named list is used as a raw pattern — 0 for a digit slot, anything else as a literal separator.

<input digitsOnly pattern="000 000" formControlName="otp" />
<!-- display: 123 456   model: '123456' -->

<input digitsOnly pattern="0000-0000-0000" formControlName="account" />
<!-- display: 1234-5678-9012   model: '123456789012' -->

Separators are only inserted once more digits are coming, so you never get a trailing separator mid-type.

convertEasternNumerals

Arabic and Persian keyboards produce different Unicode digit characters:

| Script | Characters | |---|---| | Western | 0 1 2 3 4 5 6 7 8 9 | | Arabic-Indic | ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ | | Persian/Farsi | ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ |

By default (convertEasternNumerals="true"), typing, pasting, and programmatically-set values (e.g. control.setValue(...)) all convert Eastern digits transparently, with the Arabic decimal (٫) and thousands (٬) separators normalized automatically.

<input digitsOnly [convertEasternNumerals]="false" formControlName="code" />
<!-- Eastern digits now blocked — only ASCII 0-9 accepted -->

Conversion is unicode-range-safe — it can never accidentally transform a comma, dollar sign, or letter.

Validation errors

Implements Angular's Validator interface — errors land on control.errors automatically.

| Error key | When it fires | Payload | |---|---|---| | min | Value below [min] | { min, actual } | | max | Value above [max] | { max, actual } | | maxLength | Raw digit count exceeds [maxLength] | { maxLength, actual } | | patternIncomplete | Pattern mode, not all slots filled | { required, actual, pattern } |

<input digitsOnly pattern="card" formControlName="cardNumber" />
<p *ngIf="form.get('cardNumber')?.errors?.['patternIncomplete']">
  Enter all {{ form.get('cardNumber')?.errors?.['patternIncomplete']?.required }} digits
</p>

Directive errors merge with Angular's own validators (e.g. Validators.required) on the same control.errors object.

Reactive Forms example

this.form = this.fb.group({
  cardNumber: [null, Validators.required],
  expiry:     [null, Validators.required],
  cvv:        [null, Validators.required],
  amount:     [null, Validators.required],
});
<form [formGroup]="form" (ngSubmit)="pay()">
  <input digitsOnly pattern="card" formControlName="cardNumber" placeholder="4111 1111 1111 1111" />
  <input digitsOnly pattern="expiry" formControlName="expiry" placeholder="MM/YY" />
  <input digitsOnly pattern="cvv" formControlName="cvv" placeholder="123" />
  <input digitsOnly [decimalPlaces]="2" thousandSeparator="," prefix="$" [min]="0.01" formControlName="amount" />
  <button type="submit" [disabled]="form.invalid">Pay</button>
</form>
onSubmit() {
  console.log(this.form.value);
  // {
  //   cardNumber: '4111111111111111',  // string, no spaces
  //   expiry:     '1226',              // string, no slash
  //   cvv:        '123',
  //   amount:     49.99                // number
  // }
}

Type safety — NamedPattern

import { NamedPattern } from 'ngx-digits-only';

export class MyComponent {
  selectedPattern: NamedPattern = 'card'; // autocomplete + compile-time typo checking
  customPattern = '000-0000';              // custom strings still accepted
}
<input digitsOnly [pattern]="selectedPattern" formControlName="id" />

NamedPattern uses the (string & {}) trick internally so IDE autocomplete for named patterns survives alongside free-form custom pattern strings.

Standalone Eastern numeral utilities

Zero-dependency functions, usable outside Angular entirely (React, Vue, Node, plain TS):

import { convertEasternDigits, hasEasternDigits, getDigitScript, toWesternNumber } from 'ngx-digits-only';

convertEasternDigits('١٢٣');   // '123'
hasEasternDigits('١٢٣');       // true
getDigitScript('۴۵۶');         // 'persian'
toWesternNumber('١٢٣٫٤٥');    // 123.45

Common mistakes

Using number for a card number — exceeds Number.MAX_SAFE_INTEGER and corrupts digits. Use outputType="string" or, better, pattern="card" which handles it automatically.

Setting [maxLength] alongside pattern — ignored; pattern derives it from slot count.

Expecting min/max with outputType="string" — silently ignored in string mode; only works with outputType="number".

Expecting decimalPlaces to work with pattern — ignored when a pattern is set; patterns are always integer-only identifiers.

Forgetting FormsModule/ReactiveFormsModule — required alongside DigitsOnlyDirective for ngModel/formControlName to work.

FAQ

Can I combine prefix/suffix with pattern? Yes — they wrap the formatted pattern display; the model still gets the raw digits.

Can I pre-fill from the server? Yes, via fb.group() initial values or patchValue() — writeValue() formats the display automatically.

Why null instead of 0 for an empty field? By design, to distinguish "user typed zero" from "user left it blank."

Why is decimalPlaces typed as number | null now? null means unlimited decimal places — same convention as maxLength, min, and max. 0 still means integers only and is still the default, so existing code is unaffected.

Does it work with Angular Material? Yes — including correct label floating behavior. The directive writes "" to the native input when the field is empty, even when prefix or suffix is set, so MatInput detects the empty state correctly.

Does it work with Angular signal-based forms? Yes — it bridges directly to the host's NgControl (not via ControlValueAccessor), compatible with any Angular form API.

Can I use it without a form? Yes:

<input digitsOnly (input)="onInput($event.target.value)" />

Changelog

1.1.1

Changed

  • No longer implements ControlValueAccessor / registers via NG_VALUE_ACCESSOR. Angular allows only one custom value accessor per form-bound element, so the directive now bridges directly to the host's NgControl instead — this avoids the "more than one custom value accessor matches form control" error when another directive on the same host (e.g. MatAutocompleteTrigger via [matAutocomplete]) also registers one. [formControl] / [ngModel] bindings keep working exactly as before, no template changes needed.

Fixed

  • [convertEasternNumerals]="false" now also blocks conversion of values set programmatically (e.g. control.setValue(...)), not just typed/pasted input.

1.1.0

Added

  • decimalPlaces now accepts null for unlimited decimal places (single-dot rule still enforced). Existing 0 / N behavior is unchanged — no breaking change.

Fixed

  • Angular Material (MatInput) label was permanently floated when prefix or suffix was set on an empty field. The directive now writes "" to the native input when empty so Material's empty check passes correctly.

1.0.5

Added

  • RTL / LTR direction support — automatic via getComputedStyle, no extra input required
  • convertEasternNumerals input — Arabic-Indic and Persian digit conversion on keystroke and paste
  • pattern input with 11 named aliases and custom raw pattern support
  • outputType input — 'number' or 'string'
  • NamedPattern exported type — IDE autocomplete and compile-time safety
  • allowNegative now works correctly when prefix is set

Fixed

  • Eastern digits (Arabic/Persian) were blocked on keydown because SINGLE_DIGIT only matches ASCII — fixed with Unicode range check

1.0.0

Initial release.

Compatibility

Requires Angular >=16.0.0.

License

ISC