Skip to content Skip to sidebar Skip to footer

Angular 7 Upload File Node Js Example

    How to Transfer Files and Data Between Athwart Clients and Node.js Backends

    Having a shared codebase for both the server-side and browser-side code of an Athwart application aids the maintainability of a project. You can exercise that with Angular Universal and Node.js using the server-side rendering (SSR) concept. You tin can even use SSR to deeply laissez passer data, including files, between the application server (Node.js) and the Angular application running on it.

    This post volition show you how to create an application for uploading, storing, managing, and downloading files from a Node.js server using a single codebase. When you finish this project you'll be able to:

    • Create an Angular application
    • Set up server-side rendering with Angular Universal and Node.js
    • Implement file transfer operations
    • Create a dynamic, in-memory list of files
    • Pass information about storage content between the server application and the JavaScript used by Angular for SSR

    To attain the tasks in this post yous will need the following:

    • Node.js and npm (The Node.js installation will as well install npm.)
    • Angular CLI

    These tools are referred to in the instructions, simply are not required:

    • Git

    To acquire near effectively from this post you lot should take the following:

    • Working knowledge of TypeScript and the Angular framework
    • Familiarity with Angular observables and dependency injection
    • Some exposure to Node.js

    You tin learn more about server-side rendering (SSR) in a previous Twilio web log postal service.

    At that place is a companion repository for this post available on GitHub.

    Create the project and components and service files

    In this step yous will implement a first "draft" of the awarding. You volition create a class which will be used for uploading files to the server and you will create an in-retentiveness list of uploaded files. As always, y'all demand to commencement by initializing the project.

    Go to the directory under which y'all'd like to create the project and enter the following control line instructions to initialize the project and add Angular Forms:

                      ng new athwart-and-nodejs-information --way css --routing false cd angular-and-nodejs-data/ npm install @athwart/forms                                  

    Execute the following command line instruction to create the FileService class:

    Execute the following commands to create the FileUploaderComponent and FileListComponent classes:

                      ng g c fileUploader --skipTests ng g c fileList --skipTests                                  

    Be certain to carefully notation the casing of the component names.

    Create the file service

    The initial implementation of the FileService will exist a temporary one that will enable users to add and remove files from a list, but it won't actually motility the files anywhere. It connects the file list and file uploader components and maintains the file list but, every bit you can see from the code below, information technology doesn't have upload or download functionality.

    Replace the contents of the src/app/file.service.ts file with the following TypeScript code:

                      import { Injectable } from '@angular/cadre'; import { BehaviorSubject, Subject area, Observable } from 'rxjs';  @Injectable({  providedIn: 'root' }) export class FileService {  individual fileList: string[] = new Array<string>();  private fileList$: Subject<string[]> = new Subject<string[]>();   constructor() { }   public upload(fileName: cord, fileContent: cord): void {    this.fileList.push(fileName);    this.fileList$.next(this.fileList);  }   public download(fileName: cord): void {   }   public remove(fileName): void {    this.fileList.splice(this.fileList.findIndex(name => name === fileName), i);    this.fileList$.next(this.fileList);  }   public list(): Observable<string[]> {    return this.fileList$;  }   private addFileToList(fileName: string): void {    this.fileList.push(fileName);    this.fileList$.next(this.fileList);  } }                                  

    Create the file uploader component

    The user interface for the file uploader component volition be based on a grade, so it'southward necessary to import the ReactiveFormsModule into the main Angular AppModule.

    Add together the post-obit import statement to the src/app/app.module.ts file:

                      import { ReactiveFormsModule } from '@angular/forms';                                  

    Modify the imports: section of the src/app/app.module.ts file to include ReactiveFormsModule:

                                          imports: [    BrowserModule,    ReactiveFormsModule  ],                                  

    Implementation of the FileUploaderComponent begins with a template the user tin apply to cull a file to upload.

    Replace the contents of the src/app/file-uploader/file-uploader.component.html file with the following HTML markup:

                      <h1>Upload file</h1> <form [formGroup] = "formGroup" (ngSubmit)="onSubmit()">  <input blazon="file" (change)="onFileChange($event)" />  <input type="submit" [disabled]="formGroup.invalid" value="upload" /> </form>                                  

    Implement the logic for uploading files in the FileUploaderComponent class.

    Replace the contents of the src/app/file-uploader/file-uploader.component.ts file with the following TypeScript code:

                      import { Component, OnInit } from '@angular/cadre'; import { FormBuilder, Validators } from '@angular/forms'; import { FileService } from '../file.service';  @Component({  selector: 'app-file-uploader',  templateUrl: './file-uploader.component.html',  styleUrls: ['./file-uploader.component.css'] }) export class FileUploaderComponent {   public formGroup = this.fb.grouping({    file: [null, Validators.required]  });   private fileName;   constructor(private fb: FormBuilder, private fileService: FileService) { }   public onFileChange(event) {    const reader = new FileReader();     if (event.target.files && upshot.target.files.length) {      this.fileName = event.target.files[0].name;      const [file] = effect.target.files;      reader.readAsDataURL(file);           reader.onload = () => {        this.formGroup.patchValue({          file: reader.consequence        });      };    }  }   public onSubmit(): void {    this.fileService.upload(this.fileName, this.formGroup.get('file').value);  } }                                  

    Notation that the onFileChange() method is spring to the (change) action of the input blazon="file" element of the HTML form. Also notation that the patchValue method of the formGroup object is used to provide Athwart with the contents of reader so information technology can proceed with the validation of the form.

    When the course is submitted the onSubmit() event fires and uploads the named file to fileService, where the file list is updated.

    Create the file listing component

    The FileListComponent form implements methods for retrieving list of files from the FileService. Information technology also provides download and remove operations that can be performed on the listed files.

    Replace the contents of the src/app/file-list/file-list.component.ts file with the following TypeScript lawmaking:

                      import { Component, OnInit } from '@athwart/core'; import { FileService } from '../file.service'; import { Observable } from 'rxjs';  @Component({  selector: 'app-file-list',  templateUrl: './file-list.component.html',  styleUrls: ['./file-list.component.css'] }) export class FileListComponent {   public fileList$: Observable<cord[]> = this.fileService.list();   constructor(private fileService: FileService) { }   public download(fileName: string):  void {    this.fileService.download(fileName);  }   public remove(fileName: string):  void {    this.fileService.remove(fileName);  } }                                  

    The data in the fileList$ observable volition be displayed on a list that as well includes clickable commands for downloading and removing each file.

    Supersede the contents of the src/app/file-list/file-list.component.html file with the following HTML markup:

                      <h1>Your files</h1> <ul>  <li *ngFor="let fileName of fileList$ | async" >    {{fileName}}&nbsp;&nbsp;    <span (click)="download(fileName)">download</span>&nbsp;    <span (click)="remove(fileName)">remove</bridge>  </li> </ul>                                  

    The *ngFor loop iterates through the list of files from the fileList$ observable, which emits an array of strings. A <li> chemical element containing <span> elements spring to download() and remove() operations will exist created for each entry.

    CSS can be used to indicate that the commands contained in the spans are clickable.

    Insert the post-obit CSS code into the src/app/file-list/file-listing.component.css file:

                      span:hover {    cursor: pointer; }                                  

    The FileListComponent class and the FileUploaderComponent class have to be included in the main component of the application, AppComponent, to be rendered in the browser.

    Replace the contents of the src/app/app.component.html with the following HTML markup:

                      <app-file-list></app-file-listing> <app-file-uploader></app-file-uploader>                                  

    Test the basic application

    Execute the post-obit Athwart CLI command in angular-and-nodejs-data to build and run the application:

    Open a browser tab and navigate to http://localhost:4200. You should encounter an empty file list and a grade ready for user input, like the one shown below:

    Choose a suitable file and click the upload button. The proper name of the selected file should appear in the file list, equally in the example shown below:

    Attempt clicking download. You will come across that nada happens.

    Attempt clicking remove. The file name should be removed from the list.

    At this betoken the awarding enables users to select files and "upload" them, merely they are just "uploaded" as far every bit the list of files in memory on the client machine. Files tin as well be removed from the list in memory.

    This isn't very useful, but it's enough to bear witness you how the user interface and the file list work.

    If you lot want to catch upwardly to this step using the code from the GitHub repository, execute the post-obit commands in the directory where you'd similar to create the projection directory:

                      git clone https://github.com/maciejtreder/athwart-and-nodejs-data.git cd angular-and-nodejs-data git checkout step1 npm install                                  

    Save files on the server

    The side by side footstep is to transfer files to the server and shop them on deejay. You'll practice that by adding more functionality to the FileService class.

    Outset you demand to add the Node.js server to the project and create a folder dedicated to storing user uploads.

    In the angular-and-nodejs -data folder, execute the following instructions at the command line:

                      ng add @ng-toolkit/universal mkdir user_upload                                  

    Installing the @ng-toolkit/universal project added Angular Universal back up to the project with merely 1 command. Information technology too includes a Node.js dorsum cease and server-side rendering (SSR). You can read more about SSR in Athwart and its implications for search engine optimization (SEO) in this postal service.

    Implement RESTful API endpoints in the server code

    API endpoints volition provide file handling on the server, so at that place are a few modifications to make to the server.ts file. They include calculation fs module support (for manipulating the file system) and specifying a catalog in which to store data.

    Open up the server.ts file and find the following abiding declaration:

                                          const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');                                  

    Add the following constant declarations immediately after the line above:

                      const userFiles = './user_upload/'; const fs = crave('fs');                                  

    Implement the /upload endpoint, which will be consumed by the front-end application.

    In the server.ts file, find the following line of code:

                      app.set('views', './dist/browser');                                  

    Add the following TypeScript code to the server.ts file immediately post-obit the line above:

                      app.put('/files', (req, res) => {  const file = req.body;  const base64data = file.content.supervene upon(/^data:.*,/, '');  fs.writeFile(userFiles + file.name, base64data, 'base64', (err) => {    if (err) {      console.log(err);      res.sendStatus(500);    } else {      res.ready('Location', userFiles + file.name);      res.condition(200);      res.transport(file);    }  }); });                                  

    Because we are going to upload Base64 encoded data in the asking trunk, nosotros need to adjust the maximum body size.

    Well-nigh the top of the server.ts file, find the following line of lawmaking:

                      app.employ(bodyParser.json());                                  

    Supplant the line above with the following TypeScript code:

                      app.use(bodyParser.json({limit: '50mb'}));                                  

    Implement the /delete endpoint.

    Add the following TypeScript code to the bottom of the server.ts file:

                      app.delete('/files/**', (req, res) => {  const fileName = req.url.substring(7).replace(/%20/g, ' ');  fs.unlink(userFiles + fileName, (err) => {    if (err) {      console.log(err);      res.sendStatus(500);    } else {      res.status(204);      res.send({});    }  }); });                                  

    Implement the GET /files endpoint.

    Add the following line of TypeScript code to the bottom of the server.ts file:

                      app.use('/files', express.static(userFiles));                                  

    Using the limited.static method informs Node.js that every Become request sent to the /files/** endpoint should be treated as "static" hosting, served from the userFiles directory, user_upload.

    These RESTful API endpoints in the server can now exist consumed in the front end-stop Angular application.

    Supplant the contents of the src/app/file.service.ts file with the post-obit TypeScript code:

                      import { Injectable } from '@angular/cadre'; import { BehaviorSubject, Subject area, Appreciable } from 'rxjs'; import { finalize } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http';  @Injectable({  providedIn: 'root' }) export class FileService {  individual fileList: string[] = new Array<string>();  private fileList$: Subject<string[]> = new Subject<string[]>();  private displayLoader$: Subject field<boolean> = new BehaviorSubject<boolean>(fake);   constructor(private http: HttpClient) { }   public isLoading(): Appreciable<boolean> {    return this.displayLoader$;  }   public upload(fileName: string, fileContent: cord): void {    this.displayLoader$.next(true);    this.http.put('/files', {name: fileName, content: fileContent})    .pipe(finalize(() => this.displayLoader$.next(false)))    .subscribe(res => {      this.fileList.push(fileName);      this.fileList$.side by side(this.fileList);    }, mistake => {      this.displayLoader$.next(false);    });  }   public download(fileName: string): void {    this.http.get('/files/${fileName}', { responseType: 'blob'}).subscribe(res => {      window.open(window.URL.createObjectURL(res));    });  }   public remove(fileName): void {    this.http.delete('/files/${fileName}').subscribe(() => {      this.fileList.splice(this.fileList.findIndex(proper noun => proper noun === fileName), 1);      this.fileList$.next(this.fileList);    });  }   public listing(): Appreciable<string[]> {    return this.fileList$;  }   private addFileToList(fileName: string): void {    this.fileList.push(fileName);    this.fileList$.next(this.fileList);  } }                                  

    The code above fully implements the file operations to upload, download, and remove files. Information technology as well adds the isLoading() method, which returns an observable-emitting boolean value indicating if the action of uploading information is underway or non. The observable can exist used in the AppComponent class to inform the user about the condition of that action.

    Supplant the contents of the src/app/app.component.ts with the following TypeScript code:

                      import { Component } from '@athwart/core'; import { FileService } from './file.service'; import { Observable } from 'rxjs';  @Component({  selector: 'app-root',  templateUrl: './app.component.html',  styleUrls: ['./app.component.css'] }) export course AppComponent {  title = 'angular-and-nodejs-data';   public displayLoader: Observable<boolean> = this.fileService.isLoading();   constructor(individual fileService: FileService) {} }                                  

    When the value from the Observable indicates an upload is in progress the app will display the post-obit loader GIF (which is included in the GitHub repository):

    Add the following HTML markup to the bottom of the src/app/app.component.html file:

                      <img src="https://raw.githubusercontent.com/maciejtreder/angular-and-nodejs-information/step2/src/assets/loader.gif" *ngIf="displayLoader | async" />                                  

    Test uploading and downloading files

    Rebuild the application and check if the upload and download functions work properly.

    Execute the post-obit npm command line instructions in the athwart-and-nodejs-information directory:

                      npm run build:prod npm run server                                  

    Open a browser tab and navigate to http://localhost:8080. Choose a file and upload information technology.

    The file name should exist displayed in the file listing nether Your files and as well be nowadays in the user_upload directory.

    You should also be able to download the file by clicking download. Note that your browser may open the file in a new tab or window instead of downloading it, based on the file type and your browser settings. The following illustration demonstrates the consummate sequence:

    Click remove and verify the file name is removed from the list under Your files and the file itself is removed from the user_upload directory.

    If you want to grab up to this step using the code from the GitHub repository, execute the following commands in the directory where you'd like to create the projection directory:

                      git clone https://github.com/maciejtreder/angular-and-nodejs-data.git cd angular-and-nodejs-information git checkout step2 npm install                                  

    Retrieve and display a file listing

    You are near done. The awarding supports uploading a new file to the storage, retrieving it, and removing it. The problem occurs when a user navigates back to the application.

    You lot can simulate that behavior. If you still accept http://localhost:8080 opened in your browser, hit the refresh push. The list of files is gone! But they are still on the server in the user_upload directory.

    The next pace in this project is to implement a responsive list of files for the user_upload directory. The list shown in the browser window will be updated dynamically to reflect the contents of the directory and it volition reverberate the list of files in the directory when the awarding starts.

    It's possible to do that by adding some other REST endpoint to our server that returns a list of files. This would exist good solution when the dorsum-cease server code is running on a different automobile from the code that does the server-side rendering.

    But as long as the back-terminate code is running on the same server every bit the code that serves the front end, it doesn't make sense to execute the Angular Universal (server-side rendering) code and execute REST calls to the same machine. Instead, you can utilise the fs module to list all files in a given path.

    The previous post, Build Faster JavaScript Spider web Apps with Angular Universal, a TransferState Service and an API Watchdog, demonstrates how to implement isPlatformServer() and isPlatformBrowser() methods to determine which platform is executing the code. This project uses those functions as well.

    The previous post also shows how to share data between the server and the client with the TransferState object by injecting it into the AuthService grade. These methods help brand fs module functionality accessible to the customer-side code, even though the module itself tin can't be loaded in the browser. This project also utilizes that technique.

    The post-obit diagram shows the sequence of events:

    1. The user performs a Get / request to the server.
    2. Node.js receives the request.
    3. Node.js launches Angular and renders the view on the server.
    4. Information is stored in the TransferState registry.
    5. The server-side rendered view, including the browser-side JavaScript and TransferState registry, is passed to the browser and the Angular application is re-rendered in the browser.

    There is 1 more thing to consider here. You know that browsers will not allow JavaScript code to manipulate the file system for security reasons. The webpack JavaScript module bundling system used by the Angular CLI won't allow you lot to use the fs module for code built for the browser.

    Since this project has a single codebase for both platforms, webpack interprets it as being built for the browser—which information technology is, in part. But it needs fs to read the directory contents and manipulate files, so information technology needs a solution that will get effectually the prohibition on running fs in the browser.

    At this point you might think y'all need to create a carve up codebase just for the server-side code, giving you ii projects to maintain. But there is a technique which can enable you to maintain the single codebase and even so manipulate files from the Angular executed on the server.

    Angular has the ability to inject values and references exterior the "Athwart sandbox". You lot tin can pass a reference to the Node.js part to the angular-side code and execute it from in that location.

    Have a look at the following diagram:

    1. The browser sends a GET / request to the server.
    2. Server fires Angular to render the view and calls the constructor() of the FileService.
    3. The constructor uses the isPlatformServer() method to make up one's mind if it is beingness executed in Node.js on the server. If so, the constructor calls the listFiles() method injected into FileService as a callback. The listFiles() method provides the current list of the contents of the user_upload directory, which is and so stored in the fileList local variable.
    4. The list of files is stored in the TransferState object.
    5. The rendered view is send back to the browser and the browser displays the view and bootstraps Angular on the client.
    6. The client calls the constructor() again and uses isPlatformServer() to make up one's mind that the code is beingness executed on the customer.
    7. The constructor() retrieves listing of files from the TransferState object.

    Implement server-side file manipulation

    With the API endpoints in place you can complete the implementation of file manipulation operations from the client.

    Open up the server.ts file and locate the following line of code:

                      const fs = require('fs');                                  

    Insert the following TypeScript code under the line in a higher place:

                      const listFiles = (callBack) => {  render fs.readdir('./user_upload', callBack); };                                  

    Locate the post-obit code in the server.ts file:

                      app.engine('html', ngExpressEngine({  bootstrap: AppServerModuleNgFactory,  providers: [    provideModuleMap(LAZY_MODULE_MAP)  ] }));                                  

    Modify the code shown above to include the additional line shown below:

                      app.engine('html', ngExpressEngine({  bootstrap: AppServerModuleNgFactory,  providers: [    provideModuleMap(LAZY_MODULE_MAP),    {provide: 'LIST_FILES', useValue: listFiles}  ] }));                                  

    At present comes the time to consume this server role in the Angular application.

    Open the src/app/file.service.ts file and supercede the existing import directives with the post-obit TypeScript code:

                      import { HttpClient } from '@angular/common/http'; import { Injectable, Inject, PLATFORM_ID, Optional } from '@angular/core'; import { BehaviorSubject, Bailiwick, Observable, ReplaySubject } from 'rxjs'; import { finalize } from 'rxjs/operators'; import { isPlatformServer } from '@angular/common'; import { TransferState, makeStateKey, StateKey } from '@angular/platform-browser';                                  

    To go far possible for the file list displayed on the page to include all the files in the directory, the observable blazon for fileList$ needs to be changed to a ReplaySubject, an appreciable that makes available to its subscribers a listing of the values previously emitted to it. This enables the observer to get the list of files added to the observable before the observer subscribes to the observable. According to the RxJS documentation: "ReplaySubject emits to any observer all of the items that were emitted by the source Observable(s), regardless of when the observer subscribes."

    Find the following line of lawmaking in the src/app/file.service.ts file:

                      private fileList$: Subject<string[]> = new Subject area<string[]>();                                  

    Replace the line higher up with the post-obit TypeScript lawmaking:

                      individual fileList$: Subject<string[]> = new ReplaySubject<string[]>(one);                                  

    Modify the FileService constructor to provide the course with the PLATFORM_ID (client or server) and the TransferState object. If the code is running on the server the constructor logic reads the contents of the user_upload directory (by using injected reference to the listFiles method) and adds the list of files to the TransferState object. If the code is running on the client, the listing of files in transferState is copied to the class' private fellow member variable, fileList.

    Find the line of code below in the src/app/file.service.ts file:

                      constructor(private http: HttpClient) { }                                  

    Supplant the line above with the following TypeScript lawmaking:

                      constructor(  private http: HttpClient,  @Optional() @Inject('LIST_FILES') private listFiles: (callback) => void,  @Inject(PLATFORM_ID) private platformId: whatsoever,  private transferState: TransferState  ) {   const transferKey: StateKey<string> = makeStateKey<string>('fileList');   if (isPlatformServer(this.platformId)) {    this.listFiles((err, files) => {      this.fileList = files;      this.transferState.fix(transferKey, this.fileList);    });   } else {     this.fileList = this.transferState.get<string[]>(transferKey, []);   }   this.fileList$.next(this.fileList); }                                  

    Exam the consummate awarding

    Rebuild the awarding by executing the post-obit education at the command line in the angular-and-nodejs-data directory:

                      npm run build:prod npm run server                                  

    Open a browser window and navigate to http://localhost:8080. Whatsoever files in the user_upload directory should exist listed under Your files, every bit shown below, and you should exist able to upload, download, and remove files from the server.

    If you desire to grab up to this step using the code from the GitHub repository, execute the following commands in the directory where you lot'd like to create the project directory:

                      git clone https://github.com/maciejtreder/angular-and-nodejs-data.git cd angular-and-nodejs-data git checkout step3 npm install                                  

    What about security?

    Does Angular running on the client have admission to data outside of it, like the server file system? Aye it does. And you have same codebase for the server and browser? Yes you lot do.

    You might ask: "What about path traversal? Tin can everyone on the internet encounter the data I store in the user_upload directory?" This question is more than appropriate hither!

    What we are doing in our app is passing reference to the method, not method itself. That'south why providing data from Node.js to the Athwart client app is a great way of sharing sensitive data.

    Examine the build output and take a expect at the FileService constructor in the dist/main.hashcode.js file:

                                          function e(e,t,n,r){    var o=this;    this.http=e, this.listFiles=t, this.platformId=n, this.transferState=r, this.fileList=new Assortment, this.fileList$=new oa(1),this.displayLoader$=new sa(!1);    var i=Bu("fileList");    Oa(this.platformId)?        this.listFiles(function(e,t){            o.fileList=t,o.transferState.fix(i,o.fileList)        })        :        this.fileList=this.transferState.become(i,[]),this.fileList$.next(this.fileList)    }                                  

    As you lot tin see, the JavaScript is expecting Node.js to pass a reference to the function, passed equally the variable t. No information nearly directory structure on our server can be retrieved from the JavaScript in the output bundle.

    Summary of passing data from Node.js to Angular

    In this projection you learned how to transfer files between a client browser and a Node.js server in a single projection codebase. The client's user interface tin can select files to upload, upload them to the server where they are stored, listing the files stored on the server, remove files stored on the server, and download files from the server. You saw how to do all this in a single Athwart codebase using Angular Universal and Node.js. You also saw that this is a secure method of transferring data between the client and server, including files stored on the server.

    Additional resource

    Angular Universal documentation, including tutorials and the CLI reference

    Dependency Injection in Action in Angular

    TransferState course documentation, part of the @angular/platform-browser

    ReplaySubject objects explained with other Subject object variants

    RxJS ReplaySubject documentation, a "work in progress"

    Maciej Treder is a Senior Software Development Engineer at Akamai Technologies . He is also an international conference speaker and the writer of @ng-toolkit , an open source toolkit for building Angular progressive web apps (PWAs), serverless apps, and Angular Universal apps. Check out the repo to larn more most the toolkit, contribute, and support the project. You can learn more about the author on his website . You lot tin also contact him at: contact@maciejtreder.com or @maciejtreder on GitHub, Twitter, StackOverflow, and LinkedIn.

merkleyabaces.blogspot.com

Source: https://www.twilio.com/blog/transfer-files-data-javascript-applications-angular-node-js

Post a Comment for "Angular 7 Upload File Node Js Example"