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

ng-hub-ui-sortable

v21.3.0

Published

Angular bindings for Sortable.js (ng-hub-ui fork of @worktile/ngx-sortablejs)

Downloads

353

Readme

ng-hub-ui-sortable

npm version

Part of ng-hub-ui Family

This component is part of the ng-hub-ui ecosystem, which includes:

Description

ng-hub-ui-sortable proporciona una integración completa y moderna de SortableJS para Angular, permitiéndote crear interfaces interactivas con funcionalidad de arrastrar y soltar de forma sencilla y declarativa. Con una API basada en directivas, puedes transformar cualquier lista en una experiencia sortable con solo agregar un atributo a tu HTML.

La biblioteca soporta escenarios simples como reordenar items en una lista, hasta casos avanzados como listas anidadas, transferencia de elementos entre múltiples listas, clonación de items, integración con Angular Reactive Forms (FormArray) y Angular Signals (WritableSignal), y personalización completa mediante opciones y eventos. Cada operación de drag-and-drop se sincroniza automáticamente con tu modelo de datos, manteniendo tu aplicación reactiva y predecible.

Este paquete es un fork de @worktile/ngx-sortablejs, manteniendo la misma API robusta mientras actualiza el branding y metadatos para alinearse con la familia ng-hub-ui.

Features

  • Directive-based - Simple directive API for adding sortable functionality to any container
  • Array binding - Automatically syncs drag-and-drop operations with your data array
  • Manual control mode - Opt-in manual array management for full control (similar to Angular CDK)
  • Signal support - Native integration with Angular writable signals for reactive state management
  • FormArray support - Native integration with Angular Reactive Forms FormArray
  • Full SortableJS API - Access to all SortableJS options and events
  • Zone integration - Events are properly proxied into Angular's zone for predictable change detection
  • Clone mode - Support for cloning items with custom clone functions
  • Multi-list support - Drag items between multiple connected lists
  • TypeScript support - Full type safety with proper typings

Installation

# Install the component and SortableJS
npm install ng-hub-ui-sortable sortablejs

# Install types for development
npm install -D @types/sortablejs

Or using yarn:

yarn add ng-hub-ui-sortable
yarn add -D @types/sortablejs

Peer Requirements

  • Angular >=18.0.0
  • SortableJS >=1.7.0

Quick Start

Here's a quick example to get you started with ng-hub-ui-sortable.

1. Import the module

import { SortableModule } from "ng-hub-ui-sortable";

@NgModule({
  imports: [SortableModule.forRoot({ animation: 150 })],
})
export class AppModule {}

2. Create your component

import { Component } from "@angular/core";

@Component({
  selector: "app-sortable-demo",
  templateUrl: "./sortable-demo.component.html",
})
export class SortableDemoComponent {
  items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"];
}

3. Use in your template

<div [hubUISortable]="items" [options]="{ animation: 150 }">
  @for (item of items; track item) {
  <div class="sortable-item">{{ item }}</div>
  }
</div>

Usage

The component can be used in two ways:

1. Module Import with Global Options (Recommended)

import { NgModule } from "@angular/core";
import { SortableModule } from "ng-hub-ui-sortable";

@NgModule({
  imports: [SortableModule.forRoot({ animation: 150 })],
})
export class AppModule {}

2. Standalone Component Import

import { Component } from "@angular/core";
import { SortableModule } from "ng-hub-ui-sortable";

@Component({
  selector: "app-sortable-list",
  standalone: true,
  imports: [SortableModule],
  template: `
    <div [hubUISortable]="items" [options]="{ animation: 150 }">
      @for (item of items; track item) {
        <div class="sortable-item">{{ item }}</div>
      }
    </div>
  `,
})
export class SortableListComponent {
  items = ["Item 1", "Item 2", "Item 3"];
}

Directive API

Primary Inputs

| Input | Type | Description | | ----------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | items | any[], FormArray, or WritableSignal<any[]> | Model binding that stays in sync with drag-and-drop operations (used with alias [hubSortable]) | | container | string | Optional CSS selector for the real sortable container when the host is wrapped by another component | | options | Options | Native SortableJS options object. Provide a new object reference to trigger option updates | | cloneFunction | (item: any) => any | Custom clone function for clone mode. Allows you to customize how items are cloned | | autoUpdateArray | boolean | Controls automatic array updates. When true (default), arrays update automatically. When false, you have full control (similar to Angular CDK) |

