3

I am trying to export JSON data to excel which has a dropdown list.

Is it possible to create the dropdown(with options) in excel if we pass an array as a cell value?

I am trying to use the inbuilt library XLSX to achieve this but not able to see data if I pass the array in the cell value.

Update: Jack Provided one library ExcelJS that supports the data validation but needs to do some configurations for that.

Is there any Angular Library that supports this feature?

Below is an example of the project which I tried. I would like to append multiple sheets based on data passed to the service and names of sheets will be taken from the objects.

https://stackblitz.com/edit/angular6-export-xlsx-b4al4p

Bravo
  • 61
  • 2
  • 7
  • 26
  • 1
    unfortunately I have never used xlsx library. I have tried to go through the docs on how to add validation using the library but I seem not to find anything. I believe the solution is around adding validation but how to do it I have no clue – Owen Kelvin Apr 16 '21 at 19:02
  • Are there any other libraries that support data validation? – Bravo Apr 16 '21 at 19:03
  • 1
    Currently I unfortunately do not have any suggestion – Owen Kelvin Apr 16 '21 at 19:11
  • @Owen Found one library (ExcelJS) which supports this but needs to manipulate data to be sent. – Bravo Apr 21 '21 at 23:08
  • I recommend this post, it works using the same library XLSX https://stackoverflow.com/questions/60149667/how-to-add-data-validation-list-in-excel-using-javascript – Rene Arias Apr 24 '21 at 00:18

3 Answers3

3

This is just an addition to @ZackReam solution with a focus on your current scenario

