UNPKG

ngx-export-as

Version:

[![npm version](https://badge.fury.io/js/ngx-export-as.svg)](https://badge.fury.io/js/ngx-export-as) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

754 lines (749 loc) 26.4 kB
import * as i0 from '@angular/core'; import { PLATFORM_ID, Inject, Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import html2canvas from 'html2canvas'; import * as XLSX from 'xlsx'; import html2pdf from 'html2pdf.js'; import { isPlatformBrowser } from '@angular/common'; /** * Angular service for exporting HTML/Table elements to various file formats * * @description * This service provides functionality to export HTML content, tables, or specific DOM elements * into various file formats including PDF, PNG, Excel, Word documents, CSV, JSON, and XML. * * Supports both browser download and base64 content retrieval for further processing. * * **As of v1.21.0:** This is a standalone service - no NgModule required. * Provide it directly in your component or app.config.ts. * * @export * @class ExportAsService * * @example * ```typescript * // Standalone Component (Recommended) * import { Component, inject } from '@angular/core'; * * @Component({ * selector: 'app-export', * standalone: true, * providers: [ExportAsService] * }) * export class ExportComponent { * private readonly exportAsService = inject(ExportAsService); * * exportToPDF() { * const config: ExportAsConfig = { * type: 'pdf', * elementIdOrContent: 'tableId' * }; * this.exportAsService.save(config, 'my-export').subscribe(() => { * console.log('Export completed'); * }); * } * } * ``` * * @example * ```typescript * // App-wide provider (app.config.ts) * export const appConfig: ApplicationConfig = { * providers: [ExportAsService] * }; * ``` */ class ExportAsService { /** * Creates an instance of ExportAsService * @param {Object} platformId - Angular platform identifier for SSR compatibility * @memberof ExportAsService */ constructor(platformId) { this.platformId = platformId; if (isPlatformBrowser(this.platformId)) { window['html2canvas'] = html2canvas; } } /** * Retrieves the exported content as a base64 string or JSON object * * @description * This is the main method for retrieving exported content without downloading. * The return type varies by format: * - Most formats: base64 encoded string * - JSON format: returns actual JSON object (not base64) * * @param {ExportAsConfig} config - Export configuration object * @returns {Observable<string | null>} Observable containing the base64 string or null * * @example * ```typescript * const config: ExportAsConfig = { * type: 'pdf', * elementIdOrContent: 'myTableId' * }; * * this.exportAsService.get(config).subscribe(content => { * console.log('Base64 content:', content); * // Use content for upload, preview, etc. * }); * ``` * * @memberof ExportAsService */ get(config) { // structure method name dynamically by type const func = 'get' + config.type.toUpperCase(); // if type supported execute and return if (this[func]) { return this[func](config); } // throw error for unsupported formats return new Observable((observer) => { observer.error('Export type is not supported.'); }); } /** * Exports and automatically downloads the file to the user's device * * @description * This method triggers an automatic download of the exported content. * The file extension is automatically appended based on the export type. * * @param {ExportAsConfig} config - Export configuration object * @param {string} fileName - The name of the file to be saved (without extension) * @returns {Observable<string | null>} Observable that completes when download starts * * @example * ```typescript * private readonly exportAsService = inject(ExportAsService); * * downloadReport() { * const config: ExportAsConfig = { * type: 'xlsx', * elementIdOrContent: 'dataTable', * options: { /* SheetJS options *\/ } * }; * * this.exportAsService.save(config, 'quarterly-report').subscribe(() => { * console.log('Download started'); * }); * } * ``` * * @memberof ExportAsService */ save(config, fileName) { // set download config.download = true; // get file name with type config.fileName = fileName + '.' + config.type; return this.get(config); } /** * Converts a base64 data URL string to a Blob object * * @description * Extracts the MIME type and decodes the base64 string to create a Blob. * Useful for handling binary data in browsers. * * @param {string} content - Base64 encoded data URL string (e.g., "data:image/png;base64,...") * @returns {Observable<Blob>} Observable containing the Blob object * * @example * ```typescript * private readonly exportAsService = inject(ExportAsService); * * convertToBlob() { * const dataUrl = 'data:image/png;base64,iVBORw0KGgo...'; * this.exportAsService.contentToBlob(dataUrl).subscribe(blob => { * console.log('Blob size:', blob.size); * }); * } * ``` * * @memberof ExportAsService */ contentToBlob(content) { return new Observable((observer) => { // get content string and extract mime type const arr = content.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } observer.next(new Blob([u8arr], { type: mime })); observer.complete(); }); } /** * Removes the data URL prefix from a base64 string * * @description * Strips the MIME type prefix (e.g., "data:text/csv;base64,") from a base64 data URL, * leaving only the raw base64 encoded content. * * @param {string} fileContent - The complete base64 data URL string * @returns {string} The raw base64 string without the data URL prefix * * @example * ```typescript * const dataUrl = 'data:text/csv;base64,SGVsbG8gV29ybGQ='; * const base64Only = this.removeFileTypeFromBase64(dataUrl); * // Result: 'SGVsbG8gV29ybGQ=' * ``` * * @memberof ExportAsService */ removeFileTypeFromBase64(fileContent) { const re = /^data:[^]*;base64,/g; const newContent = re[Symbol.replace](fileContent, ''); return newContent; } /** * Adds a data URL prefix to a raw base64 string * * @description * Prepends the MIME type and base64 identifier to create a valid data URL * that can be used in browsers. * * @param {string} fileContent - Raw base64 encoded string * @param {string} fileMime - MIME type (e.g., "text/csv", "image/png") * @returns {string} Complete base64 data URL string * * @example * ```typescript * const base64 = 'SGVsbG8gV29ybGQ='; * const dataUrl = this.addFileTypeToBase64(base64, 'text/plain'); * // Result: 'data:text/plain;base64,SGVsbG8gV29ybGQ=' * ``` * * @memberof ExportAsService */ addFileTypeToBase64(fileContent, fileMime) { return `data:${fileMime};base64,${fileContent}`; } /** * Downloads a file from a data URL * * @description * Converts a base64 data URL to a Blob and initiates a browser download. * * @param {string} fileName - The name for the downloaded file (with extension) * @param {string} dataURL - Base64 data URL string * @returns {void} * * @example * ```typescript * const dataUrl = 'data:text/csv;base64,SGVsbG8gV29ybGQ='; * this.downloadFromDataURL('myfile.csv', dataUrl); * ``` * * @memberof ExportAsService */ downloadFromDataURL(fileName, dataURL) { // create blob this.contentToBlob(dataURL).subscribe(blob => { // download the blob this.downloadFromBlob(blob, fileName); }); } /** * Downloads a file from a Blob object * * @description * Handles file download from Blob with cross-browser support including legacy IE. * Creates an object URL and triggers the download appropriately for each browser. * * @param {Blob} blob - The Blob object containing file data * @param {string} fileName - The name for the downloaded file (with extension) * @returns {void} * * @example * ```typescript * const blob = new Blob(['Hello World'], { type: 'text/plain' }); * this.downloadFromBlob(blob, 'hello.txt'); * ``` * * @memberof ExportAsService */ downloadFromBlob(blob, fileName) { // get object url const url = window.URL.createObjectURL(blob); // check for microsoft internet explorer if (window.navigator && window.navigator['msSaveOrOpenBlob']) { // use IE download or open if the user using IE window.navigator['msSaveOrOpenBlob'](blob, fileName); } else { this.saveFile(fileName, url); } } /** * Creates and triggers a download link programmatically * * @private * @description * Creates a temporary anchor element, simulates a click to trigger download, * and then removes the element from the DOM. * * @param {string} fileName - The name for the downloaded file * @param {string} url - The object URL or data URL to download * @returns {void} * * @memberof ExportAsService */ saveFile(fileName, url) { // if not using IE then create link element const element = document.createElement('a'); // set download attr with file name element.setAttribute('download', fileName); // set the element as hidden element.style.display = 'none'; // append the body document.body.appendChild(element); // set href attr element.href = url; // click on it to start downloading element.click(); // remove the link from the dom document.body.removeChild(element); } /** * Exports content to PDF format * * @private * @description * Uses html2pdf.js library to convert HTML content to PDF. * Supports custom PDF manipulation via pdfCallbackFn option. * Accepts HTMLElement, Canvas, Image, or element ID as input. * * @param {ExportAsConfig} config - Export configuration with PDF-specific options * @returns {Observable<string | null>} Observable with base64 PDF data or null if downloading * * @example * ```typescript * // PDF with custom options * config.options = { * margin: 10, * filename: 'report.pdf', * image: { type: 'jpeg', quality: 0.98 }, * html2canvas: { scale: 2 }, * jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }, * pdfCallbackFn: (pdf) => { * const pageCount = pdf.internal.getNumberOfPages(); * for (let i = 1; i <= pageCount; i++) { * pdf.setPage(i); * pdf.text('Page ' + i, 10, 10); * } * } * }; * ``` * * @memberof ExportAsService */ getPDF(config) { return new Observable((observer) => { if (!config.options) { config.options = {}; } config.options.filename = config.fileName; const element = document.getElementById(config.elementIdOrContent); const pdf = html2pdf().set(config.options).from(element ? element : config.elementIdOrContent); const download = config.download; const pdfCallbackFn = config.options.pdfCallbackFn; if (download) { if (pdfCallbackFn) { this.applyPdfCallbackFn(pdf, pdfCallbackFn).save(); } else { pdf.save(); } observer.next(null); observer.complete(); } else { if (pdfCallbackFn) { this.applyPdfCallbackFn(pdf, pdfCallbackFn).outputPdf('datauristring').then(data => { observer.next(data); observer.complete(); }); } else { pdf.outputPdf('datauristring').then(data => { observer.next(data); observer.complete(); }); } } }); } /** * Applies a custom callback function to modify the PDF before output * * @private * @description * Allows custom modifications to the PDF object (headers, footers, page numbers, etc.) * before final rendering or download. * * @param {any} pdf - The html2pdf instance * @param {Function} pdfCallbackFn - Callback function to modify the PDF * @returns {Promise} Promise that resolves after callback execution * * @memberof ExportAsService */ applyPdfCallbackFn(pdf, pdfCallbackFn) { return pdf.toPdf().get('pdf').then((pdfRef) => { pdfCallbackFn(pdfRef); }); } /** * Exports content to PNG image format * * @private * @description * Uses html2canvas library to convert HTML element to PNG image. * Captures the visual representation of the specified DOM element. * * @param {ExportAsConfig} config - Export configuration with html2canvas options * @returns {Observable<string | null>} Observable with base64 PNG data or null if downloading * * @example * ```typescript * // PNG with custom options * config.options = { * scale: 2, // Higher quality * backgroundColor: '#ffffff', * logging: false, * useCORS: true, // For external images * allowTaint: false * }; * ``` * * @memberof ExportAsService */ getPNG(config) { return new Observable((observer) => { const element = document.getElementById(config.elementIdOrContent); html2canvas(element, config.options).then((canvas) => { const imgData = canvas.toDataURL('image/PNG'); if (config.type === 'png' && config.download) { this.downloadFromDataURL(config.fileName, imgData); observer.next(null); } else { observer.next(imgData); } observer.complete(); }, err => { observer.error(err); }); }); } /** * Exports HTML table to CSV format * * @private * @description * Extracts data from HTML table elements (th, td) and converts to CSV format. * Each row becomes a CSV line with comma-separated values. * Values are quoted to handle special characters. * * @param {ExportAsConfig} config - Export configuration (requires table element) * @returns {Observable<string | null>} Observable with base64 CSV data or null if downloading * * @example * ```typescript * // HTML table structure required * <table id="myTable"> * <tr><th>Name</th><th>Age</th></tr> * <tr><td>John</td><td>30</td></tr> * </table> * ``` * * @memberof ExportAsService */ getCSV(config) { return new Observable((observer) => { const element = document.getElementById(config.elementIdOrContent); const csv = []; const rows = element.querySelectorAll('table tr'); for (let index = 0; index < rows.length; index++) { const rowElement = rows[index]; const row = []; const cols = rowElement.querySelectorAll('td, th'); for (let colIndex = 0; colIndex < cols.length; colIndex++) { const col = cols[colIndex]; row.push('"' + col.innerText + '"'); } csv.push(row.join(',')); } const csvContent = 'data:text/csv;base64,' + this.btoa(csv.join('\n')); if (config.download) { this.downloadFromDataURL(config.fileName, csvContent); observer.next(null); } else { observer.next(csvContent); } observer.complete(); }); } /** * Exports HTML table to plain text format * * @private * @description * Reuses CSV export logic but saves as .txt file extension. * Outputs comma-separated values in plain text format. * * @param {ExportAsConfig} config - Export configuration (requires table element) * @returns {Observable<string | null>} Observable with base64 text data or null if downloading * * @memberof ExportAsService */ getTXT(config) { const nameFrags = config.fileName.split('.'); config.fileName = `${nameFrags[0]}.txt`; return this.getCSV(config); } /** * Exports HTML table to Microsoft Excel format (.xls) * * @private * @description * Uses SheetJS (xlsx library) to convert HTML table to Excel format. * Supports both legacy .xls and modern .xlsx formats. * The resulting file is compatible with Microsoft Excel and other spreadsheet applications. * * @param {ExportAsConfig} config - Export configuration with SheetJS options * @returns {Observable<string | null>} Observable with base64 Excel data or null if downloading * * @example * ```typescript * // Excel with custom options * config.options = { * bookType: 'xlsx', * sheet: 'Sheet1', * raw: false, * dateNF: 'yyyy-mm-dd' * }; * ``` * * @memberof ExportAsService */ getXLS(config) { return new Observable((observer) => { const element = document.getElementById(config.elementIdOrContent); const ws3 = XLSX.utils.table_to_sheet(element, config.options); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws3, config.fileName); const out = XLSX.write(wb, { type: 'base64' }); const xlsContent = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,' + out; if (config.download) { this.downloadFromDataURL(config.fileName, xlsContent); observer.next(null); } else { observer.next(xlsContent); } observer.complete(); }); } /** * Exports HTML table to Microsoft Excel format (.xlsx) * * @private * @description * Alias for getXLS method. Both .xls and .xlsx use the same underlying implementation * via SheetJS library. * * @param {ExportAsConfig} config - Export configuration with SheetJS options * @returns {Observable<string | null>} Observable with base64 Excel data or null if downloading * * @memberof ExportAsService */ getXLSX(config) { return this.getXLS(config); } // private getDOCX(config: ExportAsConfig): Observable<string | null> { // return new Observable((observer) => { // const contentDocument: string = document.getElementById(config.elementIdOrContent).outerHTML; // const content = '<!DOCTYPE html>' + contentDocument; // HTMLtoDOCX(content, null, config.options).then(converted => { // if (config.download) { // this.downloadFromBlob(converted, config.fileName); // observer.next(); // observer.complete(); // } else { // const reader = new FileReader(); // reader.onloadend = () => { // const base64data = reader.result as string; // observer.next(base64data); // observer.complete(); // }; // reader.readAsDataURL(converted); // } // }); // }); // } // private getDOC(config: ExportAsConfig): Observable<string | null> { // return this.getDOCX(config); // } /** * Exports HTML table to JSON format * * @private * @description * Converts HTML table to JSON array of objects. * First row is treated as headers (keys), subsequent rows as data values. * Headers are normalized (lowercase, spaces removed). * * **Note:** Unlike other formats, this returns actual JSON objects, not base64. * * @param {ExportAsConfig} config - Export configuration (requires table element) * @returns {Observable<any[] | null>} Observable with JSON array or null if downloading * * @example * ```typescript * // HTML table: * // | Name | Age | * // | John | 30 | * // | Jane | 25 | * * // Result: * [ * { "name": "John", "age": "30" }, * { "name": "Jane", "age": "25" } * ] * ``` * * @memberof ExportAsService */ getJSON(config) { return new Observable((observer) => { const data = []; // first row needs to be headers const headers = []; const table = document.getElementById(config.elementIdOrContent); for (let index = 0; index < table.rows[0].cells.length; index++) { headers[index] = table.rows[0].cells[index].innerHTML.toLowerCase().replace(/ /gi, ''); } // go through cells for (let i = 1; i < table.rows.length; i++) { const tableRow = table.rows[i]; const rowData = {}; for (let j = 0; j < tableRow.cells.length; j++) { rowData[headers[j]] = tableRow.cells[j].innerHTML; } data.push(rowData); } const jsonString = JSON.stringify(data); const jsonBase64 = this.btoa(jsonString); const dataStr = 'data:text/json;base64,' + jsonBase64; if (config.download) { this.downloadFromDataURL(config.fileName, dataStr); observer.next(null); } else { observer.next(data); } observer.complete(); }); } /** * Exports HTML table to XML format * * @private * @description * Converts HTML table to XML structure. * First cell of each row becomes the class name attribute, * remaining cells become data elements. * * @param {ExportAsConfig} config - Export configuration (requires table element) * @returns {Observable<string | null>} Observable with base64 XML data or null if downloading * * @example * ```typescript * // HTML table: * // | Name | Age | City | * // | John | 30 | NYC | * // | Jane | 25 | Boston | * * // Result: * <?xml version="1.0" encoding="UTF-8"?> * <Root> * <Classes> * <Class name="John"> * <data>30</data> * <data>NYC</data> * </Class> * <Class name="Jane"> * <data>25</data> * <data>Boston</data> * </Class> * </Classes> * </Root> * ``` * * @memberof ExportAsService */ getXML(config) { return new Observable((observer) => { let xml = '<?xml version="1.0" encoding="UTF-8"?><Root><Classes>'; const tritem = document.getElementById(config.elementIdOrContent).getElementsByTagName('tr'); for (let i = 0; i < tritem.length; i++) { const celldata = tritem[i]; if (celldata.cells.length > 0) { xml += '<Class name="' + celldata.cells[0].textContent + '">\n'; for (let m = 1; m < celldata.cells.length; ++m) { xml += '\t<data>' + celldata.cells[m].textContent + '</data>\n'; } xml += '</Class>\n'; } } xml += '</Classes></Root>'; const base64 = 'data:text/xml;base64,' + this.btoa(xml); if (config.download) { this.downloadFromDataURL(config.fileName, base64); observer.next(null); } else { observer.next(base64); } observer.complete(); }); } /** * Encodes a string to base64 with UTF-8 support * * @private * @description * Wrapper around browser's btoa() with proper UTF-8 encoding. * Handles special characters and international text correctly. * Uses encodeURIComponent and unescape for proper character encoding. * * @param {string} content - The string content to encode * @returns {string} Base64 encoded string * * @memberof ExportAsService */ btoa(content) { return btoa(unescape(encodeURIComponent(content))); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ExportAsService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ExportAsService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: ExportAsService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }] }); /* * Public API Surface of ngx-export-as */ /** * Generated bundle index. Do not edit. */ export { ExportAsService }; //# sourceMappingURL=ngx-export-as.mjs.map