Angular 4 upload files with data and web api by drag & drop

angular2 file upload tutorial angular 2 upload file to server angular 2 file upload component angular 4 file upload angular2-image-upload example angular 2 image gallery example ng2 file upload formdata ng2 file upload additionalparameter angular2 multipart/form-data post

Upload file with data by clicking on button or by drag and drop, is the topic of this article. We will try to create an image gallery by uploading files and save data into database and images into a folder. We will try to create a component in a way that it can be used anywhere in entire application and configure properties like file size, type of files, single or multiple files, maximum number of files a user can upload at a time.

Create Angular 4 CLI application by using

ng new ImageGallery --ng4

Open Package.json and add bootstrap to design this project:

"bootstrap": "^3.3.7"
// Open ImageGallery folder and run
npm install
// Open Angular-cli.json file and add in styles
"styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
  ],

Let's see we want to give the feature to configure the control for different validation like

  • Number of files
  • File Extensions to be uploaded
  • Maximum size of a file

At the same time we want to pass some values, because when we will be uploading a file it should be linked with some records, for this say we want to use the project Id and Section Id, we can use any number of parameters

  • Project Id
  • Section Id

alt text

Create a new component names FileUpload.Component by using command:

ng generate component FileUpload

Open file file-upload.component.html and add following code in it

<div draggable="true" ngClass="{{dragAreaClass}}">
  <div class="row">
    <div class="col-md-12 text-center" >
      <a href="javascript:void(0)" (click)="file.click()" >
        Click to browse 
      </a>
      Or Drag & Drop to upload your files
      <input type="file" 
             #file 
             [multiple]="(maxFiles > 1)"
             (change) = "onFileChange($event)"
             style="display:none" />
    </div>
  </div>

</div>
<div class="row error" *ngIf="errors.length > 0">    
  <ul>
    <li *ngFor="let err of errors">{{err}}</li>
  </ul>
</div>  

This the entire html to make a component to upload file by clicking and by drag and drop.

Let's see what this code is:

  • draggable="true": make this div drag and dropable
  • ngClass="{{dragAreaClass}}": use class to change look and feel when use move file at the time of drag and drop
  • Given a reference name to the input #file. On clicking the button we are calling the click event of input type file.
  • (click)="file.click()": to click the browse button by program because we don't want to show the button
  • See there is input type file which is used to open the browse file but set property display: none to hide it.
  • [multiple]="(maxFiles > 1)": configurable, if max file to upload is more than one means user can upload multiple files otherwise only one file at a time.
  • (change) = "onFileChange($event)": When user will click and browse and select a file, this method will be called to validate and upload the files.
  • Next we used ngFor to show the list of error while uploading the file, it will show all the errors like max number of file allowed, file extension, file size etc.

file-upload.component.ts

We will see this file part by part, open this file and first import some decorators:

import { Component, OnInit, Input, Output, EventEmitter, HostListener } from '@angular/core';
import {FileService} from '../../services/file.service';
@Component({
  selector: 'app-file-upload',
  templateUrl: './file-upload.component.html'
})

We imported here everything we need to use for drag and drop, input parameter, output to notify the user the status of upload and register different listeners for drag and drop.

We need some variable for our upload component which we are going to add at the beginning of the class:

export class FileUploadComponent implements OnInit {
  errors: Array<string> =[];
  dragAreaClass: string = 'dragarea';
  @Input() projectId: number = 0;
  @Input() sectionId: number = 0;
  @Input() fileExt: string = "JPG, GIF, PNG";
  @Input() maxFiles: number = 5;
  @Input() maxSize: number = 5; // 5MB
  @Output() uploadStatus = new EventEmitter();

  constructor(private fileService: FileService) { }

  ngOnInit() { }
  ..........
}

Let's see why we need these variables, @Input is used to configure the component different properlies and @output to emit the upload status:

  • errors: List of error to show during upload
  • dragAreaClass: to change look and feel for upload area
  • projectId, sectionId: to save extra data for project id if any otherwise 0
  • fileExt: what type of file you want to use this component, default it will accept images with extension jpg, gif, png, it is not case sensitive
  • maxFiles: How many files a user can upload at a time, default is 5
  • maxSize: What should be the max file size allowed to upload, it is per file and not the total size when uploading multiple files, default is 5 MB
  • uploadStatus: event emitter to the page where we will use this component and give the status of upload, true or false, you can change according to your requirements
  • In constructor, we declare the object of our FileService to upload the file by using the Web API method which we will create latter.

