Angular Application Server Cross Origin File Upload Express Angular Cors File Upload
Creating a File Upload Component in Angular (Including Backend)
Creating file-upload components tin can be quite difficult.
Not only because you need to deal with files in JavaScript. Likewise, because yous need an API to test against before you tin can really start coding.
In this tutorial, we will learn how to practice both.
This is an affiliate link. We may receive a commission for purchases made through this link.
Outset, we volition create a uncomplicated node.js express server with simply one road to have file uploads.
Later, we will create an angular application from scratch and build a beautiful file-upload component using the angular material ui-component-library.
Hither is what the final result will look like:
Ready?
Let'due south starting time coding!
Creating the Express server for the file upload
Before nosotros can start writing our angular file-uploader, we demand to have an API to upload to right?
In this tutorial, we are going to create a elementary node.js server using express, that allows files to be uploaded in the multipart-format.
Setting up a new project
To fix a new server project, create a new directory and initialize a new project using the
command. It will ask you for some information. You lot can pass it in or but hit enter.
We will besides need ii JavaScript files. Create the following files in the projection directory:
server.js upload.js
External dependencies
Our server will have 3 external dependencies.
The offset one is evidently limited. Limited is a framework, that makes creating API very piece of cake. To install limited, you can employ this command
npm install express --salve
Because nosotros want to access the API from an angular application, the server needs to allow cross-origin requests. Therefore we are going to use a simple module called CORS. To install it, type:
Likewise, limited itself is not very proficient at agreement forms. Because we will be uploading our files in the multipart/form-data format, nosotros need to exist able to parse this format. The library "formidable" does this and is quite easy to apply. Install formidable using this command:
npm install formidable --relieve
Setting up a basic Express server
Get-go, we need to create a bones limited server in the server.js file. This part looks always the same and consists of only 3 lines.
const limited = require('express') const server = limited() server.heed(8000, () => { console.log('Server started!') })
This is already a working express server. Although it is not doing anything useful, we could start it using the
command.
Enabling CORS
To be able to access our API from an angular application, nosotros demand to enable cors. To practice so, we first need to crave CORS.
const cors = require('cors')
Next, we configure it to let any domain by creating an pick-object.
var corsOptions = { origin: '*', optionsSuccessStatus: 200, }
Finally, we tell limited to use the cors-middleware with our configuration.
server.use(cors(corsOptions))
Registering the upload route
Afterward, we demand to configure a route for our file upload.
For that, nosotros crave our upload.js file and annals a road with the HTTP-post method.
const upload = crave('./upload') server.mail('/upload', upload)
Now we are done with the server.js file. Information technology should await like this by now:
const express = require('limited') const upload = require('./upload') const cors = crave('cors') const server = express() var corsOptions = { origin: '*', optionsSuccessStatus: 200, } server.apply(cors(corsOptions)) server.post('/upload', upload) server.mind(8000, () => { panel.log('Server started!') })
Implementing the upload road
Allow's start implementing the upload functionality. We volition place information technology into the upload.js file.
First, we need to crave a class called IncomingForm from the "formidable" library.
const IncomingForm = require('formidable').IncomingForm
Subsequently that, we need to export the callback office, we are using in our server.js to register the road. This function will be called, every time somebody hits the '/upload' URL.
This is an chapter link. We may receive a commission for purchases made through this link.
This callback gives usa a request-object (req), that stores data nigh the request that hit the route.
We likewise become a response-object (res). We can use this object, to transport dorsum a response.
module.exports = function upload(req, res) {}
Within that method, nosotros create a new form.
var form = new IncomingForm()
We then register callbacks on that form. The first callback is called for every file in the form:
form.on('file', (field, file) => { // Exercise something with the file // e.grand. relieve information technology to the database // you can access it using file.path })
The uploaded files are stored in a temporary directory somewhere on your machine. To practice something with them, you can copy them from there using the node.js file-arrangement API.
The second callback is called when the form is completely parsed. In this case, we want to send dorsum a success condition code.
form.on('end', () => { res.json() })
We then trigger the parsing of the form using:
form.parse(req)
That's all we volition do for the upload functionality. It is not production ready, merely it will help us to test our upload-component of the angular awarding we actually want to build.
Hither is the consummate upload.js file:
const IncomingForm = require('formidable').IncomingForm module.exports = office upload(req, res) { var class = new IncomingForm() class.on('file', (field, file) => { // Do something with the file // e.g. salvage it to the database // you lot tin admission it using file.path }) form.on('end', () => { res.json() }) form.parse(req) }
Creating a new Angular file upload project
At present that we have a working API we tin code against, we can start creating the actual file-uploader.
For that, we first demand to create a new angular project. Nosotros are going to utilise the athwart-cli for this project. Create a new awarding past opening a command prompt at the desired location and type:
External dependencies
Because nosotros will need some complex ui-elements such as modal-windows, I decided to use the angular material library for this project. To install this library, employ this command:
npm install --save @angular/fabric @athwart/cdk
To make the css of this module bachelor in our app, nosotros need to import it into our app's global way.css file:
@import '~@athwart/material/prebuilt-themes/indigo-pink.css';
Also, we are using a flexbox-design for this. To brand flexbox a trivial chip easier to use with athwart there is a library called "@athwart/flex-layout". Install the library like this:
npm install --save @angular/flex-layout
Creating a feature module
To make our desired file-upload component equally re-usable as possible, I decided to bundle it into a separate feature module.
To create this module, but utilize this command:
ng generate module upload
Next, we will need to import a lot of external modules into this new module. For instance, we need to import all the angular material ui-components nosotros are going to utilise.
Here is how this looks like:
import { NgModule } from '@athwart/core' import { CommonModule } from '@angular/common' import { UploadComponent } from './upload.component' import { MatButtonModule, MatDialogModule, MatListModule, MatProgressBarModule, } from '@angular/material' import { BrowserAnimationsModule } from '@athwart/platform-browser/animations' import { FlexLayoutModule } from '@angular/flex-layout' import { HttpClientModule } from '@angular/mutual/http' @NgModule({ imports: [ CommonModule, MatButtonModule, MatDialogModule, MatListModule, FlexLayoutModule, HttpClientModule, BrowserAnimationsModule, MatProgressBarModule, ], declarations: [UploadComponent], exports: [UploadComponent], }) consign grade UploadModule {}
Notice, that we are besides exporting the UploadComponent, to make it bachelor outside of this module.
The file upload service
Before we can create the visuals of our file-uploader, we first need to implement the upload logic. This logic will be placed in the UploadService.
Create that service inside of the upload directory. Just employ this command:
ng generate service upload/upload
Within of that service, nosotros need to use the HttpClient, so we request it using dependency injection. Also, the service will comprise only one method chosen "upload".
import { Injectable } from '@angular/core' import { HttpClient, HttpRequest, HttpEventType, HttpResponse, } from '@athwart/common/http' import { Field of study } from 'rxjs/Subject area' import { Observable } from 'rxjs/Observable' const url = 'http://localhost:8000/upload' @Injectable() export course UploadService { constructor(private http: HttpClient) {} public upload( files: Set<File> ): { [key: string]: { progress: Observable<number> } } {} }
This upload method will return a map of progress objects. One for every file to upload. This object contains an appreciable of type number because it contains the progress of the upload in percent.
public upload(files: Set<File>): { [key: string]: { progress: Observable<number> } } { // this will exist the our resulting map const status: { [key: string]: { progress: Observable<number> } } = {}; files.forEach(file => { // create a new multipart-form for every file const formData: FormData = new FormData(); formData.append('file', file, file.name); // create a http-post asking and pass the form // tell it to report the upload progress const req = new HttpRequest('POST', url, formData, { reportProgress: true }); // create a new progress-subject field for every file const progress = new Discipline<number>(); // ship the http-request and subscribe for progress-updates this.http.request(req).subscribe(effect => { if (event.blazon === HttpEventType.UploadProgress) { // calculate the progress per centum const percentDone = Math.circular(100 * effect.loaded / event.total); // pass the pct into the progress-stream progress.next(percentDone); } else if (event instanceof HttpResponse) { // Close the progress-stream if we get an reply form the API // The upload is complete progress.consummate(); } }); // Salvage every progress-observable in a map of all observables status[file.name] = { progress: progress.asObservable() }; }); // return the map of progress.observables return condition; }
Inside of the upload-method, we package every file into a form, create an HTTP-post-request and send away that request with the form as payload. We and then mind to the progress of every file-upload, calculate the upload-percentage and laissez passer it to the progress-stream of that file. Every file has a progress-observable that is returned in a map.
Finally, we demand to provide that service in our upload-module.
The file upload dialog
Our UploadComponent will only consist of a single push. This push button volition then open a dialog to upload the files. In this chapter, we are going to create this dialog.
Using angular textile, a dialog is just a component. So let's create this DialogComponent:
ng generate component upload/dialog
Adding files
The first matter we need to practise is to add together a file input element to our component.
<input blazon="file" #file mode="display: none" (change)="onFilesAdded()" multiple />
This input chemical element is the but manner to trigger a file-option card of the operating system. But because it is quite ugly, we are going to hibernate it using "display: none". We then trigger this input using a click-event from our component-logic.
To do that, we demand a reference to it in our component.ts. For that, we are using the ViewChild directive. Nosotros will also demand a identify to store the files we desire to upload.
For that, we create a Fix of Files.
import { Component, OnInit, ViewChild } from '@angular/cadre' import { MatDialogRef } from '@athwart/textile' import { UploadService } from '../upload.service' import { forkJoin } from 'rxjs/appreciable/forkJoin' @Component({ selector: 'app-dialog', templateUrl: './dialog.component.html', styleUrls: ['./dialog.component.css'], }) export class DialogComponent { @ViewChild('file') file public files: Set<File> = new Fix() }
Nosotros can then apply it to open a file-option-menu by emulating a click:
addFiles() { this.file.nativeElement.click(); }
In our template, we have divers that the file-selection-menu should call a method chosen "onFiledAdded" in one case the file-selection is complete. In that method, we need to collect the files from the native HTML element and store them in our fix.
onFilesAdded() { const files: { [primal: cord]: File } = this.file.nativeElement.files; for (allow key in files) { if (!isNaN(parseInt(key))) { this.files.add(files[cardinal]); } } }
State
Nosotros will also need some land variables in the hereafter, so we should add them to our DialogComponent.
progress canBeClosed = truthful primaryButtonText = 'Upload' showCancelButton = true uploading = false uploadSuccessful = false
DialogRef
Adjacent, we demand some control over the dialog itself. For example, nosotros want to close the dialog from the dialog component. To do that, nosotros need to request the dialog reference via dependency injection in the DialogComponents' constructor. We likewise demand to UploadService we have created earlier, then we asking information technology, as well.
constructor(public dialogRef: MatDialogRef<DialogComponent>, public uploadService: UploadService) {}
Endmost the dialog & uploading the files
Subsequently, we have a expect at the "closeDialog" method. We will be calling this method when the OK button of our dialog is pressed (it doesn't exist however). Depending on which land nosotros are in, we want the push button to acquit differently.
When the state of the component is "uploadSuccessful" nosotros just want to close the dialog. Else, we want to start the upload of the files.
closeDialog() { // if everything was uploaded already, just close the dialog if (this.uploadSuccessful) { return this.dialogRef.close(); } // set the component country to "uploading" this.uploading = truthful; // start the upload and save the progress map this.progress = this.uploadService.upload(this.files); // catechumen the progress map into an array let allProgressObservables = []; for (let key in this.progress) { allProgressObservables.button(this.progress[cardinal].progress); } // Accommodate the state variables // The OK-button should take the text "End" now this.primaryButtonText = 'Terminate'; // The dialog should not exist closed while uploading this.canBeClosed = false; this.dialogRef.disableClose = true; // Hide the cancel-button this.showCancelButton = simulated; // When all progress-observables are completed... forkJoin(allProgressObservables).subscribe(stop => { // ... the dialog tin be closed again... this.canBeClosed = truthful; this.dialogRef.disableClose = fake; // ... the upload was successful... this.uploadSuccessful = truthful; // ... and the component is no longer uploading this.uploading = false; }); }
The full angular template
Here is the template of the dialog. It contains a push to add together new files, a chief button (Upload/Terminate) and a abolish button. Depending on the state of the component, these buttons tin can exist disabled.
The principal function of the dialog is the list of all files to upload. It contains the names of the files.
This is an affiliate link. We may receive a commission for purchases made through this link.
Once the files are uploading, a progress-bar for each file appears. This progress bar is using the progress-observable of each file.
<input blazon="file" #file style="brandish: none" (alter)="onFilesAdded()" multiple /> <div class="container" fxLayout="column" fxLayoutAlign="infinite-evenly stretch"> <h1 mat-dialog-title>Upload Files</h1> <div> <button [disabled]="uploading || uploadSuccessful" mat-raised-push color="primary" class="add-files-btn" (click)="addFiles()" > Add Files </button> </div> <!-- This is the content of the dialog, containing a list of the files to upload --> <mat-dialog-content fxFlex> <mat-list> <mat-list-particular *ngFor="permit file of files"> <h4 mat-line>{{file.name}}</h4> <mat-progress-bar *ngIf="progress" way="determinate" [value]="progress[file.name].progress | async" ></mat-progress-bar> </mat-listing-item> </mat-list> </mat-dialog-content> <!-- This are the deportment of the dialog, containing the primary and the cancel button--> <mat-dialog-actions class="actions"> <button *ngIf="showCancelButton" mat-button mat-dialog-close>Cancel</push> <push mat-raised-button color="primary" [disabled]="!canBeClosed" (click)="closeDialog()" > {{primaryButtonText}} </button> </mat-dialog-actions> </div>
The CSS styles
Here are the styles for the DialogComponent. I don't think in that location is caption needed.
.add-files-btn { float: correct; } :host { height: 100%; display: flex; flex: one; flex-management: cavalcade; } .actions { justify-content: flex-finish; } .container { pinnacle: 100%; }
Adding the DialogComponent equally EntryComponent
For our dialog to work properly, we need to add it to the entry-components of our upload-module:
import { NgModule } from '@angular/core' import { CommonModule } from '@athwart/common' import { UploadComponent } from './upload.component' import { MatButtonModule, MatDialogModule, MatListModule, MatProgressBarModule, } from '@angular/material' import { DialogComponent } from './dialog/dialog.component' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { FlexLayoutModule } from '@angular/flex-layout' import { UploadService } from './upload.service' import { HttpClientModule } from '@angular/mutual/http' @NgModule({ imports: [ CommonModule, MatButtonModule, MatDialogModule, MatListModule, FlexLayoutModule, HttpClientModule, BrowserAnimationsModule, MatProgressBarModule, ], declarations: [UploadComponent, DialogComponent], exports: [UploadComponent], entryComponents: [DialogComponent], // Add the DialogComponent as entry component providers: [UploadService], }) export class UploadModule {}
Modifying the Upload-Component
Finally, we need to modify the upload-component to trigger our new dialog. For that nosotros create a unproblematic push button within of the template of the component:
<button mat-raised-button (click)="openUploadDialog()">Upload</button>
Adjacent, we implement the method we are referencing in that location in our component. Simply first, we need to asking the MatDialog-service via dependency injection. This service allows us to open up our DialogComponent.
All we demand to exercise and so is to telephone call the open-method of this server, passing in our DialogComponent and the desired size of the dialog.
import { Component } from '@angular/core' import { MatDialog } from '@angular/material' import { DialogComponent } from './dialog/dialog.component' import { UploadService } from './upload.service' @Component({ selector: 'app-upload', templateUrl: './upload.component.html', styleUrls: ['./upload.component.css'], }) export class UploadComponent { constructor(public dialog: MatDialog, public uploadService: UploadService) {} public openUploadDialog() { permit dialogRef = this.dialog.open up(DialogComponent, { width: 'fifty%', summit: '50%', }) } }
That's it. We now have a working file-uploader.
Using the Upload-Component
All that's left to exercise, is to import the upload-module into our app-module and add the component to the app-component.
import { BrowserModule } from '@angular/platform-browser' import { NgModule } from '@athwart/core' import { AppComponent } from './app.component' import { UploadModule } from './upload/upload.module' @NgModule({ declarations: [AppComponent], imports: [BrowserModule, UploadModule], providers: [], bootstrap: [AppComponent], }) consign course AppModule {}
And finally using the component in the app-component:
<app-upload></app-upload>
Conclusion
In this tutorial, we learned how to set up up a node.js server-application from scratch and created a very bones file-upload route.
We likewise created an athwart file-upload component and styled information technology using the angular cloth UI-components.
This is an chapter link. We may receive a committee for purchases fabricated through this link.
You lot can find the full source code at the corresponding GitHub repository.
I hope you enjoyed this post.
If y'all did delight striking the share buttons beneath and aid other people building their own file-upload-components, as well.
Have a fantastic 24-hour interval!
Source: https://malcoded.com/posts/angular-file-upload-component-with-express/
0 Response to "Angular Application Server Cross Origin File Upload Express Angular Cors File Upload"
Post a Comment