Today, I faced a requirement where I need to implement file save functionality in an Angular 4 application. In this quick post, I will show you how to implement this functionality in Angular 4 using FileSaver.js module.
Step 1: Create an Angular 4 project
I use Angular CLI to generate Angular 4 applications. Navigate to a convenient location on your file system and run the following command.
$ ng new file-save-ng4-example $ cd file-save-ng4-example $ npm install # yarn install
$
is used to denote bash prompt. You don’t have to type it on your command-line terminal.
Step 2: Install FileSaver.js dependency
FileSaver.js is an HTML 5 FileSaver functionality. It makes it dead simple to implement file save functionality. FileSaver.js is available as a npm package so you can install it as shown below.
$ npm install --save file-saver
Step 3: Implement file save functionality in frontend
To showcase how to implement file save functionality, we will add a button to AppComponent
. When we click on the button then we will make a REST API call to fetch the content.
Let’s start by updating app.component.html
to as shown below.
<div style="text-align:center"> <h1> Export by clicking button below</h1> <button (click)="saveFile()">Export</button></div>
In the code snippet above, we added a button with click event handled. The saveFile
function will exist inside the app.component.ts
as shown below.
import { Component } from '@angular/core'; import { Http, Headers } from '@angular/http'; import 'rxjs/add/operator/toPromise'; import { saveAs } from 'file-saver/FileSaver'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(private http: Http) { } saveFile() { const headers = new Headers(); headers.append('Accept', 'text/plain'); this.http.get('/api/files', { headers: headers }) .toPromise() .then(response => this.saveToFileSystem(response)); } private saveToFileSystem(response) { const contentDispositionHeader: string = response.headers.get('Content-Disposition'); const parts: string[] = contentDispositionHeader.split(';'); const filename = parts[1].split('=')[1]; const blob = new Blob([response._body], { type: 'text/plain' }); saveAs(blob, filename); } }
In the code shown above, we did following:
- We implemented
saveFile
function inAppComponent
. It makes HTTP get request to our service. - Next, we converted
Observable
toPromise
usingrxjs
toPromise
operator. - Next, we read
Content-Disposition
response header to find the filename. - Next, we constructed
Blob
object using the response body. - Finally, we passed blob to FileSaver.js saveAs functionality. It take care of the rest of functionality.
It might happen that
response.headers.get('Content-Disposition')
return value asnull
even though you are setting it in the response header on server side. It wasted a lot of time to figure out that Angular 4 does not set headers until you setAccess-Control-Expose-Headers
header in the server side code. Refer to step 4 where I show a sample Java Spring Boot REST end point.
If you run the code now, you will get 404 as service does not exist. To show a full working example, next step covers shows a Spring Boot application that implements the API.
Step 5: Implement REST API
I use Spring Initializer project to scaffold a Spring Boot project. Once you have created that, just replace your application class with the one shown below.
package com.example.filesavebackend; import java.util.Collections; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry .addMapping("/api/**") .allowedOrigins("http://localhost:4200") .allowedMethods("*"); } }; } } @RestController class FileResource { @GetMapping(path = "/api/files", produces = MediaType.TEXT_PLAIN_VALUE) public ResponseEntity getFile() { String exportedContent = "Hello, World!"; String filename = "my-file.txt"; HttpHeaders headers = new HttpHeaders(); headers.setAccessControlExposeHeaders(Collections.singletonList("Content-Disposition")); headers.set("Content-Disposition", "attachment; filename=" + filename); return new ResponseEntity<>(exportedContent, headers, HttpStatus.OK); } }
The important thing to note is FileResource
. The getFile method implements our REST API. It sets content and required headers. Please note that we are explicitly setting Access-Control-Expose-Headers
header. If you don’t set it, then you will not be able to access Content-Disposition
header in Angular.
Source code of the full application
You can get the source code of the full application from Github: shekhargulati/filesave-angular4-example
Getting this error..
1. Error: No provider for Http!
at injectionError (core.es5.js:1169)
at noProviderError (core.es5.js:1207) etc
2. Unhandled Promise rejection: No provider for Http! ; Zone: ; Task: Promise.then ; Value: Error: No provider for Http!
at injectionError (core.es5.js:1169)
at noProviderError (core.es5.js:1207)
Import the HTTP module in the app.module.ts as done in the Github repository https://github.com/shekhargulati/filesave-angular4-example/blob/master/angular-frontend/src/app/app.module.ts#L13.
While implementing saveAs(blob, fileName) getting error as
location-list.service.ts (77,5): Cannot find name ‘saveAs’. and one more error is
Argument of type ‘{ headers: void; }’ is not assignable to parameter of type ‘RequestOptionsArgs’.
while using the following line in my code
const headers = new Headers();
headers.append(‘Accept’, ‘text/plain’);
this.http.get(‘/api/files’, { headers: headers })
Please provide me help.
i am implementing the above code in Angular4 application
I need to change the downloaded file from download folder to some project folder.
How to implement that?
Error: Cannot find module ‘@angular-devkit/core’
at Function.Module._resolveFilename (module.js:555:15)
at Function.Module._load (module.js:482:25)
at Module.require (module.js:604:17)
at require (internal/module.js:11:18)
at Object. (C:\Users\arun.prabhu\Downloads\filesave-angular4-example-master\filesave-angular4-example-master\angular-frontend\node_modules\@angular-devkit\schematics\src\tree\virtual.js:10:16)
at Module._compile (module.js:660:30)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
try to run the below command in the project folder:
npm install @angular-devkit/core –save-dev
Hi sir, can you please write a post for Implementing file save functionality with Angular and backend asp.net services.