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

@touchspin/angular

v5.2.0

Published

Angular adapter for TouchSpin with per-renderer subpaths

Readme

@touchspin/angular

Angular adapter for TouchSpin providing native Angular components with per-renderer support.

Features

  • ControlValueAccessor implementation for seamless Angular forms integration
  • Reactive and template-driven forms support
  • Per-renderer subpath imports (Bootstrap 3/4/5, Tailwind, Vanilla)
  • Standalone components (Angular 14+)
  • SSR/Angular Universal compatible
  • Full keyboard navigation (ArrowUp/Down, PageUp/Down, Home/End)
  • ARIA attributes for accessibility (role="spinbutton", aria-valuenow, etc.)
  • Controlled/uncontrolled patterns (value vs defaultValue)
  • Imperative API via component reference

Installation

npm install @touchspin/angular @touchspin/core @touchspin/renderer-bootstrap5

Note: Install the renderer package that matches your UI framework.

Setup

TouchSpin Angular ships fully AOT-compiled components. No runtime compiler imports or schema overrides are required—just install the package, import the renderer you need, and include its CSS in your global styles or component stylesheet.

Compatibility

The adapter is built and tested in Angular CLI projects targeting Angular 17, 18, 19, and 20. We maintain standalone example applications for each version in the touchspin-angular-examples repository. When consuming the package in your own workspace, make sure to import the renderer stylesheet (for example @touchspin/renderer-vanilla/css) in your global styles or component stylesheet.

Quick Start

Import the Component

import { Component } from '@angular/core';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TouchSpinBootstrap5Component],
  template: `
    <touch-spin
      [(ngModel)]="quantity"
      [min]="0"
      [max]="100"
      [step]="1"
    ></touch-spin>
  `
})
export class AppComponent {
  quantity = 10;
}

Template-Driven Forms

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [FormsModule, TouchSpinBootstrap5Component],
  template: `
    <form>
      <label for="quantity">Quantity:</label>
      <touch-spin
        id="quantity"
        name="quantity"
        [(ngModel)]="quantity"
        [min]="1"
        [max]="100"
        [step]="1"
        (valueChange)="onQuantityChange($event)"
      ></touch-spin>
      <p>Selected: {{ quantity }}</p>
    </form>
  `
})
export class ExampleComponent {
  quantity = 10;

  onQuantityChange(value: number) {
    console.log('New quantity:', value);
  }
}

Reactive Forms

import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [ReactiveFormsModule, TouchSpinBootstrap5Component],
  template: `
    <form>
      <label for="price">Price:</label>
      <touch-spin
        id="price"
        [formControl]="priceControl"
        [min]="0"
        [step]="0.01"
        [decimals]="2"
        prefix="$"
      ></touch-spin>
      <p>Selected: {{ priceControl.value | currency }}</p>
    </form>
  `
})
export class ExampleComponent {
  priceControl = new FormControl(9.99);
}

Available Renderers

Import from per-renderer subpaths:

// Bootstrap 3
import { TouchSpinBootstrap3Component } from '@touchspin/angular/bootstrap3';

// Bootstrap 4
import { TouchSpinBootstrap4Component } from '@touchspin/angular/bootstrap4';

// Bootstrap 5
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

// Tailwind CSS
import { TouchSpinTailwindComponent } from '@touchspin/angular/tailwind';

// Vanilla CSS
import { TouchSpinVanillaComponent } from '@touchspin/angular/vanilla';

API Reference

Inputs

| Input | Type | Default | Description | |-------|------|---------|-------------| | value | number \| null | - | Controlled value (use with valueChange output) | | defaultValue | number \| null | - | Initial value (uncontrolled mode) | | min | number | - | Minimum allowed value | | max | number | - | Maximum allowed value | | step | number | 1 | Increment/decrement amount | | decimals | number | 0 | Number of decimal places | | prefix | string | - | Text to prepend to the value | | suffix | string | - | Text to append to the value | | disabled | boolean | false | Disable the input | | readOnly | boolean | false | Make the input read-only | | name | string | - | Form control name | | id | string | - | Element ID | | class | string | - | CSS class for wrapper element | | inputClass | string | - | CSS class for input element | | ariaLabel | string | - | ARIA label for accessibility | | ariaLabelledBy | string | - | ARIA labelledby for accessibility | | coreOptions | Partial<TouchSpinCoreOptions> | - | Advanced core options |