Add the method file change to upload the file on clicking and selecting the file, we already added this method name in our html onFileChange($event)

onFileChange(event){
   let files = event.target.files; 
   this.saveFiles(files);
}

Drag and drop view and error view For now don't worry about the this.saveFiles method, we will add it latter in this article.

Now we will add different methods for drag and drop: onDragOver: add this method to change the css class when user drag over to drop area

@HostListener('dragover', ['$event']) onDragOver(event) {
    this.dragAreaClass = "droparea";
    event.preventDefault();
}

onDragEnter: add this method to change the css class when user enter to drop area

@HostListener('dragenter', ['$event']) onDragEnter(event) {
    this.dragAreaClass = "droparea";
    event.preventDefault();
}

onDragEnd: add this method to change the css class, it is called when drag operation is being ended

@HostListener('dragend', ['$event']) onDragEnd(event) {
    this.dragAreaClass = "dragarea";
    event.preventDefault();
}

onDragLeave: add this method to change the css class, it is called when goes out of the drag and drop area, so need to change the area to normal now.

@HostListener('dragleave', ['$event']) onDragLeave(event) {
    this.dragAreaClass = "dragarea";
    event.preventDefault();
}

onDrop: This is the event which is called when user drop files to our drag and drop area and now we need to check the dropped files and call the save method to save files

@HostListener('drop', ['$event']) onDrop(event) {   
    this.dragAreaClass = "dragarea";           
    event.preventDefault();
    event.stopPropagation();
    var files = event.dataTransfer.files;
    this.saveFiles(files);
}

Note: In all our drag & drop events we use the preventDefault, it is important otherwise file will try to open in the browser.

Save File method, we used here because in both type of upload, either by clicking and selecting or by drag and drop case we use the same method to upload the files:

saveFiles(files){
  this.errors = []; // Clear error
  // Validate file size and allowed extensions
  if (files.length > 0 && (!this.isValidFiles(files))) {
      this.uploadStatus.emit(false);
      return;
  }       
  if (files.length > 0) {
        let formData: FormData = new FormData();
        for (var j = 0; j < files.length; j++) {
            formData.append("file[]", files[j], files[j].name);
        }
        var parameters = {
            projectId: this.projectId,
            sectionId: this.sectionId
        }
        this.fileService.upload(formData, parameters)
            .subscribe(
            success => {
              this.uploadStatus.emit(true);
              console.log(success)
            },
            error => {
                this.uploadStatus.emit(true);
                this.errors.push(error.ExceptionMessage);
            }) 
    } 
}

This part of the code is main to upload the file with data, what this code will do:

  • First of all it will clear the error if there are any previous error
  • Check number of files and other validation which we will see next
  • If anything goes invalid, it will return, show the error, and notify the parent page by using uploadStatus emitter
  • If all goes well, try to add files to FormData data as an array
  • Create a variable parameters to pass extra data to the Service/Web API
  • Call our service upload method to upload files
  • Notify the status of upload to parent page

isValidFiles: This method will check the different types of validations like extension, size and number of files:

private isValidFiles(files){
   // Check Number of files
    if (files.length > this.maxFiles) {
        this.errors.push("Error: At a time you can upload only " + this.maxFiles + " files");
        return;
    }        
    this.isValidFileExtension(files);
    return this.errors.length === 0;
}

See first we check the number of files to upload is greater to the number of files allowed, then we use another method isValidFileExtension which check every file extension

private isValidFileExtension(files){
  // Make array of file extensions
  var extensions = (this.fileExt.split(','))
                  .map(function (x) { return x.toLocaleUpperCase().trim() });
  for (var i = 0; i < files.length; i++) {
      // Get file extension
      var ext = files[i].name.toUpperCase().split('.').pop() || files[i].name;
      // Check the extension exists
      var exists = extensions.includes(ext);
      if (!exists) {
          this.errors.push("Error (Extension): " + files[i].name);
      }
      // Check file size
      this.isValidFileSize(files[i]);
  }
}