SortableJS Option Inputs

All SortableJS options can be passed either through the [options] input or as individual inputs:

| Input | Type | Description | | ----------------------- | -------------------- | --------------------------------------------------------------------------------- | | group | string \| object | Group name or options for dragging between lists | | sort | boolean | Enable/disable sorting within the list | | delay | number | Time in milliseconds to define when sorting should start | | disabled | boolean | Disable the sortable if set to true | | draggable | string | CSS selector for draggable items within the container | | handle | string | CSS selector for drag handle within list items | | animation | number | Animation speed in milliseconds when sorting | | ghostClass | string | CSS class applied to the ghost element during drag | | chosenClass | string | CSS class applied to the chosen element | | dragClass | string | CSS class applied to the dragging element | | fallbackOnBody | boolean | Append ghost element to document body | | fallbackTolerance | number | Number of pixels a point should move before triggering drag | | fallbackClass | string | CSS class applied when using forceFallback | | fallbackOffset | object | Fallback offset configuration | | forceFallback | boolean | Force the fallback to activate | | filter | string \| function | CSS selector or function to filter items that should not be draggable | | preventOnFilter | boolean | Call preventDefault on filter event | | direction | string | Direction of Sortable ('vertical' or 'horizontal', auto-detected if not provided) | | swapThreshold | number | Threshold of swap zone (0-1) | | invertSwap | boolean | Inverts swap threshold direction | | invertedSwapThreshold | number | Threshold when swapping direction is inverted | | removeCloneOnHide | boolean | Remove clone element when not showing | | ignore | string | CSS selector for elements to ignore | | touchStartThreshold | number | Number of pixels a point should move before cancelling a delayed drag event | | emptyInsertThreshold | number | Distance mouse must be from empty sortable to insert drag element into it | | dropBubble | boolean | Enable drop bubble | | dragoverBubble | boolean | Enable dragover bubble | | dataIdAttr | string | HTML attribute that defines the data id | | delayOnTouchOnly | boolean | Only delay on touch devices | | easing | string | Easing for animation (e.g., 'cubic-bezier(1, 0, 0, 1)') | | setData | function | Function to set data for dragover/drop events | | store | object | Store module for saving and restoring the sort order |

Outputs

All outputs emit events that are proxied through Angular's zone for proper change detection:

| Output | Type | Description | | ------------- | ---------------------------------------------------------- | ------------------------------------------------------------- | | init | EventEmitter<Sortable> | Emits the instantiated Sortable instance on initialization | | start | EventEmitter<SortableEvent> | Fired when dragging starts | | end | EventEmitter<SortableEvent> | Fired when dragging ends | | add | EventEmitter<SortableEvent> | Element is added from another list | | remove | EventEmitter<SortableEvent> | Element is removed to another list | | update | EventEmitter<SortableEvent> | Element position is updated within the same list | | sortEvent | EventEmitter<SortableEvent> | Called when the list is sorted (any change in order) | | filterEvent | EventEmitter<SortableEvent> | Called when an attempt is made to drag a filtered element | | change | EventEmitter<SortableEvent> | Called when list changes by adding or removing an item | | choose | EventEmitter<SortableEvent> | Element is chosen (mouse down on draggable element) | | unchoose | EventEmitter<SortableEvent> | Element is unchosen (mouse up without drag) | | clone | EventEmitter<SortableEvent> | Element is cloned when dragging between lists with clone mode | | move | EventEmitter<{ event: MoveEvent; originalEvent: Event }> | Called during drag move with move event details |

SortableJS Options

All SortableJS options can be passed via options. They are proxied into Angular's zone to keep change detection predictable.

Common Options

import { Options } from "ng-hub-ui-sortable";

interface SortableOptions extends Options {
  animation?: number; // Animation speed in ms
  handle?: string; // CSS selector for drag handle
  filter?: string; // CSS selector for elements to ignore
  draggable?: string; // CSS selector for draggable items
  ghostClass?: string; // Class for the drop placeholder
  chosenClass?: string; // Class for the chosen item
  dragClass?: string; // Class for the dragging item
  group?: string | object; // Group name for multi-list drag
  sort?: boolean; // Enable sorting within list
  disabled?: boolean; // Disable the sortable
  // ... and many more
}

Event Callbacks