Outputs

| Output | Type | Description | |--------|------|-------------| | valueChange | EventEmitter<number> | Emitted when value changes | | change | EventEmitter<{value: number, meta: TouchSpinChangeMeta}> | Detailed change event with metadata | | blurred | EventEmitter<void> | Emitted when input loses focus | | focused | EventEmitter<void> | Emitted when input gains focus |

Imperative Methods

Access via component reference:

import { Component, ViewChild } from '@angular/core';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-example',
  template: `
    <touch-spin #spinner [min]="0" [max]="100"></touch-spin>
    <button (click)="spinner.increment()">+</button>
    <button (click)="spinner.decrement()">-</button>
  `
})
export class ExampleComponent {
  @ViewChild('spinner') spinner!: TouchSpinBootstrap5Component;

  ngAfterViewInit() {
    this.spinner.setValue(50);
    console.log(this.spinner.getValue()); // 50
  }
}

| Method | Description | |--------|-------------| | focus(): void | Focus the input element | | blur(): void | Blur the input element | | increment(): void | Increment value by step | | decrement(): void | Decrement value by step | | getValue(): number | Get current value | | setValue(value: number): void | Set value programmatically | | getCore(): TouchSpinCorePublicAPI \| null | Access core API instance |

Keyboard Support

| Key | Action | |-----|--------| | ArrowUp | Increment by step | | ArrowDown | Decrement by step | | PageUp | Increment by step * 10 | | PageDown | Decrement by step * 10 | | Home | Jump to min value | | End | Jump to max value |

Accessibility

The component includes proper ARIA attributes for screen readers:

  • role="spinbutton" - Identifies the widget type
  • aria-valuenow - Current value
  • aria-valuemin - Minimum value
  • aria-valuemax - Maximum value
  • aria-label / aria-labelledby - Accessible label
<label id="price-label">Price</label>
<touch-spin
  [ariaLabelledBy]="'price-label'"
  [min]="0"
  [max]="1000"
></touch-spin>

SSR / Angular Universal

The component is fully compatible with Angular Universal (SSR). Browser-specific code is only executed when isPlatformBrowser returns true.

import { Component } from '@angular/core';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [TouchSpinBootstrap5Component],
  template: `<touch-spin [min]="0" [max]="100"></touch-spin>`
})
export class ExampleComponent {
  // Component works seamlessly in SSR
}

Controlled vs Uncontrolled

Uncontrolled (defaultValue)

<!-- Value managed internally by component -->
<touch-spin
  [defaultValue]="10"
  (valueChange)="onValueChange($event)"
></touch-spin>

Controlled (value + valueChange)

<!-- Value managed by parent component -->
<touch-spin
  [value]="quantity"
  (valueChange)="quantity = $event"
></touch-spin>

With ngModel (Recommended)

<!-- Angular forms integration -->
<touch-spin [(ngModel)]="quantity"></touch-spin>

Advanced Examples

With Validators

import { Component } from '@angular/core';
import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
import { TouchSpinBootstrap5Component } from '@touchspin/angular/bootstrap5';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [ReactiveFormsModule, TouchSpinBootstrap5Component],
  template: `
    <touch-spin
      [formControl]="quantityControl"
      [min]="1"
      [max]="100"
    ></touch-spin>
    <div *ngIf="quantityControl.invalid && quantityControl.touched">
      Please select a quantity between 1 and 100
    </div>
  `
})
export class ExampleComponent {
  quantityControl = new FormControl(10, [
    Validators.required,
    Validators.min(1),
    Validators.max(100)
  ]);
}

With Prefix/Suffix

<!-- Currency -->
<touch-spin
  [value]="price"
  [prefix]="'$'"
  [decimals]="2"
  [step]="0.01"
></touch-spin>

<!-- Percentage -->
<touch-spin
  [value]="percentage"
  [suffix]="'%'"
  [min]="0"
  [max]="100"
></touch-spin>

License

MIT © TouchSpin Contributors