First we converted the fileExt string to an array and then convert to lower case and then keep checking one by one all the file extensions. At the end we call another method isValidFileSize which will check for the file size

private isValidFileSize(file) {
      var fileSizeinMB = file.size / (1024 * 1000);
      var size = Math.round(fileSizeinMB * 100) / 100; // convert upto 2 decimal place
      if (size > this.maxSize)
          this.errors.push("Error (File Size): " + file.name + ": exceed file size limit of " + this.maxSize + "MB ( " + size + "MB )");
}

File size and file extension error with multiple file upload Before writing the service code in Angular, let's add Web API code first then we can come back and complete our service code.

We will upload the file in a folder and save record into database, here is the list of properties we will save into database, so create a table named "Files":

CREATE TABLE [Files](
    [Id] [int] NOT NULL PRIMARY KEY IDENTITY(1,1),
    [FileName] [varchar](200) NOT NULL,
    [FileSize] [int] NOT NULL,
    [ImagePath] [varchar](200) NOT NULL,
    [ThumbPath] [varchar](200) NOT NULL,
    [ProjectId] [int] NOT NULL,
    [SectionId] [int] NOT NULL
)

Here the complete code in Web API to save the file into folder as well as save into database

[HttpPost]
public async Task<HttpResponseMessage> Upload(int projectId, int sectionId)
{
    var status = new MyReponse();
    try
    {
        var context = HttpContext.Current.Request;
        if (context.Files.Count > 0)
        {
            var filesReadToProvider = await Request.Content.ReadAsMultipartAsync();
            var index = 0;
            foreach (var streamContent in filesReadToProvider.Contents)
            {
                var fileBytes = await streamContent.ReadAsByteArrayAsync();
                var file = new File();
                file.ProjectId = projectId;
                file.SectionId = sectionId;
                file.FileName = context.Files[index].FileName;
                file.FileSize = fileBytes.Length;
                file.ImagePath = String.Format("/UploadedFiles/{0}_{1}_{2}", projectId, sectionId, file.FileName);
                file.ThumbPath = String.Format("/UploadedFiles/{0}_{1}_th_{2}", projectId, sectionId, file.FileName);
                var img = Image.FromStream(new System.IO.MemoryStream(fileBytes));
                await SaveFiles(file, img);
                index++;
            }
            status.Status = true;
            status.Message = "File uploaded successfully";
            return Request.CreateResponse(HttpStatusCode.OK, status);
        }
    }
    catch (Exception ex)
    {
        status.Message = ex.Message;
    }
    return Request.CreateResponse(HttpStatusCode.OK, status);
}

Let's see this code and explanation

  • Accept two parameter project id and section id, you can add as many as you want
  • Check the number of files by using request, if there is any file, read it and save it
  • Loop through and keep saving one by one
  • Create file entity to save into database
  • convert file bytes into image to save
  • await SaveFiles(file, img): this is the method which save files two size 200 x 200 as thumbnail and 600 x 600 and save into a folder and finally save path and other detail into database.

I will upload this Web API file on github so you can easily create your Web API method.

To use this Web API method we need a service so create a folder "services" in src folder and create a service named FileService by using command:

ng generate service File

Add following code in it to save the file and get the uploaded file detail:

import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

@Injectable()
export class FileService {
    _baseURL: string = 'http://localhost:xxxx/api/fileupload/'
    constructor(private http: Http) { }

    upload(files, parameters){
        let headers = new Headers();
        let options = new RequestOptions({ headers: headers });
        options.params = parameters;
        return  this.http.post(this._baseURL + 'upload', files, options)
                 .map(response => response.json())
                 .catch(error => Observable.throw(error));

    }
    getImages(){
        return this.http.get(this._baseURL + "getimages")
                   .map(response => response.json())
                   .catch(error => Observable.throw(error));
    }
}

It has two method upload and getImages, let see these two methods in detail:

  • upload method accept two parameters files and extra parameters to save into database
  • Not I use the header to pass these extra parameters
  • We use here Http Post Method to save the files
  • Send files in body
  • Extra parameters are send via header
  • getImages is just retrieving all the files from database

