'File upload in angular 11

I am starting on angular 11 and am trying to upload a file or multiple files as a post request to my back end API ( that I created in node.js ) Here is the code I used :

web-request.service.ts

import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
@Injectable({
  providedIn: 'root'
})
export class WebRequestService {
  readonly ROOT_URL;
  constructor(private http:HttpClient) {
    this.ROOT_URL= 'http://localhost:4000'
   }
   // observable
   get(url:string){
     return this.http.get(`${this.ROOT_URL}/${url}`)
   }
   post(url:string,payload:Object){
    return this.http.post(`${this.ROOT_URL}/${url}`,payload)
  }
  patch(url:string,payload:Object){
    return this.http.patch(`${this.ROOT_URL}/${url}`,payload)
  }
  delete (url:string){
    return this.http.delete(`${this.ROOT_URL}/${url}`)
  }
}

files.service.ts

import { Injectable } from '@angular/core';
import {WebRequestService} from "./web-request.service";

@Injectable({
  providedIn: 'root'
})
export class FilesService {

  constructor(private webReqService:WebRequestService) { }
  // file to upload should be passed
  postFile(fileToUpload: File): Observable<boolean> {
     // we want to send a post web request to upload the file

    const endpoint = '/upload-txt';
    const formData: FormData = new FormData();
    formData.append('fileKey', fileToUpload, fileToUpload.name);
    this.webReqService.post(endpoint, formData, { headers: yourHeadersConfig })
      .map(() => { return true; })
      .catch((e) => this.handleError(e));
}

  ReadTable(){
    this.webReqService.get('/read-table-csv')
  }
}

file-upload.component.html

<div class="file is-primary">
    <label class="file-label">
        <input
         type="file"
         id="file"
         (change)="handleFileInput($event.target.files)"
         />
         <span class="file-cta">
             <span class="file-icon">
                  <i class="fas fa-upload"></i>
             </span>
             <span class="file-label">Browse Files </span>
         </span>
     </label>
 </div>

file-upload.component.ts

import { Component, OnInit } from '@angular/core';
import {FilesService} from "../../files.service"
@Component({
  selector: 'app-files-upload',
  templateUrl: './files-upload.component.html',
  styleUrls: ['./files-upload.component.scss']
})
export class FilesUploadComponent implements OnInit {
  fileToUpload: File | null;
  files: File[] = [];
  constructor(private fileUploadService: FilesService) {

   }

  ngOnInit(): void {
  }
  handleFileInput(files: FileList) {
    this.fileToUpload = files.item(0);
}
  uploadFileToActivity() {
    this.fileUploadService.postFile(this.fileToUpload).subscribe(data => {
      // do something, if upload success
      }, error => {
        console.log(error);
      });
  }

  }
}

How do I fix it, and is there a way I can have the files upload as a drag and drop option as well ?



Solution 1:[1]

You can use drag/drop events. Here's how I did it in a custom Component:

HTML

 <div 
    id="drop-zone" 
    (drop)="onFileDrop($event)" 
    (dragover)="onDrag($event)" 
    (dragleave)="onDragEnd($event)" 
    (drop)="onDragEnd($event)">
    
    <strong class="message">{{_dropZoneMsg}}</strong>
    <input type="file" 
        [style.cursor]="_disabled?'auto':'pointer'"
        [accept]="_accept" 
        [disabled]="_disabled"
        [multiple]="_multiple" 
        (change)="onFileClick(uploadsInput.files)" 
        [title]="' '" 
        #uploadsInput>
        
</div>

TS

export class DropZoneComponent implements OnInit, OnChanges {

    /** What files to allow.  Default = '*' (All file types)*/
    @Input('accept') _accept = '*'
    /** Allow multiple files to be dropped. Default = true */
    @Input('multiple') _multiple = true
    /** All click to add files. Default = true */
    @Input('clickable') _clickable = true
    /** What to say before file is dragged over drop zone. Default = 'Click or Drag & Drop to add files' */
    @Input('dropZoneStartMsg') _dropZoneStartMsg = 'Click or Drag & Drop to add files'
    /** What to say when file is hovering over drop zone. Default =  'Drop sé' */
    @Input('dropZoneEndMsg') _dropZoneEndMsg = 'Drop sé'
    /** Disable drop and click - Default = false */
    @Input('disabled') _disabled = false

