Download a file from spring boot rest services from angular 5 - javascript

I have spring boot service which provides csv file as response.
How do we call this service from angular 5 typescript.
download of a file should happen depends on some input parameters so I will have post call with user clicks the export button.
below is the rest code in controller.
#Controller
public class MyController {
#RequestMapping(value = "/downLoadDataQueryCsv", method = RequestMethod.GET)
public ResponseEntity<Object> downLoadDataQueryCsv(Model model) throws IOException {
FileWriter fileWriter = null;
try {
DataQueryRequestParams dataQueryRequestParams = new DataQueryRequestParams();
dataQueryRequestParams.setMbuCategory("UKY");
// Result table.
List<OrderIdFinalRank> rankList = // call api to get data.
// construct headers
List<String> csvHeaders = constructDataQueryHeaders();
StringBuilder fileContent = new StringBuilder(String.join(",", csvHeaders));
fileContent.append("\n");
// construct file content from response
for(OrderIdFinalRank finalRank : rankList) {
fileContent.append(StringUtils.join(constructDataQueryRow(finalRank), ",")).append("\n");
}
String fileName = new String("DataQueryTab.csv");
fileWriter = new FileWriter(fileName);
fileWriter.write(fileContent.toString());
fileWriter.flush();
File file = new File(fileName);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getName()));
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
ResponseEntity<Object> responseEntity = ResponseEntity.ok().headers(headers).contentLength(file.length())
.contentType(MediaType.parseMediaType("application/txt")).body(resource);
return responseEntity;
} catch (Exception e) {
System.out.println("Exception: " +e);
return new ResponseEntity<>("Error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
if(null != fileWriter) {
fileWriter.close();
}
}
}
}
Now I need to call this from UI when I click export button, what have written is below.
I have read file saver and added below code, but its not working. kindly help me.
#Injectable()
export class ApiService {
onExport(dataQueryRequestParams: any) {
const dataQueryURL = API_URL + '/downLoadDataQueryCsv';
const body = JSON.stringify(dataQueryRequestParams);
this._http.get(dataQueryURL).subscribe(res => {
saveAs(res, 'data.csv');
});
}
}
Note: When I ran rest URL from browser the file is downloaded, but the same needs to happen when I click export button.
Am new to UI technologies.
Thanks

I have fixed problem with below code.
export class ApiService {
onExport(requestParams: any): Observable<any> {
const dataQueryURL = API_URL + '/downLoadDataQueryCsv';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'Application/json; charset=UTF-8'
}),
responseType: 'text' as 'text'
};
const body = JSON.stringify(requestParams);
return this._http.post(dataQueryURL, body, httpOptions);
}
}
added below in caller Component class.
export class Component implements OnInit {
onExport() { this._apiService.onExport(this.dataQueryForm.value).subscribe(data => {
const blob1 = new Blob([data], { type: 'text/csv' });
FileSaver.saveAs(blob1, 'data.csv');
}) ;
}
}
Thank you all for your responses !

Related

Problem with fetching data from Spring Boot API endpoint using Java Script

I work on web application and encountered an issue with fetching data from an endpoint using Java Script. If I type endpoint adres in a browser it works perfectly fine but somehow it does not work in the script. The response.ok is returns False.
Here is script:
(function() {
function requestAuthorization() {
let response = fetch("http://localhost:8080/authorizationData")
.then(response => response.json());
if (response.ok) {
let json = response.json();
alert(json);
} else {
alert("HTTP response not ok");
}
}
requestAuthorization();
})();
Here is controller:
#RestController
class AuthController {
private final AuthService service;
AuthController(AuthService service) throws IOException {
this.service = service;
}
#GetMapping("/authorizationData")
public ResponseEntity<AuthData> authorize() throws IOException {
return ResponseEntity.ok(service.getAuthData());
}
}
Here is service:
#Service
class AuthService {
private final ObjectMapper mapper;
AuthService(ObjectMapper mapper) {
this.mapper = mapper;
}
public AuthData getAuthData() throws IOException {
String resourcePath = "data/applicationSettings.json";
InputStream resource = new ClassPathResource(resourcePath).getInputStream();
return mapper.readValue(resource, AuthData.class);
}
}
What is wrong? If You have any other advice regarding my work I will be pleased to hear it.
EDIT
The script and and HTML file which runs it are both located in static directory in classpath.
You should be doing it like this:
// mark your function as async
async function requestAuthorization() {
// always try using const rather than let
const response = await fetch("http://localhost:8080/authorizationData");
if (response.ok) {
const json = response.json();
alert(json);
} else {
alert("HTTP response not ok");
}
}

Angular 5 httpClient version of code

