Search code examples
angulardrag-and-dropangular-directiveangular-event-emitter

How to set up the directive when creating a drag and drop zone in Angular?


I'm trying to create a files-drag & drop zone in Angular. I created dropzone.directive and added it to the declarations in app.module.ts.

My code compiles and I can launch everything. But when I try to drag files, the html doesn't respond to the files I drag over the zone. I have the feeling that I am missing something obvious which prevents dropzone.directive from getting activated.

How come directive.ts doesn't seem to get triggered?

My problem can easily be reproduced: You can just copy and paste the code below, there are no special imports required.

My dropzone.directive.ts reads as follows:

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

@Directive({
  selector: '[Dropzone]'
})
export class DropzoneDirective {
  @Output() onFileDropped = new EventEmitter<any>();

  @HostBinding('style.opacity') private opacity = '1';
  @HostBinding('style.border') private border = 'none';

  @HostListener('dragover', ['$event']) public onDragOver(evt: any): any {
    evt.preventDefault();
    evt.stopPropagation();
    this.opacity = '0.8';
    this.border = 'dotted 2px #FF4D2A';
  }

  @HostListener('dragleave', ['$event']) public onDragLeave(evt: any): any {
    evt.preventDefault();
    evt.stopPropagation();
    this.opacity = '1';
    this.border = 'none';
  }

  @HostListener('drop', ['$event']) public ondrop(evt: any): any {
    evt.preventDefault();
    evt.stopPropagation();
    this.opacity = '1';
    this.border = 'none';
    const files = evt.dataTransfer.files;
    if (files.length > 0) {
      this.onFileDropped.emit(files);
    }
  }
  

}

Then I declare my directive in the app.module:

import { DropzoneDirective } from './dropzone.directive';

@NgModule({
  declarations: [
    .....,
    DropzoneDirective
  ],
  imports: [
     ...,
    ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

My component.ts:


export class WebshopOverzichtComponent implements OnInit {
  allFiles: File[] = [];

  constructor() { }

  droppedFiles(allFiles: File[]| any): void {
    const filesAmount = allFiles.length;
    for (let i = 0; i < filesAmount; i++) {
      const file = allFiles[i];
      this.allFiles.push(file);
    }
  }
}

And finally, my component.html:

<div class="container text-center mt-5">
    <h3 class="mb-5"> Drop zone Dragondrop </h3>
    <!-- Applying Directive -->
    <div class="dropzone" DropZone (onFileDropped)="droppedFiles($event)">
      <div class="text-center">
        Drop files here.<BR>
      </div>
    </div>
    <div class="file-table">
      <h3 class="m-3">List of Files</h3>
      <table class="table">
        <thead>
          <tr>
            <th scope="col">#</th>
            <th scope="col">File Name</th>
            <th scope="col">Size</th>
            <th scope="col">Type</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let file of allFiles; let i = index">
            <th scope="row">{{i+1}}</th>
            <td>{{file.name}}</td>
            <td>{{file.size}} Bytes</td>
            <td>{{file.type}}</td>
          </tr>
          <tr class="text-center" *ngIf="allFiles.length === 0">
            <td colspan="4"><strong>No files are uploaded</strong></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>

Solution

  • You don't even need to use directive. Just use the input tag, set it on type="file" and use annotation multipe. Set the opacity in css en enlarge the input. Use the function in ts that I gave. It will work as drag & drop zone. Make a seperate button to perform the upload.

                <div class="file-container">
                    <input class="file" type="file" multiple 
    (change)="onFileChange($event)" >
                  </div>
    
      <button class="btn btn-primary" [disabled]="allFiles.length==0" (click)="uploadFiles(product.webshopnaam)">Upload</button>
    

    css

     .file-container{
        width: 300px;
        border: 4px dotted black;
     }
    
     .file{
        opacity: 0;
        padding: 1rem;
        width: auto;
        height: 1rem;
     }
    

    ts

     allFiles: File[] = [];
    
    onFileChange(event:any) {
       
      for (var i = 0; i < event.target.files.length; i++) { 
          this.allFiles.push(event.target.files[i]);
      }
      this.allFiles.forEach(value => console.log(value.name));
    
    }
    
      async uploadFiles(naam:string):Promise<void>{
        this.productService.uploadImages(this.allFiles, naam);
      }
    onFileChange(event:any) {
       
      for (var i = 0; i < event.target.files.length; i++) { 
          this.allFiles.push(event.target.files[i]);
      }
      this.allFiles.forEach(value => console.log(value.name));
    
    }
    
      async uploadFiles(naam:string):Promise<void>{
        this.productService.uploadImages(this.allFiles, naam);
      }