How to download excel/Zip files in Angular 4 - excel

I am using angular 4 as frond end and lumen 5.4 as back end.
My requirement is to export some data as excel and zip file.
Using import { saveAs } from 'file-saver/FileSaver'; package for file download.
Angular 4 Code:
downloadExcel() {
const type = 'application/vnd.ms-excel';
const headers = { headers: new Headers({ 'Accept': type }) };
const filename = 'file.xls';
this.http.get('http://10.2.2.109/Download/exportExcel', headers)
.toPromise()
.then(response => this.saveToFileSystem(response, type, filename));
return false;
}
private saveToFileSystem(response, __type, filename) {
const contentDispositionHeader: string = response.headers.get('Content-Disposition');
if (contentDispositionHeader !== null) {
const parts: string[] = contentDispositionHeader.split(';');
//const filename = parts[1].split('=')[1];
const blob = new Blob([response._body], { type: __type });
saveAs(blob, filename);
} else {
alert('Cant download.....');
// handling download condition if content disposition is empty
const blob = new Blob([response._body], { type: __type });
saveAs(blob, filename);
}
}
Lumen Code
public function exportExcel(Request $request) {
$file = storage_path();
$file_name = 'book1.xls';
$headers = [
'Content-type' => 'application/vnd.ms-excel',
'Content-Disposition' => 'attachment;filename="' . $file_name,
'X-Filename' => $file_name,
'Content-Transfer-Encoding' => 'binary',
'Content-Length' => filesize($file . '/' . $file_name),
'Cache-Control' => 'max-age=0',
'Cache-Control' => 'max-age=1',
'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
'Cache-Control' => 'cache, must-revalidate',
'Pragma' => 'public',
'Set-Cookie' => 'fileDownload=true; path=/',
'Access-Control-Expose-Headers' => 'Content-Length,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma'
];
return response()->download($file . '/' . $file_name, $file_name, $headers);
}
Issues
const contentDispositionHeader: string = response.headers.get('Content-Disposition'); seems always empty.
We cant open downloaded file, shows corrupted message.
It working for text file download
Please help me to resolve this issue. OR specify any other working code//package for angular

Try this:
downloadExcel() {
const type = 'application/vnd.ms-excel';
const filename = 'file.xls';
const options = new RequestOptions({
responseType: ResponseContentType.Blob,
headers: new Headers({ 'Accept': type })
});
this.http.get('http://10.2.2.109/Download/exportExcel', options)
.catch(errorResponse => Observable.throw(errorResponse.json()))
.map((response) => {
if (response instanceof Response) {
return response.blob();
}
return response;
})
.subscribe(data => saveAs(data, filename),
error => console.log(error)); // implement your error handling here
}
The key points are responseType: ResponseContentType.Blob on the RequestOptions and response.blob() when getting back the response.
In general, it's not recommended to access the _body property of the response like this: response._body, but instead you should call the relevant method to get the body content based on its type (like response.blob(), response.json(), etc)

You could use json2csv provided the input data is in JSON format. The output of the function will be CSV which can be opened in Microsoft Excel or Google Sheets.
Install the package:
$ npm install json2csv --save
Add the following to your component:
var json2csv = require('json2csv');
var fields = ['field1', 'field2', 'field3'];
var result = json2csv({ data: myData, fields: fields });

Related

getting exceljs workbookdata created in nodejs to saveAs in the client (SOLVED)

