Creating a Drag and Drop File Upload Component Using Angular Directives

Bilkiss Dulloo
The Startup
Published in
6 min readAug 9, 2020

Part 1: Attribute directives

In this 2-part series, we are looking at how to implement directives in Angular. In this first part we will be looking at attribute directives. And in part 2 we will be looking at structural directives.

Recently, during one of my training sessions, one of my trainees working on an Angular project needed to implement a drag and drop file upload component.

So as is often the first reflex, they looked for an existing npm package that does the task, but ran into issues with making the new packages feel coherent with the rest of the application’s UI.

After helping them dig around for a way to customize the CSS however, I had a thought. What if I used this situation as an opportunity to build their understanding of custom directives, and by the same occasion demystify the implementation of such a functionality by showing them they could easily build it themselves? This blog post as basically what we ended up with.

So we already had an existing Angular project initialized, but for the sake of this blog post being its own independent thing if you are following through, we will here create a generic app skeleton to demo this.

As in my previous blog post I am assuming that you already have the Angular cli installed here. (If not, please check out this guide to install the tooling: Angular — Setting up the local environment and workspace https://angular.io/guide/setup-local )

Step 1: Creating our standalone project

Run the following command in your terminal

ng new angular-file-upload --style=scss

Step 2: Generating a new directive that will contain our logic for the drag and drop file upload.

Angular directives are functions that enhance the capability of HTML elements by attaching custom behavior to the DOM via custom tags and attributes. What does this mean concretely? Let’s have a look.

First we need to change directory to our project folder

cd angular-file-upload

Then we can generate our brand new directive using the angular CLI

ng g directive directives/upload

This shall create the following upload.directive.ts file:

import { Directive } from ‘@angular/core’;@Directive({
selector: ‘[appUpload]’
})
export class UploadDirective {constructor() { }}

So basically this class will be our directive. Worthy of note already in that skeleton is the selector in the directive declaration. In a nutshell, this means that any DOM element we attach the “appUpload” attribute to will have additional behaviour that we will defined in this class. We can also change the selector if we wish, but for the purpose of this demo we shall leave it be :)

Step 3: Importing the pieces we need

Part of the beauty of Angular is that the framework comes with many parts already included, so we won’t have any external dependency for this functionality and only use what is provided to us in @angular/core.

Update the import line in our directive to read as follows:

import { Directive, Output, EventEmitter, HostBindingDecorator, HostListener } from ‘@angular/core’;

So if we go over the new additions, we now have:

  • Output and EventEmitter will be used to provide a hook event our component will be able to subscribe to. This is important since we need the component to be able define what happens when a file is dropped.
  • HostBindingDecorator will allow us to interact with the properties of the DOM element our directive will be attached to. In our case we will interact with the style of the host element
  • and HostListener will allow us to listen to DOM events from the host element and define how our directive will react to them.

Step 4: Define our Class properties

Within our Class definition, we will first add three properties using our Output and HostBindingDecorator as follows:

@Output() onFileDropped = new EventEmitter<any>();
@HostBinding(‘style.background-color’) public background = ‘#fff’;
@HostBinding(‘style.opacity’) public opacity = ‘1’;

Here we have the properties that will help us control the directive.

  • OnFileDropped will be the custom event that we will expose to our component, thus the output decorator. This will be triggered when a file is dropped on our host DOM element later.
  • background and opacity on the other hand are simple properties that we are binding to the host DOM element’s style property. We are also setting the default “idle” values to a white background and a full opacity.

As of now, if we try to attach our directive to a DOM element, nothing much will happen, only the element’s background color and opacity will be affected. So now would be the perfect time to add some more behaviour to our directive :)

Step 5: Adding the drag events

We will now add two methods to our class, which will be event listeners for our dragOver and dragLeave events. In these methods, we will simply change the background and opacity defined above, and prevent those events from bubbling up to our host component.

//Dragover listener, when something is dragged over our host element@HostListener(‘dragover’, [‘$event’]) onDragOver(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = ‘#9ecbec’;
this.opacity = ‘0.8’
}
//Dragleave listener, when something is dragged away from our host element@HostListener('dragleave', ['$event']) public onDragLeave(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = '#fff'
this.opacity = '1'
}

Step 6: Our drop event

Almost done with the directive! all that’s left is to add our last event listener, for when a file is actually dropped over our host DOM element. Let’s add the following method:

@HostListener('drop', ['$event']) public ondrop(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = '#f5fcff'
this.opacity = '1'
let files = evt.dataTransfer.files;
if (files.length > 0) {
this.onFileDropped.emit(files)
}
}

This one is a bit different. Apart from the similar bubbling prevention and style alterations, here we are checking if there are actually files that were dropped on our host DOM element, and if such is the case we emit our custom onFileDropped event that can be subscribed to from our component.

We have now completed our directive. here is the complete Class definition:

import { Directive,Output, EventEmitter, HostBinding, HostListener, HostBindingDecorator } from '@angular/core';

@Directive({
selector: '[appUpload]'
})
export class UploadDirective {

@Output() onFileDropped = new EventEmitter<any>();
@HostBinding('style.background-color') public background = '#fff';
@HostBinding('style.opacity') public opacity = '1';

//Dragover listener, when something is dragged over our host element
@HostListener('dragover', ['$event']) onDragOver(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = '#9ecbec';
this.opacity = '0.8'
};

//Dragleave listener, when something is dragged away from our host element
@HostListener('dragleave', ['$event']) public onDragLeave(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = '#fff'
this.opacity = '1'
}

@HostListener('drop', ['$event']) public ondrop(evt) {
evt.preventDefault();
evt.stopPropagation();
this.background = '#f5fcff'
this.opacity = '1'
let files = evt.dataTransfer.files;
if (files.length > 0) {
this.onFileDropped.emit(files)
}
}

constructor() { }

}

Step 7: Using the directive

Now that our directive has been implemented, we can include it in our component. It can be any component in your app, but for the purpose of this demo we will be using the main app component.

In our app.component.html file let’s add the following element:

<div appUpload (onFileDropped)="uploadFile($event)" >Drop a file here</div>

Our final step will be to handle the onFileDropped event within our app.component.ts by defining the uploadFile method.

uploadFile(evt){
// evt is an array of the file(s) dropped on our div. Here we're assuming only one file has been uploaded

let payload = new FormData();
payload.append('data', evt[0]);
// File can now be uploaded by doing an http post with the payload
}

From there we can proceed with tweaking the UI as desired to fit our requirements :)

During my training sessions, one of my main objectives is to help demystify the implementation of common UI components to my trainees. It has been my observation that Junior devs tend to treat ready-made UI components as black boxes and demonstrating how they themselves could implement such a component from scratch helps them realize the realm of possibilities at their fingertips :)

I have also added some CSS to make the demo a bit more user friendly. As usual all the code for this demo can be found at: Angular file upload.

The UI for this demo can be found at Angular upload file.

--

--

Bilkiss Dulloo
The Startup

In the field of front end dev more than 10 years, I am passionate and thrilled that there are so much new and exciting technologie/frameworks to learn everyday.