How to Build HTML Drag and Drop in Angular

The HTML Drag and Drop API gives developers a clean interface to integrate draggable and droppable elements. This article will cover how to integrate that API into directives using Angular. Specifically, we will be creating two directives, draggable and droppable, which both communicate using a common service. Before diving into the post, it’s assumed that you have some familiarity with Angular (2+) using TypeScript. Within this post, the “draggable directive” is commonly referred to as a “draggable element” and the “droppable directive” is commonly referred to as a “droppable zone”. To provide clarity, when draggable or droppable is not suffixed with “directive”, this is referencing the directive in action. Before starting, you can do the following first:

EDIT – 11/29/2018: We never made any reference to using ngZone for sake of simplicity. However, using ngZone can definitely help to increase performance.

Draggable Directive

Defines a draggable element that can be moved to a droppable zone. The primary purpose of this directive is to pass data to the drag service (covered later in the article). The directive is fairly simple which includes data binding (using the @Input decorator), lifecycle hooks, and the Renderer2 API (an abstraction for UI rendering manipulations). Now let’s actually look at the code that is used to make the directive. If you’re already familiar with Angular, then reading the code might be all the information you need. For those that are less familiar with angular, follow the comments within the code and read the list after the code block.

  1. Defines an attribute directive using the @Directive decorator. This allows us to make any HTML element a draggable directive by adding the input property “[appDraggable]”.
  2. Defines what to do with the value of input property appDraggable using Typescript’s mutator syntax for setters.
  3. Defines logic that is used when the draggable element is initialized. We use the Renderer2 API to add the attribute draggable with a value of true (draggable=”true”). The draggable attribute will trigger the browser’s native HTML Drag and Drop API. We also use the Renderer2 API to add a class to the directive element.
  4. Defines the lifecycle hook for what to do when the directive has been initialized.
  5. Defines the lifecycle hook for what to do when the directive is destroyed.
  6. Defines the event listeners for ‘dragstart’ and ‘dragend’. We are using the Renderer2 API to add the events, and not the @HostListener decorator. This allows us to destroy an event listener.
  7. The ‘dragstart’ event does two things. First, the event will set the zones which the draggable element can be dropped into, using the drag service. Secondly, the event will transfer data which can be read by a ‘drop’ event in the droppable directive. The data that the event transfers is set by the input property binding value below:
    
    <div [appDraggable]={data: '', zone: ''}>
        <span>I can be dragged to a droppable zone.</span>
    </div>.
  8. The ‘dragend’ event removes styling when a draggable element is not dropped into a droppable element.
  9. Defines the signature for the draggable options. This is useful because it allows you to import “DraggableOptions” to define signatures in other components/directives.

Droppable Directive

The droppable directive defines a zone which a draggable element can be dropped onto. The primary purpose of the directive is to handle drag events which determine if the draggable element can be added to the droppable zone. A shared service is used to compare the droppable element with the list of zones a draggable element can move too. This directive is a little more involved than the draggable directive, but still relatively simple. As before, let’s look at the code that is used to make the directive. Again, if you’re already familiar with Angular, then reading the code might be all the information you need. For those that are less familiar with angular, follow the comments within the code and read the list after the code block.

  1. Defines an attribute directive using the @Directive decorator. This allows us to make any HTML element a droppable directive by adding the input property “[appDroppable]”.
  2. Defines what to do with the value of input property appDroppable using Typescript’s mutator syntax for setters.
  3. Defines logic that is used when the droppable element is instantiated. We use the Renderer2 API to add a class to the directive element.
  4. Lifecycle hook logic to run when the directive has been initialized.
  5. Lifecycle hook logic to run when the directive has been destroyed.
  6. Defines the event listeners for ‘dragenter’, ‘dragleave’, ‘dragover’ and ‘drop’.
  7. The ‘dragenter’ event will add a class to the droppable element indicating that a draggable element can be placed in the zone. It’s important to note that we prevent the default action from happening if a draggable element can be placed into the zone. This is critical because it allows for the drag and drop action to possible.
  8. The ‘dragleave’ event will remove a class from the droppable element indicating that a draggable element is no longer placed over the zone.
  9. The ‘dragover’ event doesn’t do anything specifically, but is here for completeness for those that may need more flexibility. This could potentially be useful to determine the exact position a draggable element should be placed at. Again, it’s important to note that we prevent the default action from happening for the same reason as the previous list item.
  10. The ‘drop’ event is responsible for emitting event data to let the parent component know that a draggable element has moved. The event data consists of the zone which the draggable element was dropped into, and the data which was set by the ‘dragstart’ event of the draggable directive.
  11. Defines the signature for the droppable options. This is useful because it allows you to import “DroppableOptions” to define signatures in other components/directives.

Drag Service

The Drag Service is responsible for allowing communication between the draggable and droppable directives. The service is specified in the providers array of the application’s app module definition. This means that the same instance of the service is provided to both the draggable and droppable directive, so data can be persisted. Again, if you’re already familiar with Angular, then reading the code might be all the information you need. Continuing on, Draggable components use the service to do two things:

  1. Specify which zones the draggable element can be dropped into by using the startDrag() method.
  2. To remove the highlighted styling of a zone using the removeHighLightedAvailableZones() method.

Droppable components use the service to do three things:

  1. Add their zone, with access to control the styling of zone, to the service.
  2. Determine if drag events accept their zone as a valid drop target.
  3. Remove their zone from the service when destroyed.

The last bullet is important because the drag service is provided to all components which include it. Thus, when a zone is destroyed, it needs to de-register itself from the service.

Summary: How to Build HTML Drag and Drop in Angular

Wrapping things up, the important consideration is that communication is handled via a shared service that is provided by the application component, or which ever component that you need to provide the service. The Draggable and Droppable directives are a means of transporting data from one “state” to another based on your application requirements. If you need some help with your Angular project, don’t hesitate to get in touch with us. for Solutions Architecture. And as always, you can tweet @ThreeVentures with your Angular questions or thoughts.

Let's Talk

Full name with letters, apostrophes, and spaces only
Please enter a valid email
Numbers only, 7 to 15 digits
  • Analytics
  • Advertising
  • Amazon Web Services
  • App. Development
  • a snickers bar
  • $1,000+
  • $5,000+
  • $25,000+
  • $50,000+
  • ASAP
  • Today
  • Tomorrow
  • Next Week