I have an angular + node application that has the ability nto download excel files rendered using the exceljs package.
All the work (except for getting the data for the excel) is done throught the client side. The problem is that the browser couldn't handle such amount of data.
What I'm trying to do now is basically do all the work in the server and the client should get the data as buffer array [buffer] and save it.
This my code which worked: (below you can see the fixed version)
Component :
//The this.srv.getExcel() only return observable of data returned from the DB
this.srv.getExcel().subscribe(result =>
{
let workbook = new Workbook();
workbook.addWorksheet('sheet1');
result.forEach(dataItem => worksheet.addRow(Object.keys(dataItem).map(di => dataItem[di]))); //populating the excel
workbook.xlsx.writeBuffer().then((data) =>
{
const data: Blob = new Blob([data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'});
FileSaver.saveAs(data, 'excelFile.xlsx');
});
})
Now - Trying to convert it (SOLVED):
Component:
this.nodeSrv.getExcel(request, fileName).subscribe(result =>
{
const data: Blob = new Blob([request], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'});
FileSaver.saveAs(data, fileName + '.xlsx');
},
error => { debugger; this.loading = false; }
)
service with http to the end point on the server:
getExcel(request, fileName)
{
var path = reportsUrl.GetVotingBoxListForExcel;
const options = { withCredentials: true };
return this.http.post<any>(path, {request: request, reportName : fileName }, options);
}
This is the main change - most of te work is in the server - This is the nodeSrv:
router:
const express = require('express');
const router = express.Router();
router.use(req, res, next) =>
{
//The GetDataForExcel is the same as this.srv.getExcel() only return promise of data returned from the DB
return DB.GetDataForExcel(req.body.request).then((dataResult) => {
let reportTypeNameForExcel = req.body.reportName ? req.body.reportName : '';
return excel.createExcel(res, dataResult, reportTypeNameForExcel);
}).catch((err) => {
next({
details: err
})
});
})
module.exports = router;
This is the excel.createExcel, something is probably wrong here
createExcel : function(res, dataResult, reportTypeNameForExcel)
{
let workbook = new Workbook();
workbook.addWorksheet('sheet1');
dataResult.forEach(dataItem => worksheet.addRow(Object.keys(dataItem).map(di => dataItem[di]))); //populating the excel
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader("Content-Disposition", "attachment; filename=" + "Report.xlsx");
workbook.xlsx.write(res).then(() =>
{
res.end();
})
}
The code above is already fixed - solved

Axios Excel file download using POST results in corrupted file

I was using Axios to download a file provided by a GET endpoint previously. The endpoint has changed and is now a POST, however the parameters are not required. I'm updating the original download method, but am getting a corrupted file returned.
downloadTemplate() {
axios.post(DOWNLOAD_TEMPLATE_URL,
{
responseType: 'blob',
headers: {
'Content-Disposition': "attachment; filename=template.xlsx",
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}
})
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'template.xlsx');
document.body.appendChild(link);
link.click();
})
.catch((error) => console.log(error));
}
I'm not sure if the issue is with the responseType, headers, or how the response is handled or all of the above. I've tried various options with no luck so far. Any suggestions would be greatly appreciated!
I have been able to download the file using Postman so I know the file served by the endpoint is fine. I just can't sort out the params to do this in my React code.
Finally got it working! The post syntax in the code block for the question was not correct and also changed the responseType to "arraybuffer".
Working example below:
downloadTemplate() {
axios.post(DOWNLOAD_TEMPLATE_URL, null,
{
headers:
{
'Content-Disposition': "attachment; filename=template.xlsx",
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
},
responseType: 'arraybuffer',
}
).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'template.xlsx');
document.body.appendChild(link);
link.click();
})
.catch((error) => console.log(error));
}
We can use the following code to export Excel files from the POST method. May it help someone and save time.
For API use .Net Core 2.2 and the method is below.
Note: When we create a FileStreamResult, Content-Disposition header for the response will contain the filename and the stream will come as an attachment.
Add the "Content-Disposition" to Cors at Startup file,
app.UseCors(b => b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials().WithExposedHeaders("Content-Disposition"));
I am using the EPplus package for generating the Excel file.
using OfficeOpenXml;
using OfficeOpenXml.Style;
public static MemoryStream InvoiceToExcel(List<InvoiceSearchDto> invoices)
{
var listOfFieldNames = typeof(InvoiceSearchDto).GetProperties().Select(f => f.Name).ToList();
int cellCounter = 1, recordIndex = 2;
var ms = new MemoryStream();
using (ExcelPackage package = new ExcelPackage(ms))
{
ExcelWorksheet worksheet;
worksheet = package.Workbook.Worksheets.Add("New HGS");
// Setting the properties of the first row
worksheet.Row(1).Height = 20;
worksheet.Row(1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
worksheet.Row(1).Style.Font.Bold = true;
// Header of the Excel sheet
foreach (string header in listOfFieldNames)
{
worksheet.Cells[1, cellCounter++].Value = header;
}
// Inserting the article data into excel
// sheet by using the for each loop
// As we have values to the first row
// we will start with second row
foreach (InvoiceSearchDto invoice in invoices)
{
worksheet.Cells[recordIndex, 1].Value = invoice.CompanyName;
worksheet.Cells[recordIndex, 2].Value = invoice.CustomerNo;
worksheet.Cells[recordIndex, 3].Value = invoice.DocumentNumber;
worksheet.Cells[recordIndex, 4].Value = invoice.BillingPeriodStartDate.ToString("YYYY-MM-DD");
worksheet.Cells[recordIndex, 5].Value = invoice.BillingPeriodEndDate.ToString("YYYY-MM-DD");
worksheet.Cells[recordIndex, 6].Value = invoice.DateOfInvoice.ToString("YYYY-MM-DD");
worksheet.Cells[recordIndex, 7].Value = invoice.ExpirationDate.ToString("YYYY-MM-DD");
worksheet.Cells[recordIndex, 8].Value = invoice.Amount;
worksheet.Cells[recordIndex, 9].Value = invoice.InvoiceStatusText;
recordIndex++;
}
// By default, the column width is not
// set to auto fit for the content
// of the range, so we are using
// AutoFit() method here.
worksheet.Column(1).AutoFit();
worksheet.Column(2).AutoFit();
worksheet.Column(3).AutoFit();
worksheet.Column(4).AutoFit();
worksheet.Column(5).AutoFit();
worksheet.Column(6).AutoFit();
worksheet.Column(7).AutoFit();
worksheet.Column(8).AutoFit();
worksheet.Column(9).AutoFit();
package.Save();
}
ms.Position = 0;
return ms;
}
The Action Method code is as below
[HttpPost]
[Route("[action]")]
public IActionResult GetInvoiceWithExcel([FromBody]SearchInvoice searchInvoice)
{
try
{
if (!string.IsNullOrEmpty(searchInvoice.InvoiceDateFrom))
{
searchInvoice.DateFrom = Convert.ToDateTime(searchInvoice.InvoiceDateFrom);
}
if (!string.IsNullOrEmpty(searchInvoice.InvoiceDateTo))
{
searchInvoice.DateTo = Convert.ToDateTime(searchInvoice.InvoiceDateTo);
}
var invoices = invoiceBatchService.GetAllForExcel(searchInvoice.PagingParams, searchInvoice, searchInvoice.FilterObject);
if (invoices != null)
{
MemoryStream invoiceStream = ExcelConverter.InvoiceToExcel(invoices);
var contentType = "application/octet-stream";
var fileName = "Invoice.xlsx";
return File(invoiceStream, contentType, fileName);
}
else
{
ResponseModel.Notification = Utility.CreateNotification("Not Found Anything", Enums.NotificationType.Warning);
return NotFound(ResponseModel);
}
}
catch (Exception ex)
{
NLogger.LogError(ex, "Get Invoice With Excel");
ResponseModel.Notification = Utility.CreateNotification(Helpers.ExceptionMessage(ex), Enums.NotificationType.Error);
return StatusCode(500, ResponseModel);
}
}
Finally the React and axois code as below.
the Service code:
return http.post(
API_BASE_URL + "/Invoice/GetInvoiceWithExcel",
searchInvoice,
{
headers: getHeaders(), // for token and others
responseType: 'blob' // **don't forget to add this**
}
);
};
And the Action method Code is below. Here I use the "file-saver" package to download the file.
import { saveAs } from 'file-saver';
export const getInvoiceWithExcel = invoiceInfo => {
return dispatch => {
dispatch({
type: LOADING_ON
});
InvoiceService.getInvoiceWithExcel(invoiceInfo)
.then(res => {
console.log(res);
let filename = res.headers['content-disposition']
.split(';')
.find((n) => n.includes('filename='))
.replace('filename=', '')
.trim();
let url = window.URL
.createObjectURL(new Blob([res.data]));
saveAs(url, filename);
dispatch({
type: GET_INVOICE_EXCEL_SUCCESS,
payload: ""
});
dispatch({
type: LOADING_OFF
});
dispatch({
type: ON_NOTIFY,
payload: {
...res.data.notification
}
});
})
.catch(err => {
dispatch({
type: GET_INVOICE_EXCEL_FAILED
});
dispatch({
type: LOADING_OFF
});
dispatch({
type: ON_NOTIFY,
payload: {
...Utility.errorResponseProcess(err.response)
}
});
});
};
};