I have some code which works for me in it's current format but I would like to change it so it uses angular httpClient instead.
Here is the current code:
const xhr = new XMLHttpRequest();
const xml = XMLData;
xhr.open('PUT', 'my url here', true);
xhr.setRequestHeader('Content-type', 'text/xml');
xhr.send(xml);
const response = xhr.responseText;
How can I do this with Angular 5's httpClient PUT?
This is what an PUT request with HttpClient and options could look like. You will need to transform your XMLData, whatever that may be, to a string. The SO question provided by #Vikas in his comment mentions a few libraries that are effective at parsing XML.
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
#Injectable()
export class AppService {
constructor(private http: HttpClient) { }
doPut() {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'text/xml'
})
};
const xml: string = '<foo>1</foo>';
return this.http.put("/some/url", xml, httpOptions)
.subscribe(result => console.log(result));
}
}
Consolidated version if you prefer:
doPut(xml: string) {
return this.http.put("/some/url", xml, { headers: new HttpHeaders({ 'Content-Type': 'text/xml' }) })
.subscribe(result => console.log(result));
}
The HTTP request will NOT execute unless you subscribe() to the returned Observable produced by put() somewhere. I'd additionally review the documentation for error handling and additional options/functionality.
Hopefully that helps!

ORIGINAL EXCEPTION: Cannot read property 'post' of undefined in angular 2

I had an issue on
Cannot read property 'post' of undefined in angular 2
on submitting the form, a function is called, here is the code
onSubmit() {
console.log("onsubmit->", this.addForm.value);
let addArray = this.addForm.value;
this._employeeservice.addEmployeeCollection(addArray)
.subscribe(sample => {
this.dbemp.push(sample);
});
this.addForm.reset();
this.SubmitToast();
}
addEmployeeCollection() code is here
addEmployeeCollection(addArray: EmployeeSchema) {
let url: string = Constants.DOMAIN + Constants.CREATE_EMPLOYEE_ROUTE;
console.log('addArray at emplyee service', addArray)
var body = addArray;
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let options = {
headers: headers
};
let token = localStorage.getItem('realtoken');
options.headers.set('Authorization', ` ${token}`);
return this.http.post(url, body, options).map(res => res.json()).catch(this._errorHandler);
}
Based on comments, we learned that the HTTP wasn't injected properly, which caused this.http to be undefined.
Instead of marking the http in the service like so:
export class EmployeeService {
public http: Http;
}
it should be injected in the constructor:
export class EmployeeService {
constructor(public http: Http) { }
}
Generally, it's safer to empty your form once you got your success call back. So try this :
onSubmit() {
console.log("onsubmit->", this.addForm.value);
let addArray = this.addForm.value;
this._employeeservice.addEmployeeCollection(addArray)
.subscribe(sample => {
this.dbemp.push(sample);
this.addForm.reset(); //move this in the subscribe area
this.SubmitToast();
});
}

How to force Angular2 to POST using x-www-form-urlencoded