We are done, let's add this component to the app.component.html to upload the file and use some hard coded value for project id and section id:

<div class="row">
  <div class="col-md-12">
    <app-file-upload 
      projectId="100" 
      sectionId="107" 
      maxFiles="4"
      maxSize="2"
      fileExt="JPG, GIF, PNG"
      (uploadStatus)="refreshImages($event)" >
    </app-file-upload>
  </div>
</div>

As you can see here everything is configurable so this component can be used for any number of files, any size of file, any type of files.

We use here refreshImages method to chech the upload status and refresh the images on the page.

refreshImages(status){
    if (status == true){
      console.log( "Uploaded successfully!");
      this.getImageData();
    }
}

How to show the images on the page

<div class="well">
  <img *ngFor="let img of images"
       [src]="'http://localhost:xxxx' + img.thumbPath"
      class="img-thumbnail"
      alt="">
</div>

In app.component.ts add code to get images getImageData() from the database by using Web API method, call this method OnInit and when we get notification from upload component that upload completed:

import { Component, OnInit } from '@angular/core';
import { FileService } from '../services/file.service';
@Component({....})
export class AppComponent implements OnInit {
  ....
  constructor(private fileService: FileService) { }
  ngOnInit(){ this.getImageData(); }
  getImageData(){
    this.fileService.getImages().subscribe(       
      data =>{ this.images = data.result},
      error => this.errorMessage = error
    )
  }
  ......
}

CSS used for this drag and drop and error:

.error{ color: #f00; }
.dragarea{
    font-size: 24px;
    border: 3px solid #bbb; 
    padding: 20px ;
    background-color: #fff;
    color: #bbb;
}
.droparea{
    font-size: 24px;
    border: 3px dashed #bbb; 
    padding: 20px ;
    background-color: #eff;
    color: #aaa;
}

Just uploaded the src folder from my Image Gallery application to github - please download from there for more detail, also added complete Web API controller with name FileUploadController.cs, hope it will help to understand both part.

Ali Adravi Having 13+ years of experience in Microsoft Technologies (C#, ASP.Net, MVC and SQL Server). Worked with Metaoption LLC, for more than 9 years and still with the same company. Always ready to learn new technologies and tricks.
  • angular2
  • angularjs
  • web-api
By Ali Adravi On 24 Sep, 17  Viewed: 40,971

Other blogs you may like

Angular component communication by using Input Output decorator

@Input and @Output decorator ar used to pass data/communicate between components, @input is used to pass data from parent to child component on the other hand @Output is use to notify the parent by using EventEmitter. Take the simple example of customers list parent page and add/edit child... By Ali Adravi   On 30 Sep 2017  Viewed: 2,107

Angular 2 CRUD with Web API 2

Angular 2 insert update, delete, search, sort feature we will try in this article which we will improve with dynamic component loading and image upload with drag and drop feature in coming articles with the same base code. We are going to use Angular-CLI 4 for this application. What we need to... By Ali Adravi   On 03 Sep 2017  Viewed: 25,716

Angular 2 Dynamically Add & remove Components

Angular 2 gives full control to add or remove component in other component programmatically by simple steps. In this article we are going to see how to add, remove experience component to any other component, we will also try to pass some values to component from parent to child component to see if... By Ali Adravi   On 27 Aug 2017  Viewed: 50,293

Angular 2 Accordion expand only one at a time

Createing accordion demo in Angular 2 is more than easy, Angular have rich of conditional features which we will see in this article, like how to apply more than one class on the basis of some conditional values. For this demo we are not goign to write some fancy CSS and animation but use the... By Ali Adravi   On 19 May 2017  Viewed: 11,878

Angular 2 Page Title Static and Dynamic with built-in Title Service

Angular 2 have a built-in service called Title, while in angularjs or angular 1 we were maintaining it through the routing or through the service. By using the Title service provided by Angular 2, in 2 minute every thing will be set without any complication, even we don't need to understand the... By Ali Adravi   On 14 Apr 2017  Viewed: 5,024