Download excel file in angular 7

Hey after struggling to download the excel from server below is the solution i found very easy.But API side they will just read the path and send the files.
How can i differentiate the file type?
If server files are in your project directory or server , we would like to down load the excel or any file directly. Added the below implementation which works only for excel.
API(.net):
public ActionResult Download()
{
string fileName = WebConfigurationManager.AppSettings["filename"];
var filePath = System.Web.HttpContext.Current.Server.MapPath("~/" + fileName);
if (System.IO.File.Exists(filePath))
{
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
else
{
var response = new WebServiceStatus()
{
code = -1,
data = null,
message = "File is Not available"
};
var data = JsonConvert.SerializeObject(response);
return HandleTrivialHttpRequests(data);
}
}
Angular V7
//Declaration
headers: HttpHeaders;
options: any;
//Constructor or u can have for specific method
this.headers = new HttpHeaders({ 'Content-Type': 'application/json' });
this.options = {
observe: 'response',
headers: this.headers,
responseType: 'arraybuffer'
};
//Service request:
this.httpClient.post('http://localhost:8080/api/report', this.data,
this.option)
.pipe(
catchError(err => this.handleError(err))
).subscribe(response => {
Helper.exportExelFile(response, 'FileName');
});
//In component or in helper function in one class, I have used helper
function which can be reused in other places also
import * as FileSaver from 'file-saver';
function exportExelFile(data, filename) {
const blobValue = new Blob([data['body']], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
FileSaver.saveAs(blobValue, filename + '.' + FileType.EXCEL);
}
export const Helper = {
exportExelFile
};

Angular2 + Express.js: Return CSV and offer downloadable file?

I have this code in my ng2 component:
data => {
let blob = new Blob([data], { type: "text/csv" });
FileSaver.saveAs(blob, "data.csv);
}
Then there is a file saved, but the contents of the file read:
Response with status: 200 OK for URL: https://MYURL/URI/data.csv
My backend is node/express, do I have to create a stream for this or does it work with direct file access like in the old days?
This is my backend at the moment, how can I alter it to return a stream?
'use strict';
const Transactions = require('../../models').transaction;
module.exports = function * details(req, res) {
logger.debug(' Get TME details ' + req.currentTME._id);
const transaction = yield Transactions.findOne({
_tme: req.currentTME.id
}).lean();
return res
.json({
status: constants.status.SUCCESS,
timemodel: req.currentTME,
linkedInfo: {
transaction
}
});
};
Full Server Call:
request(url: string, data: any, method: RequestMethod, showErrorDialog = true): Observable<any> {
const requestOptionsArgs: RequestOptionsArgs = {
url: this.baseUrl + url,
method: method,
headers: this.createHeaders(),
body: JSON.stringify(data)
};
this.log.info(this.getCurl(requestOptionsArgs), this);
const requestOptions: RequestOptions = new RequestOptions(requestOptionsArgs);
return this.intercept(
this.http.request(new Request(requestOptions)).timeout(3000),
requestOptionsArgs,
showErrorDialog
).share();
}

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request...Stream ended unexpectedly

SITUATION:
Submitting a multipart form request from Node.js (via node core HTTPS module) to a spring-boot Java API. The API requires two form-data elements:
"route"
"files"
FULL ERROR:
Exception processed - Main Exception:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is org.apache.commons.fileupload.FileUploadException: Stream ended unexpectedly
REQUEST HEADERS:
{"Accept":"*/*",
"cache-control":"no-cache",
"Content-Type":"multipart/form-data; boundary=2baac014-7974-49dd-ae87-7ce56c36c9e7",
"Content-Length":7621}
FORM-DATA BEING WRITTEN (all written as binary):
Content-Type: multipart/form-data; boundary=2baac014-7974-49dd-ae87-7ce56c36c9e7
--2baac014-7974-49dd-ae87-7ce56c36c9e7
Content-Disposition:form-data; name="route"
...our route object
--2baac014-7974-49dd-ae87-7ce56c36c9e7
Content-Disposition:form-data; name="files"; filename="somefile.xlsx"
Content-Type:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
...excel file contents
--2baac014-7974-49dd-ae87-7ce56c36c9e7--
NODE CODE:
let mdtHttpMultipart = (options, data = reqParam('data'), cb) => {
const boundaryUuid = getUuid()
, baseHeaders = {
'Accept': '*/*',
'cache-control': 'no-cache'
}
, composedHeaders = Object.assign({}, baseHeaders, options.headers)
;
options.path = checkPath(options.path);
let composedOptions = Object.assign({}, {
'host': getEdiHost(),
'path': buildPathFromObject(options.path, options.urlParams),
'method': options.method || 'GET',
'headers': composedHeaders,
'rejectUnauthorized': false
});
composedOptions.headers['Content-Type'] = `multipart/form-data; boundary=${boundaryUuid}`;
let multipartChunks = [];
let dumbTotal = 0;
let writePart = (_, encType = 'binary', skip = false) => {
if (!_) { return; }
let buf = Buffer.from(_, encType);
if (!skip) {dumbTotal += Buffer.byteLength(buf, encType);}
multipartChunks.push(buf);
};
writePart(`Content-Type: multipart/form-data; boundary=${boundaryUuid}\r\n\r\n`, 'binary', true)
writePart(`--${boundaryUuid}\r\n`)
writePart(`Content-Disposition:form-data; name="route"\r\n`)
writePart(JSON.stringify(data[0]) + '\r\n')
writePart(`--${boundaryUuid}\r\n`)
writePart(`Content-Disposition:form-data; name="files"; filename="${data[1].name}"\r\n`)
writePart(`Content-Type:${data[1].contentType}\r\n`)
writePart(data[1].contents + '\r\n')
writePart(`\r\n--${boundaryUuid}--\r\n`);
let multipartBuffer = Buffer.concat(multipartChunks);
composedOptions.headers['Content-Length'] = dumbTotal;
let request = https.request(composedOptions);
// on nextTick write multipart to request
process.nextTick(() => {
request.write(multipartBuffer, 'binary');
request.end();
});
// handle response
request.on('response', (httpRequestResponse) => {
let chunks = []
, errObject = handleHttpStatusCodes(httpRequestResponse);
;
if (errObject !== null) {
return cb(errObject, null);
}
httpRequestResponse.on('data', (chunk) => { chunks.push(chunk); });
httpRequestResponse.on('end', () => {
let responseString = Buffer.concat(chunks).toString()
;
return cb(null, JSON.parse(responseString));
});
});
request.on('error', (err) => cb(err));
};
We cannot see any reason for the 500 to be thrown based on the spec. Tons of tinkering around with the format here but we have yet to achieve the result correctly.
SIDE NOTE: It works for us using POSTMAN, just can't get it to work using our our own application server (where we actually build the excel file).
Any help would be appreciated even if just ideas to try.
Try this:
let mdtHttpMultipart = (options, data = reqParam('data'), cb) => {
const boundaryUuid = getUuid()
, baseHeaders = {
'Accept': '*/*',
'cache-control': 'no-cache'
}
, composedHeaders = Object.assign({}, baseHeaders, options.headers)
;
let file = data[1]
let xlsx = file.contents
options.path = checkPath(options.path);
let composedOptions = Object.assign({}, {
'host': getEdiHost(),
'path': buildPathFromObject(options.path, options.urlParams),
'method': options.method || 'GET',
'headers': composedHeaders,
'rejectUnauthorized': false
});
let header = Buffer.from(`--${boundaryUuid}
Content-Disposition: form-data; name="route"
${JSON.stringify(data[0])})
--${boundaryUuid}
Content-Disposition: form-data; name="files"; filename="${file.name}"
Content-Type: ${file.contentType}
`.replace(/\r?\n */gm, '\r\n'))
let footer = Buffer.from(`\r\n--${boundaryUuid}--`)
let length = header.length + xlsx.length + footer.length
let body = Buffer.concat([header, xlsx, footer], length)
composedOptions.headers['Content-Length'] = length;
composedOptions.headers['Content-Type'] = `multipart/form-data; boundary=${boundaryUuid}`;
let request = https.request(composedOptions);
// handle response
request.on('response', (httpRequestResponse) => {
let chunks = []
, errObject = handleHttpStatusCodes(httpRequestResponse);
;
if (errObject !== null) {
return cb(errObject, null);
}
httpRequestResponse.on('data', (chunk) => { chunks.push(chunk); });
httpRequestResponse.on('end', () => {
let responseString = Buffer.concat(chunks).toString()
;
return cb(null, JSON.parse(responseString));
});
});
request.on('error', (err) => cb(err));
// write multipart to request
request.end(body);
};
Is it that you're not calling request.end() anywhere?
The (very general) form for sending a request with a body is https.request(opts).end(body).
Also, you could just call request.write(buf) every time you want to send data instead of accumulating into one giant buffer (and you shouldn't need to do it on nextTick) (EDIT: as OP points out in the comments, this will prevent setting Content-Length, so perhaps keep as is)

Resources