    /** Grab the dropped file*/
    @Output('fileDrop') _onFileDrop = new EventEmitter<Array<File>>()
    /** Grab ALL the files that were dropped SO FAR*/
    @Output('newFiles') _onNewFiles = new EventEmitter<Array<File>>()
    /** Let user know that something went wrong - eg. wrong file type*/
    @Output('error') _onError = new EventEmitter<string>()

    _dropZoneMsg: string
    files: File[] = []
    private _acceptableTypes: string[] = []

    //--------------------------------------------//

    constructor() { }//ctor

    //--------------------------------------------//

    ngOnChanges(changes: SimpleChanges): void {

      if (changes._accept) {

        this._acceptableTypes = this._accept
          .split(',')
          .map(ac => ac.trim().toLowerCase())

      }//if

      if (changes._dropZoneStartMsg)
        this.initializeDropZoneMessage()

    }//ngOnChanges

    //--------------------------------------------//

    ngOnInit() {

      // this.initializeDropZoneMessage()

    }//ngOnInit

    //--------------------------------------------//

    onFileDrop(ev: DragEvent) {


      ev.preventDefault();

      const newFiles: File[] = []

      // Prevent default behavior (Prevent file from being opened)
      ev.preventDefault();

      if (ev.dataTransfer.items) {

        const items = ev.dataTransfer.items

        // Use DataTransferItemList interface to access the file(s)
        for (let i = 0; i < items.length; i++) {
          // If dropped items aren't files, reject them
          if (items[i].kind !== 'file' || !this.isValidFile(items[i].getAsFile()))
            continue
          const file = items[i].getAsFile()
          newFiles.push(file)

        }//For

      } else {
        const files = ev.dataTransfer.files
        // Use DataTransfer interface to access the file(s)

        for (let i = 0; i < files.length; i++) {
          const file = files[i]
          if (!this.isValidFile(file))
            continue

          newFiles.push(file)
        }//For

      }//Else


      this.emitFiles(newFiles)

      // Pass event to removeDragData for cleanup
      this.removeDragData(ev)

    }//onFileDrop

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

    /** Called when drop zone is clicked */
    onFileClick(files: FileList) {

      if (!this._clickable)
        return

      const newFiles: File[] = []

      for (let i = 0; i < files.length; i++) {
        this.files.push(files[i])
        newFiles.push(files[i])
      }//for

      this.emitFiles(newFiles)

    }//onFileClick

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

    /** Prevent default behavior (Prevent file from being opened) */
    onDrag(ev: DragEvent) {

      if (this._disabled)
        return

      this._dropZoneMsg = this._dropZoneEndMsg
      ev.preventDefault()

    }//dragOverHandler

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

    /** Prevent default behavior (Prevent file from being opened) */
    onDragEnd(ev: DragEvent) {

      this._dropZoneMsg = this._dropZoneStartMsg

    }//dragOverHandler

    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //

    removeDragData(ev: DragEvent) {

      if (ev.dataTransfer.items)
        // Use DataTransferItemList interface to remove the drag data
        ev.dataTransfer.items.clear()
      else
        // Use DataTransfer interface to remove the drag data
        ev.dataTransfer.clearData()

    }//removeDragData

    //--------------------------------------------//

    initializeDropZoneMessage() {

      this._dropZoneMsg = this._dropZoneStartMsg

    }//initializeDropZoneMessage

    //--------------------------------------------//

    isValidFile(file: File): boolean {

      const fileType = file.type.trim().toLowerCase()
      console.log(fileType)

      const idx = this._acceptableTypes
        .findIndex(tp => tp === fileType)

      //-1 means it wasn't found
      const isValid = idx !== -1

      if (!isValid)
        this._onError.emit('Incorrect file type!')

      return isValid

    }//isValidFile

    //--------------------------------------------//

    emitFiles(newFiles: File[]) {

      if (this._disabled)
        return

      //Anything to emit?
      if (!newFiles?.length)
        return

      if (!this._multiple && newFiles.length > 1)
        return

      if (this._multiple)
        this.files = this.files.concat(newFiles)
      else
        this.files = newFiles

      this._onFileDrop.emit(newFiles)
      this._onNewFiles.emit(this.files)

    }//emitFiles


    //--------------------------------------------//

  }//Cls

Then use it like this:

<inigo-drop-zone
    [dropZoneStartMsg]="Click or Drag & Drop to add file.'" 
    [multiple]="false" 
    (fileDrop)="onFileDrop($event)" 
    [accept]="_myAcceptableTypes"
    (error)="onError($event)">
</inigo-drop-zone>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ShanieMoonlight