Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@bhobbs/ng2-dragula

b-hobbs25MIT2.0.6TypeScript support: included

Simple drag and drop with dragula

angular, dragula, ng-dragula, angular2, ng2, angular2-dragula, ng2-dragula, ngx-dragula

readme

Logo

Drag and drop so simple it hurts

Official Angular wrapper for dragula.

Notice: v2 has been released

It contains a number of breaking changes. Follow the migration guide here or read the full changelog.

npm version npm downloads slack Build Status codecov Dependency Status

Demo

Demo

Try out the demo!

Install

You can get it on npm.

npm install ng2-dragula
# or
yarn add ng2-dragula

Setup

1. Important: add the following line to your polyfills.ts:

(window as any).global = window;

This is a temporary workaround for #849, while upstream dragula still relies on global.

2. Add DragulaModule.forRoot() to your application module.

import { DragulaModule } from 'ng2-dragula';
@NgModule({
  imports: [
    ...,
    DragulaModule.forRoot()
  ],
})
export class AppModule { }

On any child modules (like lazy loaded route modules), just use DragulaModule.

3. Add the CSS to your project

You'll also need to add Dragula's CSS stylesheet dragula.css to your application (e.g. in styles.scss). The following is slightly better than node_modules/dragula/dist/dragula.css (it includes pointer-events: none (#508) and this fix), but you may wish to make your own modifications.

/* in-flight clone */
.gu-mirror {
  position: fixed !important;
  margin: 0 !important;
  z-index: 9999 !important;
  opacity: 0.8;
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
  filter: alpha(opacity=80);
  pointer-events: none;
}
/* high-performance display:none; helper */
.gu-hide {
  left: -9999px !important;
}
/* added to mirrorContainer (default = body) while dragging */
.gu-unselectable {
  -webkit-user-select: none !important;
  -moz-user-select: none !important;
  -ms-user-select: none !important;
  user-select: none !important;
}
/* added to the source element while its mirror is dragged */
.gu-transit {
  opacity: 0.2;
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
  filter: alpha(opacity=20);
}

Then you're ready to go

Here's a super quick sample to get you started:

@Component({
  selector: "sample",
  template:`
  <div>
    <div class="wrapper">
      <div class="container" dragula="DRAGULA_FACTS">
        <div>You can move these elements between these two containers</div>
        <div>Moving them anywhere else isn't quite possible</div>
        <div>There's also the possibility of moving elements around in the same container, changing their position</div>
      </div>
      <div class="container" dragula="DRAGULA_FACTS">
        <div>This is the default use case. You only need to specify the containers you want to use</div>
        <div>More interactive use cases lie ahead</div>
        <div>Make sure to check out the <a href="https://github.com/bevacqua/dragula#readme">documentation on GitHub!</a></div>
      </div>
    </div>
  </div>
  `
})
class Sample {}

Usage

This package isn't very different from dragula itself. I'll mark the differences here, but please refer to the documentation for dragula if you need to learn more about dragula itself.

Directive

There's a dragula directive that makes a container's direct children draggable. You must supply a string. Both syntaxes, dragula="VAMPIRES" or [dragula]="'VAMPIRES'", work equally well.

<ul dragula="VAMPIRES">
  <li>Dracula</li>
  <li>Kurz</li>
  <li>Vladislav</li>
  <li>Deacon</li>
</ul>

Grouping containers

You can group containers together by giving them the same group name. When you do, the children of each container can be dragged to any container in the same group.

<div dragula="VAMPIRES">
  <!-- vamps in here -->
</div>
<div dragula="VAMPIRES">
  <!-- vamps in here -->
</div>

<div dragula="ZOMBIES">
  <!-- but zombies in here! -->
</div>

If you want to make sure you are using the same type string in different places, use the [dragula] syntax to pass a string variable from your component:

<div [dragula]="Vampires"></div>
<div [dragula]="Vampires"></div>
class MyComponent {
  Vampires = "VAMPIRES";
}

dragulaModel

If your container's children are rendered using ngFor, you may wish to have it synced. If you provide the same array to the dragulaModel attribute on the container element, any changes will be synced back to the array.

NOTE: v2 changes the behaviour of [dragulaModel]. It no longer mutates the arrays you give it, but will shallow clone them and give you the results. Use two-way binding with [(dragulaModel)]="...", or use the DragulaService dropModel and removeModel events to save the new models produced.

<ul dragula="VAMPIRES" [(dragulaModel)]="vampires">
  <li *ngFor="let vamp of vampires">
    {{ vamp.name }} likes {{ vamp.favouriteColor }}
  </li>
</ul>

Note: DO NOT put any other elements inside the container. The library relies on having the index of a DOM element inside a container mapping directly to their associated items in the array. Everything will be messed up if you do this.

On top of the normal Dragula events, when [dragulaModel] is provided, there are two extra events: dropModel and removeModel. Further details are available under Events

Drake options

If you need to configure the drake (there's exactly one drake per group), you can use the DragulaService.

import { DragulaService } from 'ng2-dragula';

class ConfigExample {
  constructor(private dragulaService: DragulaService) {
    dragulaService.createGroup("VAMPIRES", {
      removeOnSpill: true
    });
  }
}

See below for more info on options.

DragulaService

This service exposes a few different methods with which you can interact with dragula.

dragulaService.createGroup(name, options)

NOTE: formerly known as setOptions()

Creates a group named name, with an options object.

dragulaService.find(name: string)

Returns a Group named name, if there is one. A Group contains the following properties.

  • name is the name that identifies the group
  • drake is the raw drake instance itself
  • options is the options object used to create the drake. Modifying it won't do anything useful.

dragulaService.destroy(name)

Destroys a Group named name and its associated drake instance. Silently returns if the group does not exist.

DragulaOptions

Refer to the documentation for dragula to learn more about the native options.

All of the native options work with ng2-dragula. However, there is one addition:

copyItem: <T>(item: T) => T

When you have:

  • [dragulaModel]
  • copy is true or a function that returns true

... ng2-dragula will have to create a clone of the JS object you picked up. In previous versions of ng2-dragula, there was a terribly buggy, one-size-fits-all clone function. From v2 onwards, you MUST provide your own copyItem function.

If you have a simple object with no nested values, it could be as simple as:

{
  copy: ...,
  copyItem: (item: MyType) => ({ ...item })
}

There is a complete example using a Person class on the demo page.

Events

Whenever a drake instance is created with the dragula directive, there are several events you can subscribe to via DragulaService. Each event emits a typed object, which you can use to get information about what happened.

Refer to the Drake events documentation for more information about the different events available. Each event follows this format:

Event named: 'drag'

Native dragula:
  Use: drake.on('drag', listener)
  Listener arguments: (el, source)

ng2-dragula:
  Method: DragulaService.drag(groupName?: string): Observable<...>
  Observable of: { name: string; el: Element; source: Element; }

Each supports an optional parameter, groupName?: string, which filters events to the group you're interested in. This is usually better than getting all groups in one observable.

The sample below illustrates how you can use destructuring to pull values from the event, and unsubscribe when your component is destroyed.

<div dragula="VAMPIRES"></div>
import { Subscription } from 'rxjs';
import { DragulaService } from 'ng2-dragula';

export class MyComponent {
  // RxJS Subscription is an excellent API for managing many unsubscribe calls.
  // See note below about unsubscribing.
  subs = new Subscription();

  constructor(private dragulaService: DragulaService) {

    // These will get events limited to the VAMPIRES group.

    this.subs.add(this.dragulaService.drag("VAMPIRES")
      .subscribe(({ name, el, source }) => {
        // ...
      })
    );
    this.subs.add(this.dragulaService.drop("VAMPIRES")
      .subscribe(({ name, el, target, source, sibling }) => {
        // ...
      })
    );
    // some events have lots of properties, just pick the ones you need
    this.subs.add(this.dragulaService.dropModel("VAMPIRES")
      // WHOA
      // .subscribe(({ name, el, target, source, sibling, sourceModel, targetModel, item }) => {
      .subscribe(({ sourceModel, targetModel, item }) => {
        // ...
      })
    );

    // You can also get all events, not limited to a particular group
    this.subs.add(this.dragulaService.drop()
      .subscribe(({ name, el, target, source, sibling }) => {
        // ...
      })
    );
  }

  ngOnDestroy() {
    // destroy all the subscriptions at once
    this.subs.unsubscribe();
  }
}

NOTE: You should always unsubscribe each time you listen to an event. This is especially true for a component, which should tear itself down completely in ngOnDestroy, including any subscriptions. It might not be necessary if you have a global singleton service (which is never destroyed) doing the subscribing.

You can also engineer your use of events to avoid subscribing in the first place:

import { merge } from 'rxjs';
import { mapTo, startWith } from 'rxjs/operators';

dragStart$ = this.dragulaService.drag("VAMPIRES").pipe(mapTo(true));
dragEnd$ = this.dragulaService.dragend("VAMPIRES").pipe(mapTo(false));
isDragging$ = merge(dragStart$, dragEnd$).pipe(startWith(false));

// html: [class.dragging]="isDragging$ | async"

Special Events for ng2-dragula

Each of dropModel(name?: string) and removeModel(name?: string) takes

Event Name Listener Arguments Event Description
dropModel { type, el, target, source, sourceModel, targetModel, item } same as normal drop, but with updated models + the item that was dropped
removeModel { type, el, container, source, sourceModel, item } same as normal remove, but with updated model + the item that got removed

Classic Blunders

There are a number of very common issues filed against this repo. You will be mocked terribly if you file a bug and it turns out you made one of these blunders and it wasn't a bug at all.

1. Do not put [dragula] or [dragulaModel] on the same element as *ngFor.

WRONG:

<div class="container">
  <div *ngFor="let x of list"
       dragula="WRONG" [dragulaModel]="list">...</div>
</div>

RIGHT:

<div class="container" dragula="RIGHT" [dragulaModel]="list">
  <div *ngFor="let x of list">...</div>
</div>

2. Do not add any child elements that aren't meant to be draggable

WRONG:

<div class="container" dragula="WRONG" [dragulaModel]="list">
  <h2>WRONG: This header will mess up everything, and you will
      get really weird bugs on drop</h2>
  <div *ngFor="let x of list">...</div>
</div>

RIGHT:

<h2>This header will not be draggable or affect drags at all.</h2>
<div class="container" dragula="RIGHT" [dragulaModel]="list">
  <div *ngFor="let x of list">...</div>
</div>

Development

  • You must use Yarn >= 1.3. It includes the 'workspaces' feature.
  • Please use Conventional Commits in your commit messages.

setup

yarn
(cd modules/ng2-dragula && yarn build)

run tests

(cd modules/ng2-dragula && yarn test)
# or
(cd modules/ng2-dragula && yarn test:headless)

run demo server

# listens for changes in the library and rebuilds on save
yarn watch
# runs demo server
(cd modules/demo && yarn start)

Publishing a new version

yarn lerna publish

Credits

  • v1: Nathan Walker (@NathanWalker)
  • v1.x: Dmitriy Shekhovtsov (@valorkin)
  • v2: Cormac Relf (@cormacrelf)

changelog

Change Log

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

2.0.6 (2018-11-08)

Note: Version bump only for package @bhobbs/ng2-dragula

2.0.5 (2018-07-26)

Note: Version bump only for package @bhobbs/ng2-dragula

2.0.4 (2018-07-26)

Note: Version bump only for package @bhobbs/ng2-dragula

2.0.3 (2018-07-26)

Note: Version bump only for package @bhobbs/ng2-dragula

2.0.2 (2018-07-25)

Bug Fixes

  • directive should only emit, not save on removeModel (59783f0)
  • was doubling up on writes to drake.models, causing sometimes wrong model order (f1b26cd), closes #878

2.0.1 (2018-07-20)

Bug Fixes

  • types: '@types/dragula' was a devDependency, not present in published version, closes #876 (eaef778)

2.0.0 (2018-07-19)

Version 2.0.0 is big. It has lots of breaking API changes are bundled into one release. (Thankfully, it also gains a hefty test suite.) Rather than reading all the changes here, maybe check out the V2 migration guide.

Bug Fixes

  • @types/dragula DragulaOptions.copy only taking boolean (ec282c0)
  • build with Angular Package Format via ng-packagr (c362bd8)
  • DragulaService.handleModels called twice (intro'd by c5b1d40) (d4df88c)
  • dropModel event not ever firing if models is initially null/undefined. fix #307 (c5b1d40)
  • export EventTypes, document MockDrake much more (10249ab)
  • memory leaks and containers/dragulaModels hanging around in drakes after destroy or setting null (2d2c890), closes #794 #852 #784
  • Mismatch of on remove parameter values. Thanks @Thanatos-elNyx, fix #842 and #855 (7cb1889)
  • more types on DragulaService (927cad5)
  • redo confusing DragulaService methods (setOptions => createGroup) (f50d37a)
  • remove [dragulaOptions] (d2886c3)
  • fix target.removeChild(dropElm). (766285a), closes #596

Features

  • Custom copyItem to clone model objects (78f3f9f)
  • DragulaModule.forRoot(), with plain DragulaModule on child NgModules. (b739f9b), closes #805 #187
  • dropModel and removeModel get item, sourceModel/targetModel props (572c1ba)
  • enable two-way binding of [(dragulaModel)] (53f8d41)
  • new strongly typed events API with type (aka 'bag') filtering (6339fc4)

BREAKING CHANGES

Package

  • Requires Angular 6. This is because of the Angular Package Format changes, and also RxJS 6's breaking changes.
  • Requires RxJS 6. If you are still using Angular 5, which requires RxJs 5, and you can't upgrade yet (even though the CLI upgrade tools are excellent), you might be able to use `rxjs@5.6.0-forward-compat.5`. This has not been tested.
  • Requires importing DragulaModule.forRoot() in your root NgModule, and plain DragulaModule in child modules. This is so you can break your app into lazy loaded modules and not experience problems with having more than 1 DragulaService.

Directive

  • dragulaModel no longer modifies arrays internally. You have to use [(dragulaModel)]="myModel" if you want to save the changes the library makes. In most cases, this will be an easy change. However, if you were relying on the arrays being modified directly, that will no longer work.
  • Removes [dragulaOptions] from the directive. It is not a neater API (still have to import DragulaOptions, why not just import DragulaService), and it is misleading (changing the object does not get reflected merely because it uses Angular binding; you can't change options once set).

DragulaService

  • All the events available on DragulaService are now functions with an optional groupName?: string argument that narrows the stream down to events from drags of a particular group. (Groups are formerly known as 'bags'). E.g: this.dragulaService.drag("DOG").subscribe(...).
  • DragulaService events are now proper Observables, not EventEmitters.
  • DragulaService events no longer output arrays directly from Dragula.js; they are now strongly typed objects. You can migrate by just swapping square braces for curly ones, if you used the same names: .subscribe(([el, source]) => {}) becomes .subscribe(({el, source}) => {})
  • DragulaService.add now takes a Group. You probably don't want to use it.
  • DragulaService.setOptions is now called createGroup. This is much clearer. It was not obvious that setOptions actually created groups before; it was even less obvious that you couldn't call it again later to change behaviour.
  • If you are using copy with [dragulaModel], you must now provide your own copyItem function. The old one was JSON.parse(JSON.stringify(x)), which was terrible.

1.5.0 (2017-05-21)

1.3.1 (2017-04-10)

Bug Fixes

Features

  • package: relaxed peer dependencies to allow ng v4 (94f5e2b)

1.3.0 (2017-01-17)

Bug Fixes

  • demo: make AoT compatible (8667689)

Features

  • package: v1.3.0 upgrade to ng v2.3+ (b8ce361)

1.2.2 (2016-12-16)

Features

  • package: build upgrage + AoT support (#494) (0a3e929)

1.2.2-0 (2016-10-19)

Features

  • package: added module metadata extraction for Angular AoT compiler (0deed74), closes #417

1.2.1 (2016-09-21)

Features

1.2.0 (2016-09-07)

Bug Fixes

  • demo: the error in the demo move } to the bottom(#329) (c20e699)
  • demo: update ngFor syntax (e3c870a)

Features

  • package: added main entry point to package.json (#319) (a53bf7b)
  • package: upgrade to angular 2.0.0-rc.2 (#299) (e275b99)

1.1.10 (2016-06-06)

Bug Fixes

  • package: install ng2-dracula in a project without typings.json (#277) (9bebfed)

1.1.9 (2016-05-13)

Bug Fixes

  • publish: enabled .d.ts files generation (a7366c8)

1.1.8 (2016-05-13)

Bug Fixes

  • publish: ng2-dragula publish flow fixed (61a6bec)

1.1.7 (2016-05-12)

Bug Fixes

  • dragula: compatible with webpack and other loaders. no more window.dragula (e6830ca)
  • dragula: use require. also update to beta.6 (b0a9ca3)
  • dragula: using typings and removed require usage (feaf945)
  • dragulaModel: model sync works properly now (326d198)
  • removeModel: issue with callback (c19c676)
  • require: removing require (b033e85)
  • require: removing usage of require (0f7ee40)

Features

  • package: Upgrade to angular 2.0.0-rc.1 (f3ff82c), closes #239