Softwareentwicklung Zürich & Deutschschweiz

How to create a custom styled file upload in angular 9

29.04.2021
When implementing web applications often we need to implement some kind of upload to allow the clients to upload files. There's a basic html file upload input element which looks a bit old-fashioned. In this blog post I'll show you how to implement a custom file upload in angular. Please not that I'm not gonna explain how to create an angular app but simply start from an existing app.

The basic HTML file upload

As already mentioned, there's a default file upload input element in HTML. As far as I know for several reasons this is the only possibility nowadays to access your computer's data and select a file which you want to upload from your browser.
The following few lines of code show how to implement such a control. This is inside an angular component. You can then add events that you want to implement to upload the file to your server.
<h1>Basic HTML File Upload</h1>
<input type="file">

How change the style of this element?

Since using the input element is the only possibility to upload files from your browser we are restricted to a few possibilities of customization. In this blog I'm gonna show it in an angular application but you can do almost the same in any other JavaScript based application.
Right now we want to implement something pretty cool which allows clients to drag and drop files to a pre-defined drop zone in the browser as well as beig able to pci ka file to upload. First of all we're going to add ng2-file-upload package which hepls us a lot in implementing our file upload. We can also do this stuff on our own but in this example I'm a lazy developer and so am going to use a package.
npm i ng2-file-upload@latest --save
In your module import the package
import { FileUploadModule } from 'ng2-file-upload';

imports: [
	FileUploadModule, ...
],	
I'm going to import some more stuff plus some material components since we're going to need it in our application
imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    FormsModule,
    ReactiveFormsModule,
    MatToolbarModule,
    MatButtonModule,
    FileUploadModule
],

Implement the client part

Now we've set up our application base so we're ready to implement our upload formular. First we'll create a form containing the drop zone and an file picker button.
<div id="direct_upload" ng2FileDrop [uploader]="uploader" (fileOver)="fileOverBase($event)" [ngClass]="{ 'nv-file-over': hasBaseDropZoneOver }">
	<h1>Upload File</h1>
	<p>
	Please select an image. You can also drag and drop an image file into the
	dashed area.
	</p>
	<form [formGroup]="imageForm">
		<div>
			<button type="button" mat-raised-button (click)="fileInput.click()">
			Choose File
			</button>
			<input hidden [uploader]="uploader" (change)="fileInput.value = ''" ng2FileSelect #fileInput type="file" id="file" multiple />
		</div>
	</form>
</div>
Right now our component.ts file looks like the following
import { Component, OnInit, Input } from "@angular/core";
import { FormGroup, FormBuilder } from "@angular/forms";
import { FileUploader } from "ng2-file-upload";

@Component({
  selector: "app-file-upload",
  templateUrl: "./file-upload.component.html",
  styleUrls: ["./file-upload.component.scss"]
})
export class FileUploadComponent implements OnInit {
  responses: any[];
  hasBaseDropZoneOver = false;
  uploader: FileUploader;
  imageForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.imageForm = this.formBuilder.group({});
  }

  ngOnInit(): void {}

  fileOverBase(e: any): void {
    this.hasBaseDropZoneOver = e;
  }
}
So here, we defined a drop container which allows the users to drag&drop their files to. It also contains a form where your clients can select files manually using the file picker. Here you can see the button simply triggers a click on the input field. If you don't want to implement drag and drop but simply style your upload control. You can skip the drop zone and stuff and simply add the form to your application. But currently it looks pretty basic.
We're going to style our drop zone, now. So we're going to add some css.
#direct_upload {
	padding: 20px;
	box-sizing: content-box;
	border-top: 1px solid #ccc;
	border-bottom: 1px solid #ccc;
	border: 4px dashed #ccc;
  }  

Handling file upload

So you're now able to select files or to drag&drop them to our drop zone. Let's go on defining what should happen when we drop or select any images.
Since we're using the uploader library this is pretty easy. In out ngOnInit function we initialize the upload logic.
ngOnInit(): void {
	this.initializeUploader();
}