{
  onStart: (event) => { /* Drag started */ },
  onEnd: (event) => { /* Drag ended */ },
  onAdd: (event) => { /* Item added from another list */ },
  onRemove: (event) => { /* Item removed to another list */ },
  onUpdate: (event) => { /* Item order changed within list */ },
  onSort: (event) => { /* Any sorting change */ },
  onChange: (event) => { /* Item moved within or between lists */ },
  onChoose: (event) => { /* Item chosen */ },
  onUnchoose: (event) => { /* Item unchosen */ },
  onFilter: (event) => { /* Filtered element clicked */ },
  onClone: (event) => { /* Clone created */ }
}

Examples

Simple Sortable List

<ul [hubUISortable]="items">
  @for (item of items; track item) {
  <li>{{ item }}</li>
  }
</ul>

With Animation and Handle

<div [hubUISortable]="items" [options]="{ animation: 150, handle: '.drag-handle' }">
  @for (item of items; track item.id) {
  <div class="item">
    <span class="drag-handle">&#9776;</span>
    {{ item.name }}
  </div>
  }
</div>

Multiple Connected Lists

@Component({
  selector: "app-multi-list",
  standalone: true,
  imports: [SortableModule],
  template: `
    <div class="list" [hubUISortable]="list1" [options]="options">
      @for (item of list1; track item) {
        <div>{{ item }}</div>
      }
    </div>
    <div class="list" [hubUISortable]="list2" [options]="options">
      @for (item of list2; track item) {
        <div>{{ item }}</div>
      }
    </div>
  `,
})
export class MultiListComponent {
  list1 = ["Item 1", "Item 2", "Item 3"];
  list2 = ["Item 4", "Item 5", "Item 6"];

  options = {
    group: "shared",
    animation: 150,
  };
}

With FormArray

@Component({
  selector: "app-form-array",
  standalone: true,
  imports: [ReactiveFormsModule, SortableModule],
  template: `
    <form [formGroup]="form">
      <div [hubUISortable]="formArray" [options]="{ animation: 150 }">
        @for (control of formArray.controls; track control; let i = $index) {
          <div>
            <input [formControlName]="i" />
          </div>
        }
      </div>
    </form>
  `,
})
export class FormArrayComponent {
  form = new FormGroup({
    items: new FormArray([new FormControl("Item 1"), new FormControl("Item 2"), new FormControl("Item 3")]),
  });

  get formArray() {
    return this.form.get("items") as FormArray;
  }
}

With Angular Signals

import { Component, signal, computed } from "@angular/core";
import { SortableModule } from "ng-hub-ui-sortable";

@Component({
  selector: "app-signal-sortable",
  standalone: true,
  imports: [SortableModule],
  template: `
    <div [hubUISortable]="items" [options]="{ animation: 150 }">
      @for (item of items(); track item.id) {
        <div class="item">{{ item.name }}</div>
      }
    </div>
    <p>Current items: {{ itemNames() }}</p>
  `,
})
export class SignalSortableComponent {
  items = signal([
    { id: 1, name: "Item 1" },
    { id: 2, name: "Item 2" },
    { id: 3, name: "Item 3" },
  ]);

  // Computed signal that derives from sortable signal
  itemNames = computed(() =>
    this.items()
      .map((item) => item.name)
      .join(", "),
  );
}

Clone Mode

@Component({
  selector: "app-clone",
  standalone: true,
  imports: [SortableModule],
  template: `
    <div [hubUISortable]="items" [options]="cloneOptions" [cloneFunction]="cloneItem">
      @for (item of items; track item.name) {
        <div>{{ item.name }}</div>
      }
    </div>
  `,
})
export class CloneComponent {
  items = [{ name: "Item 1" }, { name: "Item 2" }];

  cloneOptions = {
    group: {
      name: "clone-group",
      pull: "clone",
      put: false,
    },
  };

  cloneItem = (item: any) => ({ ...item, name: `${item.name} (copy)` });
}

Custom Container

For cases where the directive host is wrapped by another component (e.g., Angular Material):

<mat-list [hubUISortable]="items" container=".mat-list-inner">
  @for (item of items; track item) {
  <mat-list-item>{{ item }}</mat-list-item>
  }
</mat-list>

Manual Control Mode

By default, ng-hub-ui-sortable automatically updates your arrays when items are dragged and dropped. However, you can opt into manual control mode for complete control over when and how arrays are updated, similar to Angular CDK's drag-and-drop approach.