To begin, we first define a data structure

  workbookData = [
    {
      workSheet: "data 1",
      rows: [
        { eid: "1", ename: "John", esal: ["val 1", "val2", "val 3"] },
        { eid: "4", ename: "Parker", esal: ["val 1", "val2", "val 3"] },
        { eid: "5", ename: "Iron", esal: ["val 1", "val2", "val 3"] }
      ]
    },
    {
      workSheet: "data 2",
      rows: [
        { eid: "9", ename: "Doe", esal: ["val 1", "val2", "val 3"] },
        { eid: "10", ename: "Peter", esal: ["val 1", "val2", "val 3"] },
        { eid: "11", ename: "Man", esal: ["val 1", "val2", "val 3"] }
      ]
    }

Next we define a service to generate a workbook dynamically from the above data

import { Injectable } from "@angular/core";
import * as FileSaver from "file-saver";
import * as ExcelJS from "exceljs/dist/exceljs.min.js";

const EXCEL_TYPE =
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8";
const EXCEL_EXTENSION = ".xlsx";

@Injectable()
export class ExcelService {
  constructor() {}

  public async exportAsExcelFile(workbookData: any[], excelFileName: string) {
    const workbook = new ExcelJS.Workbook();

    workbookData.forEach(({ workSheet, rows }) => {
      const sheet = workbook.addWorksheet(workSheet);
      const uniqueHeaders = [
        ...new Set(
          rows.reduce((prev, next) => [...prev, ...Object.keys(next)], [])
        )
      ];
      sheet.columns = uniqueHeaders.map(x => ({ header: x, key: x }));

      rows.forEach((jsonRow, i) => {
        let cellValues = { ...jsonRow };

        uniqueHeaders.forEach((header, j) => {
          if (Array.isArray(jsonRow[header])) {
            cellValues[header] = "";
          }
        });
        sheet.addRow(cellValues);
        uniqueHeaders.forEach((header, j) => {
          if (Array.isArray(jsonRow[header])) {
            const jsonDropdown = jsonRow.esal;
            sheet.getCell(
              this.getSpreadSheetCellNumber(i + 1, j)
            ).dataValidation = {
              type: "list",
              formulae: [`"${jsonDropdown.join(",")}"`]
            };
          }
        });
      });
    });

    const buffer = await workbook.xlsx.writeBuffer();
    this.saveAsExcelFile(buffer, excelFileName);
  }

  private getSpreadSheetCellNumber(row, column) {
    let result = "";

    // Get spreadsheet column letter
    let n = column;
    while (n >= 0) {
      result = String.fromCharCode((n % 26) + 65) + result;
      n = Math.floor(n / 26) - 1;
    }

    // Get spreadsheet row number
    result += `${row + 1}`;

    return result;
  }

  private saveAsExcelFile(buffer: any, fileName: string): void {
    const data: Blob = new Blob([buffer], {
      type: EXCEL_TYPE
    });
    FileSaver.saveAs(
      data,
      fileName + "_export_" + new Date().getTime() + EXCEL_EXTENSION
    );
  }
}

The service will dynamically determine the headers and the columns to set as validation

Transforming the data

We can transform your data to this structure using below

  transform (data) {
    const noOfRowaToGenerate = 10;
    return data.map(({name, values}) => {
      const headers = values.reduce((prev, next) => 
        ({...prev, [next.header]: Array.isArray
        (next.value) ? next.value.map(({name}) => name): next.value}), {})
      return {
        workSheet: name,
        rows: Array(noOfRowaToGenerate).fill(headers)
      }
    })
  }
   workbookData = this.transform(this.data1)

Below is a sample demo

Sample Demo

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
  • Thanks a lot, Owen.. just wondering what changes I need to do to support the structure like this where the header is coming from object data1: any = [ { name: "data1", //sheet1 with name data1 values: [ { header: "eid", value: "" }, { header: "test", value: [{ name: "test1" }, { name: "test2" }] }, { header: "ename", value: "" }, { header: "esal", value: [{ name: "val" }, { name: "val1" }] } ] } ]; – Bravo Apr 22 '21 at 13:27
  • 1
    Will have a look in the morning and get back – Owen Kelvin Apr 22 '21 at 20:12
  • this is a very good solution for the Bravo issue – Rene Arias Apr 24 '21 at 00:09
  • Hi Owen, Can you please help me with this question? https://stackoverflow.com/questions/67571001/how-to-add-frozen-columns-to-primeng-when-rows-are-rendering-dynamically-in-angu – Bravo May 17 '21 at 13:49
2

According to this issue in SheetJS (package: xlsx), data validation is a pro-only feature.

We offer this in the Pro compendium. Since companies have paid for the features already, it would be unfair to them if we turned around and made it available as open source.


Another library you could check out is ExcelJS (package: exceljs). It runs in the browser and has support for data validation. You would have to do a bit of manual mapping of your data to get your data in the format that the library expects, but it is possible.

Here is an example Stackblitz.

Zack Ream
  • 2,956
  • 7
  • 18
  • Thanks a lot, Just One question if I have a data structure like, How I can create excel dynamically based on the value have an array of objects? data: any = [ { header: "eid", value: "" }, { header: "test", value: [ { name: "test1" }, { name: "test2" } ] }, { header: "ename", value: "" }, { header: "esal", value: [ { name: "val" }, { name: "val1" } ] } ]; – Bravo Apr 19 '21 at 14:21
  • In this example excel header will as eid,test,ename, and esal and next row have dropdown validations if the value has an array. – Bravo Apr 19 '21 at 14:24
  • 1
    @Bravo I have checked demo provided by ZackReam and it is working fantastically. How would you like me to help? – Owen Kelvin Apr 22 '21 at 06:48
  • I have modified data where object have header and data and dropdown values (if available) as well as multiple sheets. – Bravo Apr 22 '21 at 10:31
  • It is available in my stackblitz. – Bravo Apr 22 '21 at 10:35
1

You can try do it this works using the same library exceljs and filesaver.js from you example

import { Workbook } from 'exceljs';
import * as fs from 'file-saver';
generateExcel(list,header) {
  let data:any = [];
  for(let i=0;i<list.length;i++){
    let arr = [list[i].requisitionId,list[i].applicationid, list[i].candidateid, list[i].unitcode];
    data.push(arr);
  }
  console.log(data);
  //Create workbook and worksheet
  let workbook = new Workbook();
  let worksheet = workbook.addWorksheet('Candidate Report');

  //Add Header Row
  let headerRow = worksheet.addRow(header);

  // Cell Style : Fill and Border
  headerRow.eachCell((cell, number) => {
    cell.fill = {
      type: 'pattern',
      pattern: 'solid',
      fgColor: { argb: 'FFFFFF00' },
      bgColor: { argb: 'FF0000FF' }
    }
    cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }
  })
  worksheet.getColumn(3).width = 30;
  data.forEach(d => {
    let row = worksheet.addRow(d);
  }
  );
  list.forEach((element,index) =>{
    worksheet.getCell('E'+(+index+2)).dataValidation = {
      type: 'list',
      allowBlank: true,
      formulae: ['"Selected,Rejected,On-hold"']
  };
  })
  //Generate Excel File with given name
  workbook.xlsx.writeBuffer().then((data) => {
    let blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    fs.saveAs(blob, 'candidate.xlsx');
  })

}

My reference is this How to add data validation list in excel using javascript

Rene Arias
  • 164
  • 1
  • 7