Community Post

How to create a Drag and Drop file directive in angular2 with angular-cli [Part 2]

Luis Moncaris

Introduction

Hi everyone, in this post we will build a Dran and drop directive with angular from a angular-cli scratch project.

Firts of all, we need to install angular-cli on our computer, for this we are need installed node.js, the latest version is 7.5.0, and that's the one I'll be using for this tutorial.

Once we had installed node on our computer downloading the distribution for our OS, we will be able to use npm, the node package manager solution, and we will be able to install angular-cli with this simple comand (may require sudo):

$ npm install angular-cli -g

Once installed, we will be able to create our new project with angular cli, with the command

$ ng new DnDExample

This will generate our initial structure, configuration and download all node modules required for running angular2.

Angular cli prompt on shell

My first component

Once we reach this point we will create our first example component, where will live our code of DnD. One of the advantages of working with angular-cli is that the client will generate some code for us, and will create a clean work tree directory on it, also it will help us to generate final package.

In the working directory angular-cli generated for us, we will be able to move to src/app that is the path where our typescript code will live. once there we will create our component by typing on the console

$ ng generate component dnd

or we can also use the shorter version :

$ ng g c dnd

In shorter version, g comes from generate and c from component.

Once the execution has finished, there will be a new directory named "dndcomponent" where our component code will live, also we will see that ng modified file app.module to import and declare our new component for us. Bash output after ng generate have been executed

Once in here, we will have a folder named dnd-component with 4 files, these files are :

  • dnd-component.css (style file for our component)
  • dnd-component.html (html code for our component)
  • dnd-component.ts (typescript file to define the behaviour of our component)
  • dnd-component.specs.ts (karma test file for unit testing, will be ignored for this post)

Serving our component in our app component

Once we have generated the first component, in dnd-component.ts we will see a decorator like this :

@Component({
  selector: 'app-dnd',
  templateUrl: './dnd.component.html',
  styleUrls: ['./dnd.component.css']
})

This decorator will show the meta data of the new created component, to start using it we should use a tag with the selector metadata and add it to the app.component.html file in src/app folder.

<app-dnd></app-dnd>

Once we have done this, we will start our test server with angular-cli by executing on project's root path the next command :

$ ng serve

Once the code have been transpiled and all been set, we can go to our browser to localhost:4200 and we will see something like this.

Creating drag and drop zone on component

In dnd.component.html file we will create the next html structure that will define our drop zone:

<div class="dropzone">
  <div class="text-wrapper">
    <div class="centered">Drop your file here!</div>
  </div>
</div>

And also we are going to set some styling to this structure, by adding this to the file dnd.component.css :

.dropzone {
  min-height: 400px;
  min-width: 400px;
  display: table;
  width: 100%;
  background-color: #eee;
  border: dotted 1px #aaa;
}

.text-wrapper {
  display: table-cell;
  vertical-align: middle;
}

.centered {
  font-family: sans-serif;
  font-size: 1.3em;
  font-weight: bold;
  text-align:center;
}

With this structure we will get something like this : Div with styling

Directives and how to use them

After we go on, we should talk a little bit about directive, there are 3 differents kinds of directives on angular 2. The most used (and we have already use one of them) are components. This kind of directives define the behaviour of each part of our application. The second ones are structural directives, this ones make changes on the dom of the application against some conditions or expressions declared on them. (Like ngFor or ngIf directives). The third ones are attributes directives, this ones make changes on the element they are attached, like changes on styling, classes, attributes, etc. (Like ngClass or ngStyle directives).

To create a directive, we must be placed on folder we are going to create it and use the next angular-cli command :

$ ng g d dnd

By doing this angular-cli will create 2 files on our actual directory :

  • dnd.directive.ts (main file with behaviour of our directive)
  • dnd.directive.specs.ts (karma test file for unit testing) In dnd.directive.ts file we will find a decorator like in components file but slight different from it, it is a directive decorator and it attach metadata to our new class to allow it to behave like a simple directive.
@Directive({
  selector: "[appDnd]",
})