initializeUploader(): void {
    //Create the file uploader, wire it to upload to your account
    const uploaderOptions: FileUploaderOptions = {
      url: 'http://localhost:59976/files',
      // Upload files automatically upon addition to upload queue
      autoUpload: true,
      // Use xhrTransport in favor of iframeTransport
      isHTML5: true,
      // Calculate progress independently for each uploaded file
      removeAfterUpload: true,
      // XHR request headers
      headers: [
        {
          name: "X-Requested-With",
          value: "XMLHttpRequest"
        },
        {
          name: "Access-Control-Allow-Origin",
          value: "http://localhost:4200"
        }
      ]
    };
    this.uploader = new FileUploader(uploaderOptions);
    this.uploader.onBuildItemForm = (fileItem: any, form: FormData): any => {
      return { fileItem, form };
    };
  }
We are calling an ASP.NET WebAPI running on localhost and port 59976. We also need to add CORS policies to make it work. In the following I'm going to show you the controller action as well as the CORS configuration. Please note that you should restrict the CORS policies more than I do in the example.
[Route("[controller]")]
[ApiController]
public class FilesController : ControllerBase
{
	[HttpPost()]
	public async Task<IActionResult> OnPostUploadAsync()
	{
		var files = Request.Form.Files;
		long size = files.Sum(f => f.Length);

		foreach (var formFile in files)
		{
			if (formFile.Length > 0)
			{
				var filePath = Path.GetTempFileName();

			}
		}

		return Ok(new { count = files.Count, size });
	}
}
public void ConfigureServices(IServiceCollection services)
{
	services.AddCors(c =>
		c.AddPolicy("DemoAPIPolicy", policy => policy.AllowAnyHeader().WithMethods("POST", "OPTIONS").WithOrigins("http://localhost:4200").AllowCredentials()));
	services.AddControllers();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	app.UseCors("DemoAPIPolicy");
	...
}

Handle upload status

So right now it's possible to upload files to your API. Next we want to show the users an indicator about the upload status. We're going to add a simple status container which shows a progress bar and some more information about what's going on. First let's add our HTML. This is pretty simple. It shows a progress indicator, a progress bar and on the bottom an information box with some extra information.
<h2>Upload Status</h2>
<div class="file" *ngFor="let response of responses; let i = index">
  <h3>{{ response.file.name }}</h3>
  <div class="status" *ngIf="response.progress < 100">
    Uploading... {{ response.progress }}%
    <div *ngIf="!response.status">In progress</div>
    <div class="status-code" *ngIf="response.status">
      Upload completed with status code {{ response.status }}
    </div>
  </div>
  <div class="progress-container">
    <div class="progress-bar">
      <div
        class="progress"
        role="progressbar"
        [style.width.%]="response.progress"
      ></div>
    </div>
  </div>
  <div class="info">
    <div *ngIf="response.progress && response.progress < 100">
      Still working...
    </div>
    <div *ngIf="(response.progress && response.progress == 100)">
      Upload was successful
    </div>
    <div *ngIf="response.status && response.status !== 200">
      Ooops something went wrong
    </div>
  </div>
</div>
Now we need to file the responses array. Therefor we attach the onProgressItem event to our uploader and implement the response handling.
this.uploader.onProgressItem = (fileItem: any, progress: any) =>
this.upsertResponse({
	file: fileItem.file,
	progress,
	data: {},
});
}

upsertResponse = (fileItem: any) => {
const existingId: any = this.responses.reduce(
	(prev: any, current: any, index: number) => {
		if (
			current.file.name === fileItem.file.name &&
			!current.status
		) {
			return index;
		}
		return prev;
	},
	-1
);
if (existingId > -1) {
	this.responses[existingId] = Object.assign(
		this.responses[existingId],
		fileItem
	);
} else {
	this.responses.push(fileItem);
}
};
We also need to initialize the responses array from our constructor.
constructor(private formBuilder: FormBuilder) {
    this.imageForm = this.formBuilder.group({});
    this.responses = [];
  }
Last but not least, we're going to style our progress bar for the image upload. Usually you will center the containers using flex layout or bootstrap grid or any but for simplicity I just did it old-school.
.progress-container {
	width: 100vw;
	height: 12px;
	position: relative;
  
	.progress-bar {
	  width: 100px;
	  height: 12px;
	  position: absolute;
	  top: 50%;
	  right: 50%;
	  transform: translate(50%, -50%);
  
	  .progress {
		height: 12px;
		background-color: #b5d3e7;
		width: 0;
	  }
	}
  }
The output will look as follows
Finally we achieved an upload with progress information and a progress bar. Pretty easy isn't it?
If you have any questions or improvements please send me an email to
© 2016-2021 OneCodex GmbH – All rights reserved