When to Use Manual Control

Use manual control mode ([autoUpdateArray]="false") when you need to:

  • Validate changes before updating the data model
  • Make API calls to persist changes to a backend
  • Implement undo/redo functionality
  • Use immutable data patterns for optimal change detection
  • Add custom business logic to drag operations
  • Handle errors gracefully before committing changes

How It Works

When autoUpdateArray is set to false, the directive only emits events without modifying your arrays. You're responsible for updating the data using the provided event information.

import { Component } from "@angular/core";
import { SortableDirective, moveItemInArray, transferArrayItem, SortableEvent } from "ng-hub-ui-sortable";

@Component({
  selector: "app-tasks",
  standalone: true,
  imports: [SortableDirective],
  template: `
    <div [hubSortable]="tasks" [autoUpdateArray]="false" (update)="onUpdate($event)">
      @for (task of tasks; track task.id) {
        <div class="task">{{ task.title }}</div>
      }
    </div>
  `,
})
export class TasksComponent {
  tasks = [
    { id: 1, title: "Design mockups" },
    { id: 2, title: "Implement feature" },
    { id: 3, title: "Write tests" },
  ];

  onUpdate(event: SortableEvent): void {
    if (event.oldIndex === undefined || event.newIndex === undefined) {
      return;
    }

    // Validate the drag operation
    if (this.isValidMove(event.oldIndex, event.newIndex)) {
      // Manually update the array
      moveItemInArray(this.tasks, event.oldIndex, event.newIndex);

      // Persist to backend
      this.apiService.updateTaskOrder(this.tasks).subscribe();
    }
  }

  isValidMove(oldIndex: number, newIndex: number): boolean {
    // Add your validation logic here
    return true;
  }
}

Helper Functions

The library provides three helper functions for manual array manipulation:

moveItemInArray

Moves an item within the same array:

import { moveItemInArray } from "ng-hub-ui-sortable";

const items = ["A", "B", "C", "D"];
moveItemInArray(items, 1, 3);
// Result: ['A', 'C', 'D', 'B']
transferArrayItem

Transfers an item from one array to another:

import { transferArrayItem } from "ng-hub-ui-sortable";

const source = ["A", "B", "C"];
const target = ["1", "2", "3"];
transferArrayItem(source, target, 1, 2);
// source: ['A', 'C']
// target: ['1', '2', 'B', '3']
copyArrayItem

Copies an item to another array without removing it from the source:

import { copyArrayItem } from "ng-hub-ui-sortable";

const source = ["A", "B", "C"];
const target = ["1", "2", "3"];
copyArrayItem(source, target, 1, 2);
// source: ['A', 'B', 'C'] (unchanged)
// target: ['1', '2', 'B', '3']

Multiple Lists Example

import { Component } from "@angular/core";
import { SortableDirective, transferArrayItem, SortableEvent } from "ng-hub-ui-sortable";

@Component({
  selector: "app-kanban",
  standalone: true,
  imports: [SortableDirective],
  template: `
    <div class="board">
      <div class="column">
        <h3>To Do</h3>
        <ul [hubSortable]="todoList" [autoUpdateArray]="false" [options]="{ group: 'tasks' }" (add)="onListAdd($event, todoList)" (update)="onListUpdate($event, todoList)" data-list-id="todo">
          @for (item of todoList; track item.id) {
            <li>{{ item.title }}</li>
          }
        </ul>
      </div>

      <div class="column">
        <h3>In Progress</h3>
        <ul [hubSortable]="inProgressList" [autoUpdateArray]="false" [options]="{ group: 'tasks' }" (add)="onListAdd($event, inProgressList)" (update)="onListUpdate($event, inProgressList)" data-list-id="inProgress">
          @for (item of inProgressList; track item.id) {
            <li>{{ item.title }}</li>
          }
        </ul>
      </div>

      <div class="column">
        <h3>Done</h3>
        <ul [hubSortable]="doneList" [autoUpdateArray]="false" [options]="{ group: 'tasks' }" (add)="onListAdd($event, doneList)" (update)="onListUpdate($event, doneList)" data-list-id="done">
          @for (item of doneList; track item.id) {
            <li>{{ item.title }}</li>
          }
        </ul>
      </div>
    </div>
  `,
})
export class KanbanComponent {
  todoList = [{ id: 1, title: "Task 1" }];
  inProgressList = [{ id: 2, title: "Task 2" }];
  doneList = [{ id: 3, title: "Task 3" }];