As we can see, the selector now (it's like a css standar selector) is an attribute selector, so to use this directive we are going to declare a new attribute on the host tag, in our case the div with class dropzone.

<div class="dropzone" appDnd>
  <div class="text-wrapper">
    <div class="centered">Drop your file here!</div>
  </div>
</div>

So, by doing this we are attaching the directive to the host tag div.dropzone.

Creating directive behaviour

First of all, we are going to explain how to bind events and attributes from host to the directive. The first one we are going to modify is the color of the div when a file is being dragging inside of the component. To do this we are going to listen to host event dragover, to do this we are going to use decorator @HostListener and create a new function on our directive that will be executed every time the host trigger the evenet dragover like this :

@HostListener('dragover', ['$event']) onDragOver(evt){
    evt.preventDefault();
    evt.stopPropagation();
    let files = evt.dataTransfer.files;
    if(files.length > 0){
      //do some stuff here
    }
  }

Remember to import HostListener from @angular/core

To check out what we are doing, I logged on the console every time the event have been fired and it looks like this :

On draggin over, the console shows logs every few hundred milliseconds

In the same way, we are going to declare 2 more @HostListeners, one for drag leaving dragleave and one for drop, en each one we are going to also to define 2 functions and decorate them with @HostListener, let check out how are they going to be :

@HostListener('dragleave', ['$event']) public onDragLeave(evt){
    evt.preventDefault();
    evt.stopPropagation();
    //do some stuff
  }

  @HostListener('drop', ['$event']) public onDrop(evt){
    evt.preventDefault();
    evt.stopPropagation();
    let files = evt.dataTransfer.files;
    if(files.length > 0){
      //do some stuff
    }
  }

In dropping function (onDrop) we are also checking in the event if there are some files on the file list that comes in the event evt.dataTransfer.files to check if the object that have been dropped is actually a file (or files, we will check this later).

Also, as in some DnD plugins, we are going to change background or show something to the user when is draggin/dropping something in our zone, to do this we should bind also properties from the host to the directive, we can do this by using @HostBinding decorator and decorate a property on the directive class, for this example we are going to control the background to make it darker when draggin over the zone.

export class DndDirective {

  @HostBinding('style.background') private background = '#eee';

  constructor() { }

  // here will continue the code for @HostListener...
  // ...
  }

Remember to import HostBinding from @angular/core By doing this, I'm ordering angular to make the property style.background from the host to be override by property named background on the directive class.

Once we have done this, any time the property of the directive called background changes on the class, it will reflect on the background property of the tag div.dropzone, so on @HostListener functions we are going to change this background property like this :

  @HostListener('dragover', ['$event']) public onDragOver(evt){
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#999';
  }
  @HostListener('dragleave', ['$event']) public onDragLeave(evt){
    evt.preventDefault();
    evt.stopPropagation();
    this.background = '#eee'
  }
  @HostListener('drop', ['$event']) public onDrop(evt){
    evt.preventDefault();
    evt.stopPropagation();
    let files = evt.dataTransfer.files;
    if(files.length > 0){
      this.background = '#eee'
    }
  }

Now, when the user drag an object over the zone this will be darker than usual, and when it drop it inside the zone or leave outside of it with the dragged object, the zone will be lighter like in the beggining.

On left normal zone | On right draggin event is being triggered

  • On left normal zone | On right draggin event is being triggered

    Conclutions

  • We learned how to start a scratch project with angular-cli (by doing ng new ).
  • We learned how to create components on the angular-cli (by doing ng generate component or ng g c ).
  • We have learned how to give an structure to our components and some styles also.
  • We have learned how to create a new directive in angular-cli (by doing ng generate directive or ng g d ).
  • We have learned what is the difference between the 3 most used types of directives in angular 2.
  • We have learned how to start create a behaviour for our directive by listening to events with @HostListener and how to bind properties from host tag to our directive with @HostBinding.

Thanks for reading and sharing this post. I'll be doing part 2 asap. Minrock.

Luis Moncaris

Backend developer with more than 4 year experience in PHP, Python/Django and other web frameworks. Also working 2 years in frontend technologies to create rich UI and UX. Actually developer at Nokia Colombia as Full Stack developer.