Files

@isa/utils/positive-integer-input

An Angular directive that ensures only positive integers can be entered into number input fields through keyboard blocking, paste sanitization, and input validation.

Overview

The PositiveIntegerInputDirective provides three layers of protection to prevent invalid input in number fields:

  1. Keyboard input blocking - Prevents invalid characters from being typed
  2. Paste sanitization - Cleans pasted content to extract only positive integers
  3. General input validation - Catches any other input methods (drag & drop, autofill, voice input, etc.)

This directive is particularly useful for scenarios where you need to enforce strictly positive integer values, such as:

  • Quantity inputs in shopping carts
  • Age or count fields
  • Point or score inputs
  • Any numeric field that should not accept decimals, negatives, or special characters

Key Behaviors

Blocked characters: ., ,, -, +, e, E, and all other non-digit characters

Important notes:

  • This directive removes ALL non-digit characters, so 1.58 becomes 158, not 1 or 2
  • Leading zeros are automatically removed (007 becomes 7)
  • A single 0 input results in an empty field
  • Works seamlessly with Angular forms (ngModel, formControl, formControlName)

Installation

Import the directive in your component:

import { PositiveIntegerInputDirective } from '@isa/utils/positive-integer-input';

@Component({
  selector: 'app-my-component',
  standalone: true,
  imports: [PositiveIntegerInputDirective],
  // ...
})
export class MyComponent {}

API Reference

PositiveIntegerInputDirective

Selector: input[type="number"][positiveIntegerInput]

Standalone: Yes

A standalone Angular directive that attaches to input[type="number"] elements and enforces positive integer input.

Host Listeners

Event Handler Description
keydown onKeyDown(event) Blocks invalid characters (., ,, -, +, e, E) before they can be entered
paste onPaste(event) Intercepts paste events, sanitizes clipboard content, and inserts only positive integers
input onInput(event) Catch-all for other input methods (drag & drop, autofill, voice input, IME)

Private Methods

sanitizeInput(value: string): string

Sanitizes a string to contain only positive integers by:

  1. Removing all non-digit characters using regex \D
  2. Removing leading zeros to prevent parsing issues

Usage Examples

Basic Usage

<input type="number" positiveIntegerInput />

With ngModel (Two-way Binding)

<input
  type="number"
  positiveIntegerInput
  [(ngModel)]="quantity"
  placeholder="Enter quantity"
/>
export class MyComponent {
  quantity: number | undefined;
}

With Reactive Forms (FormControl)

<input
  type="number"
  positiveIntegerInput
  [formControl]="pointsControl"
  placeholder="Enter points"
/>
import { FormControl } from '@angular/forms';

export class MyComponent {
  pointsControl = new FormControl<number | null>(null);
}

With Reactive Forms (FormGroup)

<form [formGroup]="orderForm">
  <input
    type="number"
    positiveIntegerInput
    formControlName="quantity"
    placeholder="Enter quantity"
  />
</form>
import { FormBuilder, FormGroup } from '@angular/forms';

export class MyComponent {
  orderForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.orderForm = this.fb.group({
      quantity: [null]
    });
  }
}

Complete Example with Validation

<form [formGroup]="productForm">
  <label for="quantity">Quantity:</label>
  <input
    id="quantity"
    type="number"
    positiveIntegerInput
    formControlName="quantity"
    placeholder="Enter quantity"
    [class.error]="quantity.invalid && quantity.touched"
  />

  @if (quantity.invalid && quantity.touched) {
    <span class="error-message">
      Quantity is required and must be a positive integer
    </span>
  }
</form>
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { PositiveIntegerInputDirective } from '@isa/utils/positive-integer-input';

@Component({
  selector: 'app-product-form',
  standalone: true,
  imports: [ReactiveFormsModule, PositiveIntegerInputDirective],
  templateUrl: './product-form.component.html',
})
export class ProductFormComponent {
  productForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.productForm = this.fb.group({
      quantity: [null, [Validators.required, Validators.min(1)]]
    });
  }

  get quantity() {
    return this.productForm.get('quantity')!;
  }

  onSubmit() {
    if (this.productForm.valid) {
      console.log('Quantity:', this.quantity.value);
    }
  }
}

Behavior Examples

Keyboard Input

User Action Result Reason
Types 123 123 appears Valid digits allowed
Types 1-2-3 Only 123 appears Minus signs blocked
Types 1.5 Only 15 appears Decimal point blocked
Types 1,000 Only 1000 appears Comma blocked
Types +42 Only 42 appears Plus sign blocked
Types 1e5 Only 15 appears Letter e blocked

Paste Operations

Clipboard Content Result Reason
-100 100 Negative sign removed
1.000 1000 Decimal point removed
1,58 158 Comma removed (NOT rounded to 1 or 2)
abc123xyz 123 Non-digit characters removed
007 7 Leading zeros removed
+42 42 Plus sign removed
3.14e2 3142 All non-digits removed
0 Empty field Single zero becomes empty string

Other Input Methods

The directive also sanitizes input from:

  • Drag and drop - Dropping text from other sources
  • Browser autofill - Autocomplete values
  • Voice input - Speech-to-text input
  • IME input - Input Method Editors (Asian languages)
  • Right-click paste - Context menu paste (some browsers)

Browser Compatibility

Works with all modern browsers that support Angular 20+ and native input[type="number"] elements.

Testing

The directive includes comprehensive test coverage with Vitest, including:

  • Keyboard input blocking tests
  • Paste sanitization tests
  • General input validation tests
  • Angular forms integration tests
  • Edge case handling

Run tests:

nx test utils-positive-integer-input