<!DOCTYPE html>
<html>

  <head>
    <base href="." />
    <title>angular2 playground</title>
    <link rel="stylesheet" href="style.css" />
    <script src="https://unpkg.com/zone.js/dist/zone.js"></script>
    <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3/Reflect.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
    <script src="config.js"></script>
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  </head>

  <body>
    <my-app>
    loading...
  </my-app>
  </body>

</html>
/* Styles go here */
body.dragging-mode {
}

div.sortable {
  display: flex;
  flex-wrap: wrap;
}

div.sortable div {
  width: 200px;
  height: 200px;
  margin: 10px;
  text-align: center;
  font-size: 50px;
  line-height: 200px;
  background: #DDD;
  border: 5px solid #5B99B3;
}

div.sortable div.dragging {
  border: 5px solid #851E20;
}
### Angular Starter Plunker - Typescript
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {
    
    'app': './src',
    
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
    
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    
    'rxjs': 'npm:rxjs',
    'typescript': 'npm:typescript@2.0.2/lib/typescript.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
    rxjs: {
      defaultExtension: 'js'
    }
  }
});
//main entry point
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app';

platformBrowserDynamic().bootstrapModule(AppModule)
//our root app component
import {Component, NgModule} from '@angular/core'

import {BrowserModule} from '@angular/platform-browser'
import {SortableDirective} from './sortable.directive'

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Sortable</h2>
      <div appSortable class="sortable" (orderChanged)="orderChanged($event)">
        <div *ngFor="let index of indices">{{index}}</div>
      </div>
      <div>
        Order = {{order}}
      </div>
    </div>
  `,
})
export class App {
  indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  order = [...this.indices];
  constructor() {
  }

  orderChanged(order) {
    this.order = order;
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, SortableDirective ],
  bootstrap: [ App ]
})
export class AppModule {
}
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';

import { Observable } from 'rxjs/Observable';

@Directive({
  selector: '[appSortable]'
})
export class SortableDirective implements AfterContentInit {
  @Output() orderChanged = new EventEmitter();

  private _draggingElement: HTMLElement;
  private _dropSucceded: boolean;
  private _isInsideContainer: boolean;

  constructor(private element: ElementRef) { }

  ngAfterContentInit() {
    this.setDataAttributesToChildren();
    this.savePositions('orgIndex');
  }

  @HostListener('dragstart', ['$event'])
  dragStart(event) {
    this.savePositions('dragIndex');
    this._draggingElement = this.getDraggableElement(event);
    this._dropSucceded = false;
    this._isInsideContainer = true;

    // If you prefer to set an image while dragging
    // let img = new Image();
    // img.src = 'YOUR-IMAGE.PNG';
    // event.dataTransfer.setDragImage(img, 10, 10);

    // Change body class if something has to be done while dragging
    document.body.classList.add('dragging-mode');

    // Defer adding current element dragging class to let th browser to take a snapshot of the selected element
    setTimeout(() => {
      this._draggingElement.classList.add('dragging');
    });
  }

  @HostListener('dragend', ['$event'])
  dragEnd(event: MouseEvent) {
    if (!this._dropSucceded) {
      this.cancelDragging();
    }
    // Remove dragging CSS classes
    document.body.classList.remove('dragging-mode');
    this._draggingElement.classList.remove('dragging');
    this.element.nativeElement.classList.remove('dragging-outside');

    event.preventDefault();
  }

  @HostListener('dragover', ['$event'])
  dragOver(event: MouseEvent) {
    // Required to receive "drop"" event
    event.preventDefault();
  }

  @HostListener('drag', ['$event'])
  drag(event) {
    // Check if mouse is outside container or not
    const divCoords = this.element.nativeElement.getBoundingClientRect();
    const inside = (event.clientX >= divCoords.left && event.clientX <= divCoords.right && event.clientY >= divCoords.top && event.clientY <= divCoords.bottom);
    // Check if mouse mouves outisde container
    if (this._isInsideContainer && !inside) {
      this.element.nativeElement.classList.add('dragging-outside');
      this.cancelDragging();
    }
    // Else check if mouse moves back inside container
    else if (!this._isInsideContainer && inside) {
      this.element.nativeElement.classList.remove('dragging-outside');
    }

    this._isInsideContainer = inside;
  }

  @HostListener('dragenter', ['$event'])
  dragEnter(event: MouseEvent) {
    // Search for "draggable" element under the mouse
    const element: HTMLElement = this.getDraggableElement(event);

    if (element && element.attributes) {
      const draggingIndex = this._draggingElement.dataset['index'];
      const dropIndex = element.dataset['index'];

      if (draggingIndex !== dropIndex) {
        // Move dragging ghost element at its new position
        if (draggingIndex > dropIndex) {
          this.element.nativeElement.insertBefore(this._draggingElement, element);
        } else {
          this.element.nativeElement.insertBefore(this._draggingElement, element.nextSibling);
        }
        this.setDataAttributesToChildren();
      }
    }

    event.preventDefault();
  }

  @HostListener('drop', ['$event'])
  drop(event: MouseEvent) {
    this._dropSucceded = true;
    let values = [];
    for (let i = 0; i < this.element.nativeElement.childElementCount; i++) {
      let element = this.element.nativeElement.children[i];
      values.push(element.dataset.orgIndex);
    };

    this.orderChanged.emit(values);

    event.preventDefault();
  }

  private setDataAttributesToChildren() {
    for (let i = 0; i < this.element.nativeElement.childElementCount; i++) {
      let element = this.element.nativeElement.children[i];
      element.draggable = true;
      element.dataset.index = i;
    }
  }

  private savePositions(attribute) {
    for (let i = 0; i < this.element.nativeElement.childElementCount; i++) {
      let element = this.element.nativeElement.children[i];
      element.dataset[attribute] = i;
    }
  }

  private getElementAt(attribute, index) {
    for (let i = 0; i < this.element.nativeElement.childElementCount; i++) {
      let element = this.element.nativeElement.children[i];
      if (parseInt(element.dataset[attribute], 10) === index) {
        return element;
      }
    }
    return null;
  }

  private cancelDragging() {
    let index = this.element.nativeElement.childElementCount - 1;
    // Get last element
    let beforeElement = this.getElementAt('dragIndex', index);

    while (index > 0) {
      const element = this.getElementAt('dragIndex', index - 1);
      this.element.nativeElement.insertBefore(element, beforeElement);

      beforeElement = element;
      index--;
    }
  }

  private getDraggableElement(event): HTMLElement {
    // Search for "draggable" element under the mouse
    let element: HTMLElement = <HTMLElement> event.target;
    while (element && element.attributes && !element.attributes['draggable']) {
      element = <HTMLElement> element.parentNode;
    }
    return element;
  }
}