I have a project that needs to use Angular2 (final) to post to an old, legacy Tomcat 7 server providing a somewhat REST-ish API using .jsp pages.
This worked fine when the project was just a simple JQuery app performing AJAX requests. However, the scope of the project has grown such that it will need to be rewritten using a more modern framework. Angular2 looks fantastic for the job, with one exception: It refuses to perform POST requests using anything option but as form-data, which the API doesn't extract. The API expects everything to be urlencoded, relying on Java's request.getParameter("param") syntax to extract individual fields.
This is a snipped from my user.service.ts:
import { Injectable } from '#angular/core';
import { Headers, Response, Http, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private loggedIn = false;
private loginUrl = 'http://localhost:8080/mpadmin/api/login.jsp';
private headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
constructor(private http: Http) {}
login(username, password) {
return this.http.post(this.loginUrl, {'username': username, 'password': password}, this.headers)
.map((response: Response) => {
let user = response.json();
if (user) {
localStorage.setItem('currentUser', JSON.stringify(user));
}
}
);
}
}
No matter what I set the header content type to be, it always ends up arriving as non-encoded form-data. It's not honoring the header I'm setting.
Has anyone else encountered this? How do you go about forcing Angular2 to POST data in a format that can be read by an old Java API using request.getParameter("param")?
For Angular > 4.3 (New HTTPClient) use the following:
let body = new URLSearchParams();
body.set('user', username);
body.set('password', password);
let options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
this.http
.post('//yourUrl.com/login', body.toString(), options)
.subscribe(response => {
//...
});
Note 3 things to make it work as expected:
Use URLSearchParams for your body
Convert body to string
Set the header's content-type
Attention: Older browsers do need a polyfill! I used: npm i url-search-params-polyfill --save and then added to polyfills.ts: import 'url-search-params-polyfill';
UPDATE June 2020: This answer is 4 years old and no longer valid due to API changes in Angular. Please refer to more recent answers for the current version approach.
You can do this using URLSearchParams as the body of the request and angular will automatically set the content type to application/x-www-form-urlencoded and encode the body properly.
let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);
this.http.post(this.loginUrl, body).map(...);
The reason it's not currently working for you is you're not encoding the body data in the correct format and you're not setting the header options correctly.
You need to encode the body like this:
let body = `username=${username}&password=${password}`;
You need to set the header options like this:
this.http.post(this.loginUrl, body, { headers: headers }).map(...);
For those still looking for an answer this is how I solved it with Angular 5 and HttpClient:
const formData = new FormData();
// append your data
formData.append('myKey1', 'some value 1');
formData.append('myKey1', 'some value 2');
formData.append('myKey3', true);
this.httpClient.post('apiPath', formData);
Do NOT set Content-Type header, angular will fix this for you!
This is what worked for me with Angular 7:
const payload = new HttpParams()
.set('username', username)
.set('password', password);
this.http.post(url, payload);
No need to explicitly set the header with this approach.
Note that the HttpParams object is immutable. So doing something like the following won't work, it will give you an empty body:
const payload = new HttpParams();
payload.set('username', username);
payload.set('password', password);
this.http.post(url, payload);
When using angular forms most parameters will be sent as objects, hence your login function will most likely have this object
form.value = {username: 'someone', password:'1234', grant_type: 'password'} as the parameter
to send this object as x-www-form-urlencoded your code will be
export class AuthService {
private headers = new HttpHeaders(
{
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
}
);
constructor(private http: HttpClient) { }
login(data): Observable<any> {
const body = new HttpParams({fromObject: data});
const options = { headers: this.headers};
return this.http.post(`${environment.baseUrl}/token`, body.toString(), options);
}
Angular 9
This is a code that works.
Take other options that fit to you to return not success answer.
let params = new HttpParams({
fromObject: { email: usuario.email, password: usuario.password, role: usuario.role },
});
let httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
};
return this.http.post(`${this.url}/usuario/signup`, params.toString(), httpOptions).pipe(
map(
(resp) => {
console.log('return http', resp);
return resp;
},
(error) => {
console.log('return http error', error);
return error;
}
)
);
remember from string you use fromString and not fromObject.
I found out this solution after working several hours on this issue
login(userName: string, password: string) {
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append( 'No-Auth', 'True');
const body = new URLSearchParams();
body.set('username', userName);
body.set('password', password);
body.set('grant_type', 'password');
return this.http.post(
this.baseUrl + '/token'
, body.toString()
, { headers: headers }
)
.pipe(map(res => res.json()))
.pipe(map(res => {
localStorage.setItem('auth_token', res.auth_token);
return true;
}))
.pipe(catchError((error: any) => {
return Observable.throw(error);
}));
}
For Angular 12, this is what worked for me.
options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
params = new HttpParams()
.set("client_id", "client_id")
.set("client_secret", "client_secret")
.set("grant_type", "grant_type")
.set("scope", "scope")
getToken(){
return this._http.post(`${URL}`, this.params, this.options)
}
Also, remember to import the following at the top import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
Also notice that, unlike the others, we do not use toString() as it's redundant.
Guys I've been working on this since a while and thanks to this post from Josh Morony https://www.joshmorony.com/integrating-an-ionic-application-with-a-nodejs-backend/ I figured out what the problem was. Basically, when I started testing my api I was using POSTMAN and it was working perfectly but when it came to implementing it with Ionic Angular it became a problem. The solution in this post is only about importing body-parser and use it as app middleware like this app.use(bodyParser.json()) on your server-side root file(index).
Hopefully, this will help, Thanks!
Angular 8
const headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
});
const params = new HttpParams();
params.set('username', 'username');
params.set('password', 'password');
this.http.post(
'https://localhost:5000/api',
params.toString(),
{ headers }
);
export class MaintenanceService {
constructor(private http: HttpClient) { }
//header de requete http
private headers = new HttpHeaders(
{ 'Content-Type': 'application/x-www-form-urlencoded' }
);
// requete http pour recuperer le type des maintenances
createMaintenance(data: createMaintenance){
const options = { headers: this.headers};
return this.http.post('api/v2/admin/maintenances', data, options ).subscribe(status=> console.log(JSON.stringify(status)));
}
let options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
let body = new URLSearchParams();
body.set('userId', userId);
body.set('discussionId', discussionId);

How do I download a file with Angular2 or greater

I have a WebApi / MVC app for which I am developing an angular2 client (to replace MVC). I am having some troubles understanding how Angular saves a file.
The request is ok (works fine with MVC, and we can log the data received) but I can't figure out how to save the downloaded data (I am mostly following the same logic as in this post). I am sure it is stupidly simple, but so far I am simply not grasping it.
The code of the component function is below. I've tried different alternatives, the blob way should be the way to go as far as I understood, but there is no function createObjectURL in URL. I can't even find the definition of URL in window, but apparently it exists. If I use the FileSaver.js module I get the same error. So I guess this is something that changed recently or is not yet implemented. How can I trigger the file save in A2?
downloadfile(type: string){
let thefile = {};
this.pservice.downloadfile(this.rundata.name, type)
.subscribe(data => thefile = new Blob([data], { type: "application/octet-stream" }), //console.log(data),
error => console.log("Error downloading the file."),
() => console.log('Completed file download.'));
let url = window.URL.createObjectURL(thefile);
window.open(url);
}
For the sake of completeness, the service that fetches the data is below, but the only thing it does is to issue the request and pass on the data without mapping if it succeeds:
downloadfile(runname: string, type: string){
return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
.catch(this.logAndPassOn);
}
The problem is that the observable runs in another context, so when you try to create the URL var, you have an empty object and not the blob you want.
One of the many ways that exist to solve this is as follows:
this._reportService.getReport().subscribe(data => this.downloadFile(data)),//console.log(data),
error => console.log('Error downloading the file.'),
() => console.info('OK');
When the request is ready it will call the function "downloadFile" that is defined as follows:
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url= window.URL.createObjectURL(blob);
window.open(url);
}
the blob has been created perfectly and so the URL var, if doesn't open the new window please check that you have already imported 'rxjs/Rx' ;
import 'rxjs/Rx' ;
I hope this can help you.
Try this!
1 - Install dependencies for show save/open file pop-up
npm install file-saver --save
npm install -D #types/file-saver
2- Create a service with this function to recive the data
downloadFile(id): Observable<Blob> {
let options = new RequestOptions({responseType: ResponseContentType.Blob });
return this.http.get(this._baseUrl + '/' + id, options)
.map(res => res.blob())
.catch(this.handleError)
}
3- In the component parse the blob with 'file-saver'
import {saveAs as importedSaveAs} from "file-saver";
this.myService.downloadFile(this.id).subscribe(blob => {
importedSaveAs(blob, this.fileName);
}
)
This works for me!
If you don't need to add headers in the request, to download a file in Angular2 you can do a simple (KISS PRINCIPLE):
window.location.href='http://example.com/myuri/report?param=x';
in your component.
This is for folks looking how to do it using HttpClient and file-saver:
Install file-saver
npm install file-saver --save
npm install #types/file-saver --save
API Service class:
export() {
return this.http.get(this.download_endpoint,
{responseType: 'blob'});
}
Component:
import { saveAs } from 'file-saver';
exportPdf() {
this.api_service.export().subscribe(data => saveAs(data, `pdf report.pdf`));
}
How about this?
this.http.get(targetUrl,{responseType:ResponseContentType.Blob})
.catch((err)=>{return [do yourself]})
.subscribe((res:Response)=>{
var a = document.createElement("a");
a.href = URL.createObjectURL(res.blob());
a.download = fileName;
// start download
a.click();
})
I could do with it.
no need additional package.
For newer angular versions:
npm install file-saver --save
npm install #types/file-saver --save
import {saveAs} from 'file-saver';
this.http.get('endpoint/', {responseType: "blob", headers: {'Accept': 'application/pdf'}})
.subscribe(blob => {
saveAs(blob, 'download.pdf');
});
As mentioned by Alejandro Corredor it is a simple scope error. The subscribe is run asynchronously and the open must be placed in that context, so that the data finished loading when we trigger the download.
That said, there are two ways of doing it. As the docs recommend the service takes care of getting and mapping the data:
//On the service:
downloadfile(runname: string, type: string){
var headers = new Headers();
headers.append('responseType', 'arraybuffer');
return this.authHttp.get( this.files_api + this.title +"/"+ runname + "/?file="+ type)
.map(res => new Blob([res],{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }))
.catch(this.logAndPassOn);
}
Then, on the component we just subscribe and deal with the mapped data. There are two possibilities. The first, as suggested in the original post, but needs a small correction as noted by Alejandro:
//On the component
downloadfile(type: string){
this.pservice.downloadfile(this.rundata.name, type)
.subscribe(data => window.open(window.URL.createObjectURL(data)),
error => console.log("Error downloading the file."),
() => console.log('Completed file download.'));
}
The second way would be to use FileReader. The logic is the same but we can explicitly wait for FileReader to load the data, avoiding the nesting, and solving the async problem.
//On the component using FileReader
downloadfile(type: string){
var reader = new FileReader();
this.pservice.downloadfile(this.rundata.name, type)
.subscribe(res => reader.readAsDataURL(res),
error => console.log("Error downloading the file."),
() => console.log('Completed file download.'));
reader.onloadend = function (e) {
window.open(reader.result, 'Excel', 'width=20,height=10,toolbar=0,menubar=0,scrollbars=no');
}
}
Note: I am trying to download an Excel file, and even though the download is triggered (so this answers the question), the file is corrupt. See the answer to this post for avoiding the corrupt file.
Download *.zip solution for angular 2.4.x: you must import ResponseContentType from '#angular/http' and change responseType to ResponseContentType.ArrayBuffer (by default it ResponseContentType.Json)
getZip(path: string, params: URLSearchParams = new URLSearchParams()): Observable<any> {
let headers = this.setHeaders({
'Content-Type': 'application/zip',
'Accept': 'application/zip'
});
return this.http.get(`${environment.apiUrl}${path}`, {
headers: headers,
search: params,
responseType: ResponseContentType.ArrayBuffer //magic
})
.catch(this.formatErrors)
.map((res:Response) => res['_body']);
}
I am using Angular 4 with the 4.3 httpClient object. I modified an answer I found in Js' Technical Blog which creates a link object, uses it to do the download, then destroys it.
Client:
doDownload(id: number, contentType: string) {
return this.http
.get(this.downloadUrl + id.toString(), { headers: new HttpHeaders().append('Content-Type', contentType), responseType: 'blob', observe: 'body' })
}
downloadFile(id: number, contentType: string, filename:string) {
return this.doDownload(id, contentType).subscribe(
res => {
var url = window.URL.createObjectURL(res);
var a = document.createElement('a');
document.body.appendChild(a);
a.setAttribute('style', 'display: none');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
a.remove(); // remove the element
}, error => {
console.log('download error:', JSON.stringify(error));
}, () => {
console.log('Completed file download.')
});
}
The value of this.downloadUrl has been set previously to point to the api. I am using this to download attachments, so I know the id, contentType and filename:
I am using an MVC api to return the file:
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public FileContentResult GetAttachment(Int32 attachmentID)
{
Attachment AT = filerep.GetAttachment(attachmentID);
if (AT != null)
{
return new FileContentResult(AT.FileBytes, AT.ContentType);
}
else
{
return null;
}
}
The attachment class looks like this:
public class Attachment
{
public Int32 AttachmentID { get; set; }
public string FileName { get; set; }
public byte[] FileBytes { get; set; }
public string ContentType { get; set; }
}
The filerep repository returns the file from the database.
Hope this helps someone :)
Downloading file through ajax is always a painful process and In my view it is best to let server and browser do this work of content type negotiation.
I think its best to have
to do it. This doesn't even require any new windows opening and stuff like that.
The MVC controller as in your sample can be like the one below:
[HttpGet("[action]")]
public async Task<FileContentResult> DownloadFile()
{
// ...
return File(dataStream.ToArray(), "text/plain", "myblob.txt");
}
It will be better if you try to call the new method inside you subscribe
this._reportService.getReport()
.subscribe((data: any) => {
this.downloadFile(data);
},
(error: any) => сonsole.log(error),
() => console.log('Complete')
);
Inside downloadFile(data) function we need to make block, link, href and file name
downloadFile(data: any, type: number, name: string) {
const blob = new Blob([data], {type: 'text/csv'});
const dataURL = window.URL.createObjectURL(blob);
// IE doesn't allow using a blob object directly as link href
// instead it is necessary to use msSaveOrOpenBlob
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob);
return;
}
const link = document.createElement('a');
link.href = dataURL;
link.download = 'export file.csv';
link.click();
setTimeout(() => {
// For Firefox it is necessary to delay revoking the ObjectURL
window.URL.revokeObjectURL(dataURL);
}, 100);
}
}
Well, I wrote a piece of code inspired by many of the above answers that should easily work in most scenarios where the server sends a file with a content disposition header, without any third-party installations, except rxjs and angular.
First, how to call the code from your component file
this.httpclient.get(
`${myBackend}`,
{
observe: 'response',
responseType: 'blob'
}
).pipe(first())
.subscribe(response => SaveFileResponse(response, 'Custom File Name.extension'));
As you can see, it's basically pretty much the average backend call from angular, with two changes
I am observing the response instead of the body
I am being explicit about the response being a blob
Once the file is fetched from the server, I am in principle, delegating the entire task of saving the file to the helper function, which I keep in a separate file, and import into whichever component I need to
export const SaveFileResponse =
(response: HttpResponse<Blob>,
filename: string = null) =>
{
//null-checks, just because :P
if (response == null || response.body == null)
return;
let serverProvidesName: boolean = true;
if (filename != null)
serverProvidesName = false;
//assuming the header is something like
//content-disposition: attachment; filename=TestDownload.xlsx; filename*=UTF-8''TestDownload.xlsx
if (serverProvidesName)
try {
let f: string = response.headers.get('content-disposition').split(';')[1];
if (f.includes('filename='))
filename = f.substring(10);
}
catch { }
SaveFile(response.body, filename);
}
//Create an anchor element, attach file to it, and
//programmatically click it.
export const SaveFile = (blobfile: Blob, filename: string = null) => {
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blobfile);
a.download = filename;
a.click();
}
There, no more cryptic GUID filenames! We can use whatever name the server provides, without having to specify it explicitly in the client, or, overwrite the filename provided by the server (as in this example).
Also, one can easily, if need be, change the algorithm of extracting the filename from the content-disposition to suit their needs, and everything else will stay unaffected - in case of an error during such extraction, it will just pass 'null' as the filename.
As another answer already pointed out, IE needs some special treatment, as always. But with chromium edge coming in a few months, I wouldn't worry about that while building new apps (hopefully).
There is also the matter of revoking the URL, but I'm kinda not-so-sure about that, so if someone could help out with that in the comments, that would be awesome.
For those using Redux Pattern
I added in the file-saver as #Hector Cuevas named in his answer. Using Angular2 v. 2.3.1, I didn't need to add in the #types/file-saver.
The following example is to download a journal as PDF.
The journal actions
public static DOWNLOAD_JOURNALS = '[Journals] Download as PDF';
public downloadJournals(referenceId: string): Action {
return {
type: JournalActions.DOWNLOAD_JOURNALS,
payload: { referenceId: referenceId }
};
}
public static DOWNLOAD_JOURNALS_SUCCESS = '[Journals] Download as PDF Success';
public downloadJournalsSuccess(blob: Blob): Action {
return {
type: JournalActions.DOWNLOAD_JOURNALS_SUCCESS,
payload: { blob: blob }
};
}
The journal effects
#Effect() download$ = this.actions$
.ofType(JournalActions.DOWNLOAD_JOURNALS)
.switchMap(({payload}) =>
this._journalApiService.downloadJournal(payload.referenceId)
.map((blob) => this._actions.downloadJournalsSuccess(blob))
.catch((err) => handleError(err, this._actions.downloadJournalsFail(err)))
);
#Effect() downloadJournalSuccess$ = this.actions$
.ofType(JournalActions.DOWNLOAD_JOURNALS_SUCCESS)
.map(({payload}) => saveBlobAs(payload.blob, 'journal.pdf'))
The journal service
public downloadJournal(referenceId: string): Observable<any> {
const url = `${this._config.momentumApi}/api/journals/${referenceId}/download`;
return this._http.getBlob(url);
}
The HTTP service
public getBlob = (url: string): Observable<any> => {
return this.request({
method: RequestMethod.Get,
url: url,
responseType: ResponseContentType.Blob
});
};
The journal reducer
Though this only sets the correct states used in our application I still wanted to add it in to show the complete pattern.
case JournalActions.DOWNLOAD_JOURNALS: {
return Object.assign({}, state, <IJournalState>{ downloading: true, hasValidationErrors: false, errors: [] });
}
case JournalActions.DOWNLOAD_JOURNALS_SUCCESS: {
return Object.assign({}, state, <IJournalState>{ downloading: false, hasValidationErrors: false, errors: [] });
}
I hope this is helpful.
I share the solution that helped me (any improvement is greatly appreciated)
On your service 'pservice' :
getMyFileFromBackend(typeName: string): Observable<any>{
let param = new URLSearchParams();
param.set('type', typeName);
// setting 'responseType: 2' tells angular that you are loading an arraybuffer
return this.http.get(http://MYSITE/API/FILEIMPORT, {search: params, responseType: 2})
.map(res => res.text())
.catch((error:any) => Observable.throw(error || 'Server error'));
}
Component Part :
downloadfile(type: string){
this.pservice.getMyFileFromBackend(typename).subscribe(
res => this.extractData(res),
(error:any) => Observable.throw(error || 'Server error')
);
}
extractData(res: string){
// transforme response to blob
let myBlob: Blob = new Blob([res], {type: 'application/vnd.oasis.opendocument.spreadsheet'}); // replace the type by whatever type is your response
var fileURL = URL.createObjectURL(myBlob);
// Cross your fingers at this point and pray whatever you're used to pray
window.open(fileURL);
}
On the component part, you call the service without subscribing to a response. The subscribe
for a complete list of openOffice mime types see : http://www.openoffice.org/framework/documentation/mimetypes/mimetypes.html
To download and show PDF files, a very similar code snipped is like below:
private downloadFile(data: Response): void {
let blob = new Blob([data.blob()], { type: "application/pdf" });
let url = window.URL.createObjectURL(blob);
window.open(url);
}
public showFile(fileEndpointPath: string): void {
let reqOpt: RequestOptions = this.getAcmOptions(); // getAcmOptions is our helper method. Change this line according to request headers you need.
reqOpt.responseType = ResponseContentType.Blob;
this.http
.get(fileEndpointPath, reqOpt)
.subscribe(
data => this.downloadFile(data),
error => alert("Error downloading file!"),
() => console.log("OK!")
);
}
Here's something I did in my case -
// service method
downloadFiles(vendorName, fileName) {
return this.http.get(this.appconstants.filesDownloadUrl, { params: { vendorName: vendorName, fileName: fileName }, responseType: 'arraybuffer' }).map((res: ArrayBuffer) => { return res; })
.catch((error: any) => _throw('Server error: ' + error));
}
// a controller function which actually downloads the file
saveData(data, fileName) {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
let blob = new Blob([data], { type: "octet/stream" }),
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
}
// a controller function to be called on requesting a download
downloadFiles() {
this.service.downloadFiles(this.vendorName, this.fileName).subscribe(data => this.saveData(data, this.fileName), error => console.log("Error downloading the file."),
() => console.info("OK"));
}
The solution is referenced from - here
I found the answers so far lacking insight as well as warnings. You could and should watch for incompatibilities with IE10+ (if you care).
This is the complete example with the application part and service part after. Note that we set the observe: "response" to catch the header for the filename. Also note that the Content-Disposition header has to be set and exposed by the server, otherwise the current Angular HttpClient will not pass it on. I added a dotnet core piece of code for that below.
public exportAsExcelFile(dataId: InputData) {
return this.http.get(this.apiUrl + `event/export/${event.id}`, {
responseType: "blob",
observe: "response"
}).pipe(
tap(response => {
this.downloadFile(response.body, this.parseFilename(response.headers.get('Content-Disposition')));
})
);
}
private downloadFile(data: Blob, filename: string) {
const blob = new Blob([data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8;'});
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
} else {
const link = document.createElement('a');
if (link.download !== undefined) {
// Browsers that support HTML5 download attribute
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
private parseFilename(contentDisposition): string {
if (!contentDisposition) return null;
let matches = /filename="(.*?)"/g.exec(contentDisposition);
return matches && matches.length > 1 ? matches[1] : null;
}
Dotnet core, with Content-Disposition & MediaType
private object ConvertFileResponse(ExcelOutputDto excelOutput)
{
if (excelOutput != null)
{
ContentDisposition contentDisposition = new ContentDisposition
{
FileName = excelOutput.FileName.Contains(_excelExportService.XlsxExtension) ? excelOutput.FileName : "TeamsiteExport.xlsx",
Inline = false
};
Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
Response.Headers.Add("Content-Disposition", contentDisposition.ToString());
return File(excelOutput.ExcelSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
else
{
throw new UserFriendlyException("The excel output was empty due to no events.");
}
}
Update to Hector's answer using file-saver and HttpClient for step 2:
public downloadFile(file: File): Observable<Blob> {
return this.http.get(file.fullPath, {responseType: 'blob'})
}
The following code worked for me
Made the HTML like this:
<button type="button" onclick="startDownload(someData)">Click to download!</button>
JS is as follows:
let someData = {};
someData.name = 'someName';
someData.fileurl= 'someUrl';
function startDownload(someData){
let link = document.createElement('a');
link.href = someData.fileurl; //data is object received as response
link.download = someData.fileurl.substr(someData.fileurl.lastIndexOf('/') + 1);
link.click();
}
I got a solution for downloading from angular 2 without getting corrupt,
using spring mvc and angular 2
1st- my return type is :-ResponseEntity from java end. Here I am sending byte[] array has return type from the controller.
2nd- to include the filesaver in your workspace-in the index page as:
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script>
3rd- at component ts write this code:
import {ResponseContentType} from '#angular.core';
let headers = new Headers({ 'Content-Type': 'application/json', 'MyApp-Application' : 'AppName', 'Accept': 'application/pdf' });
let options = new RequestOptions({ headers: headers, responseType: ResponseContentType.Blob });
this.http
.post('/project/test/export',
somevalue,options)
.subscribe(data => {
var mediaType = 'application/vnd.ms-excel';
let blob: Blob = data.blob();
window['saveAs'](blob, 'sample.xls');
});
This will give you xls file format. If you want other formats change the mediatype and file name with right extension.
Download file
my_url should have the same origin, otherwise it will redirect to that location
I was facing this same case today, I had to download a pdf file as an attachment (the file shouldn't be rendered in the browser, but downloaded instead). To achieve that I discovered I had to get the file in an Angular Blob, and, at the same time, add a Content-Disposition header in the response.
This was the simplest I could get (Angular 7):
Inside the service:
getFile(id: String): Observable<HttpResponse<Blob>> {
return this.http.get(`./file/${id}`, {responseType: 'blob', observe: 'response'});
}
Then, when I need to download the file in a component, I can simply:
fileService.getFile('123').subscribe((file: HttpResponse<Blob>) => window.location.href = file.url);
UPDATE:
Removed unnecessary header setting from service
Angular 12 + ASP.NET 5 WEB API
You can return a Blob object from the server and create an anchor tag and set the href property to an object URL created from the Blob. Now clicking on the anchor will download the file. You can set the file name as well.
downloadFile(path: string): Observable<any> {
return this._httpClient.post(`${environment.ApiRoot}/accountVerification/downloadFile`, { path: path }, {
observe: 'response',
responseType: 'blob'
});
}
saveFile(path: string, fileName: string): void {
this._accountApprovalsService.downloadFile(path).pipe(
take(1)
).subscribe((resp) => {
let downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(resp.body);
downloadLink.setAttribute('download', fileName);
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
});
}
Backend
[HttpPost]
[Authorize(Roles = "SystemAdmin, SystemUser")]
public async Task<IActionResult> DownloadFile(FilePath model)
{
if (ModelState.IsValid)
{
try
{
var fileName = System.IO.Path.GetFileName(model.Path);
var content = await System.IO.File.ReadAllBytesAsync(model.Path);
new FileExtensionContentTypeProvider()
.TryGetContentType(fileName, out string contentType);
return File(content, contentType, fileName);
}
catch
{
return BadRequest();
}
}
return BadRequest();
}
let headers = new Headers({
'Content-Type': 'application/json',
'MyApp-Application': 'AppName',
'Accept': 'application/vnd.ms-excel'
});
let options = new RequestOptions({
headers: headers,
responseType: ResponseContentType.Blob
});
this.http.post(this.urlName + '/services/exportNewUpc', localStorageValue, options)
.subscribe(data => {
if (navigator.appVersion.toString().indexOf('.NET') > 0)
window.navigator.msSaveBlob(data.blob(), "Export_NewUPC-Items_" + this.selectedcategory + "_" + this.retailname +"_Report_"+this.myDate+".xlsx");
else {
var a = document.createElement("a");
a.href = URL.createObjectURL(data.blob());
a.download = "Export_NewUPC-Items_" + this.selectedcategory + "_" + this.retailname +"_Report_"+this.myDate+ ".xlsx";
a.click();
}
this.ui_loader = false;
this.selectedexport = 0;
}, error => {
console.log(error.json());
this.ui_loader = false;
document.getElementById("exceptionerror").click();
});
Simply put the url as href as below .
Download File
You may also download a file directly from your template where you use download attribute and to [attr.href] you can provide a property value from the component.
This simple solution should work on most browsers.
<a download [attr.href]="yourDownloadLink"></a>
Reference: https://www.w3schools.com/tags/att_a_download.asp
Create a temporary anchor tag, then click it programmatically with Javascript
async function downloadFile(fileName) {
const url = document.getElementById("url").value
const link = document.createElement('a');
link.href = await toDataURL(url);
link.setAttribute('download', fileName ? fileName : url.split('/').pop());
link.setAttribute('target', 'blank');
document.body.appendChild(link);
link.click();
}
function toDataURL(url) {
return fetch(url)
.then((response) => {
return response.blob();
})
.then((blob) => {
return URL.createObjectURL(blob);
});
}
<input id="url" value="https://images.pexels.com/photos/1741205/pexels-photo-1741205.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"/>
<button onclick="downloadFile('test')">Download</button>
Although the question is old, none of the answers are that viable.
As far as I saw all the files are first loaded in memory and saved after that.
This way we:
Cause a lag, for which a custom loading must be implemented.
Load the file in memory, which means for big files the browser will crash.
Do not use the implemented browser download function.
The front end side is simple enough (Angular 12):
downloadFile(url: string, fileName: string): void {
const downloadLink = document.createElement('a');
downloadLink.download = fileName;
downloadLink.href = url;
downloadLink.click();
}
On the back end (.NET 6) we need to work with streams and write to the response body:
public void Get(string fileId)
{
var fileName = fileService.GetFileName(fileId);
var fileContentType = fileService.GetFileContentType(fileId);
this.Response.Headers.Add(HeaderNames.ContentType, fileContentType);
this.Response.Headers.Add(HeaderNames.ContentDisposition, $"attachment; filename=\"{fileName}\"");
fileService.GetFile(Response.Body, fileId);
}
File content type and name can be retrieved from either the DB (if you save file info in there) or the file system.
Content type is parsed from the extension.
I write to the stream like this:
public void GetFile(Stream writeStream, string fileId)
{
var file = GetFileInfo(fileId);
try
{
var fileStream = File.OpenRead(file.FullName);
byte[] buffer = new byte[32768];
int read;
while ((read = fileStream.Read(buffer, 0, buffer.Length)) > 0)
{
writeStream.Write(buffer, 0, read);
}
writeStream.Flush();
}
catch (Exception e)
{
throw new CustomException($"Error occured while reading the file. Inner Exception Message: ({e.Message}) Stack Trace: ({e.StackTrace})", ErrorCode.FileReadFailure, e);
}
}
Keep in mind I have simplified my implementation for presentation purposes, so it has not been tested.
The answers I found were either not working on Angular 13.1 and/or unnecessary complex (like the accepted example) without explaining why this is necessary.
It would be useful for constantly changing ecosystems like Angular to require the version number to be attached.
The mini snippet provided by user #Aleksandar Angelov bypasses the session system, so an unnecessary authorization is necessary.
Derived by his answer, I came up with the following code:
downloadConfiguration(url: string, filename: string) {
this.http.get(url, {responseType: 'blob'})
.subscribe(data => {
// console.log("data", data);
var url = window.URL.createObjectURL(data);
let downloadLink = document.createElement('a');
downloadLink.href = url
downloadLink.setAttribute('download', filename);
downloadLink.click();
});
}
If you only send the parameters to a URL, you can do it this way:
downloadfile(runname: string, type: string): string {
return window.location.href = `${this.files_api + this.title +"/"+ runname + "/?file="+ type}`;
}
in the service that receives the parameters

Categories

Resources