  onListAdd(event: SortableEvent, targetList: any[]): void {
    if (event.oldIndex === undefined || event.newIndex === undefined) {
      return;
    }

    const sourceList = this.getListByElement(event.from);
    if (sourceList) {
      transferArrayItem(sourceList, targetList, event.oldIndex, event.newIndex);

      // Update backend
      this.apiService.moveTask(/* ... */).subscribe();
    }
  }

  onListUpdate(event: SortableEvent, list: any[]): void {
    if (event.oldIndex === undefined || event.newIndex === undefined) {
      return;
    }

    moveItemInArray(list, event.oldIndex, event.newIndex);
  }

  private getListByElement(element: HTMLElement): any[] | null {
    const listId = element.getAttribute("data-list-id");
    switch (listId) {
      case "todo":
        return this.todoList;
      case "inProgress":
        return this.inProgressList;
      case "done":
        return this.doneList;
      default:
        return null;
    }
  }
}

Comparison: Automatic vs Manual

| Aspect | Automatic Mode (default) | Manual Mode | | ------------------- | -------------------------- | ------------------------------ | | Array updates | Automatic | You control when and how | | Code required | Minimal | Event handlers needed | | Validation | Not possible before update | Full validation support | | API integration | Update after the fact | Update before committing | | Immutability | Mutates arrays in place | Can create new arrays | | Best for | Simple lists, prototypes | Production apps, complex logic |

Note on Event Handling: SortableJS internally fires multiple events for a single drag operation (e.g., both update and sort). ng-hub-ui-sortable handles this automatically, ensuring you only receive one event per operation in manual mode. For more details on the internal event flow, see the Events Guide.

Real-world Use Cases

The ng-hub-ui-sortable component is versatile and can be used in various real-world applications:

  • Task Lists - Reorder tasks by priority with drag-and-drop
  • Playlist Managers - Arrange media items in custom order
  • Form Builders - Drag-and-drop form field ordering
  • Dashboard Widgets - User-customizable widget layouts
  • Photo Galleries - Rearrange images in albums
  • Menu Editors - CMS navigation menu ordering
  • Kanban Boards - (For advanced boards, see ng-hub-ui-board)

Troubleshooting

Here are some common issues and how to resolve them:

Drag and drop not working

  • Check imports: Ensure SortableModule is properly imported
  • Verify binding: Make sure [hubUISortable] is bound to an array or FormArray
  • Check container: Items must be direct children of the sortable container

Array not updating

  • Reference check: SortableJS modifies the array in place; ensure change detection picks it up
  • Zone issues: If using OnPush, you may need to trigger change detection manually

Events not firing

  • Zone proxying: Events are proxied through Angular's zone automatically
  • Option reference: Provide a new object reference to options when updating options

Multi-list issues

  • Group name: Ensure all lists share the same group name in options
  • Module import: SortableModule must be imported in both components' modules

FormArray sync issues

  • Direct binding: Bind the FormArray directly, not the parent FormGroup
  • Control access: Access controls via formArray.controls in your template

If problems persist, open an issue at: https://github.com/carlos-morcillo/ng-hub-ui-sortable/issues

Scripts

| Command | Description | | ------------------- | -------------------------------------------------------------------- | | npm run start | Run the demo app locally | | npm run build:app | Production build for GitHub Pages (base href /ng-hub-ui-sortable/) | | npm run build:lib | Build the library package | | npm run test:app | Run demo app tests | | npm run pub | Publish workflow (uses wpm publish then npm publish) |

Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin feature/my-new-feature
  5. Submit a pull request

Support the Project

If you find this project helpful and would like to support its development, you can buy me a coffee:

"Buy Me A Coffee"

Your support is greatly appreciated and helps maintain and improve this project!

License

This project is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).

What this means:

You can:

  • Use commercially and non-commercially
  • Modify, adapt, and create derivatives
  • Distribute and redistribute in any format
  • Use in private and public projects

You must:

  • Give appropriate credit to the original authors
  • Provide a link to the license
  • Indicate if changes were made

Example attribution:

Based on ng-hub-ui-sortable by Carlos Morcillo
Original: https://github.com/carlos-morcillo/ng-hub-ui-sortable
License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

For full license details, see the LICENSE file.


Made with love by Carlos Morcillo Fernandez