--- title: Sheets in Angular Sites sidebar_label: Angular description: Build interactive websites with Angular. Seamlessly integrate spreadsheets into your app using SheetJS. Bring Excel-powered workflows and data to the modern web. pagination_prev: demos/index pagination_next: demos/grid/index sidebar_position: 3 --- import current from '/version.js'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import CodeBlock from '@theme/CodeBlock'; Angular is a JS library for building user interfaces.[^1] [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing data from spreadsheets. This demo uses Angular and SheetJS to process and generate spreadsheets. We'll explore how to load SheetJS in Angular projects and compare common state models and data flow strategies. :::note pass This demo focuses on Angular concepts. Other demos cover general deployments: - [iOS and Android applications powered by NativeScript](/docs/demos/mobile/nativescript) - [iOS and Android applications powered by Ionic](/docs/demos/mobile/ionic) ::: :::caution pass Angular tooling uses native NodeJS modules. There are a number of issues when trying to run Angular projects with different NodeJS versions. These issues should be directed to the Angular project. ::: :::danger Telemetry Angular CLI enables telemetry by default. When using a recent version, disable analytics globally through the CLI tool before creating a new project: ```bash npx @angular/cli analytics disable -g ``` (If prompted to share data, type `N` and press Enter) ::: ## Installation [The "Frameworks" section](/docs/getting-started/installation/frameworks) covers installation with `pnpm` and other package managers. The library can be imported directly from JS or TS code with: ```js import { read, utils, writeFile } from 'xlsx'; ``` ## Internal State The various SheetJS APIs work with various data shapes. The preferred state depends on the application. :::danger pass Angular 17 broke backwards compatibility with projects using Angular 2 - 16. **Despite the Angular turmoil, SheetJS plays nice with each version of Angular**. When relevant, code snippets for Angular 17 and Angular 2 - 16 are included. The "Angular 2-16" and "Angular 17+" tabs change the displayed code blocks ::: ### Array of Objects Typically, some users will create a spreadsheet with source data that should be loaded into the site. This sheet will have known columns. #### State The example [presidents sheet](https://docs.sheetjs.com/pres.xlsx) has one header row with "Name" and "Index" columns. The natural JS representation is an object for each row, using the values in the first rows as keys:
SpreadsheetState
![`pres.xlsx` data](pathname:///pres.png) ```js [ { Name: "Bill Clinton", Index: 42 }, { Name: "GeorgeW Bush", Index: 43 }, { Name: "Barack Obama", Index: 44 }, { Name: "Donald Trump", Index: 45 }, { Name: "Joseph Biden", Index: 46 } ] ```
This data is typically stored as an array of objects in the component class: ```ts import { Component } from '@angular/core'; @Component({ /* ... component configuration options ... */ }) export class AppComponent { /* the component state is an array of objects */ // highlight-next-line rows: any[] = [ { Name: "SheetJS", Index: 0 }]; } ``` When the spreadsheet header row is known ahead of time, row typing is possible: ```ts import { Component } from '@angular/core'; interface President { Name: string; Index: number; } @Component({ /* ... component configuration options ... */ }) export class AppComponent { /* the component state is an array of presidents */ // highlight-next-line rows: President[] = [ { Name: "SheetJS", Index: 0 }]; } ``` :::caution pass The types are informative. They do not enforce that worksheets include the named columns. A runtime data validation library should be used to verify the dataset. When the file header is not known in advance, `any` should be used. ::: #### Updating State The SheetJS [`read`](/docs/api/parse-options) and [`sheet_to_json`](/docs/api/utilities/array#array-output) functions simplify state updates. They are best used in the function bodies of `ngOnInit`[^2] and event handlers. A `ngOnInit` method can download and update state when a person loads the site: ```mermaid flowchart LR url[(Remote\nFile)] ab[(Data\nArrayBuffer)] wb(SheetJS\nWorkbook) ws(SheetJS\nWorksheet) aoo(array of\nobjects) state((component\nstate)) url --> |fetch\n\n| ab ab --> |read\n\n| wb wb --> |wb.Sheets\nselect sheet| ws ws --> |sheet_to_json\n\n| aoo aoo --> |setPres\nfrom `setState`| state ``` ```ts import { Component } from '@angular/core'; import { read, utils } from 'xlsx'; interface President { Name: string; Index: number }; @Component({ /* ... component configuration options ... */ }) export class AppComponent { rows: President[] = [ { Name: "SheetJS", Index: 0 }]; ngOnInit(): void { (async() => { /* Download from https://docs.sheetjs.com/pres.numbers */ const f = await fetch("https://docs.sheetjs.com/pres.numbers"); const ab = await f.arrayBuffer(); // highlight-start /* parse workbook */ const wb = read(ab); /* generate array of objects from first worksheet */ const ws = wb.Sheets[wb.SheetNames[0]]; // get the first worksheet const data = utils.sheet_to_json(ws); // generate objects /* update data */ this.rows = data; // highlight-end })(); } } ``` #### Rendering Data Components typically render HTML tables from arrays of objects. The `` table row elements are typically generated by mapping over the state array, as shown in the example template. :::caution pass Angular 2 - 16 recommended using `ngFor`[^3]. Angular 17 no longer supports the storied syntax, instead opting for a `@for` block reminiscent of JavaScript[^4]. ::: ```html title="Example Template for displaying arrays of objects (Angular 2-16)"
// highlight-start // highlight-end
NameIndex
{{row.Name}} {{row.Index}}
```
```html title="Example Template for displaying arrays of objects (Angular 17+)"
// highlight-start @for(row of rows; track $index) { } // highlight-end
NameIndex
{{row.Name}} {{row.Index}}
```
#### Exporting Data The [`writeFile`](/docs/api/write-options) and [`json_to_sheet`](/docs/api/utilities/array#array-of-objects-input) functions simplify exporting data. They are best used in the function bodies of event handlers attached to button or other elements. A callback can generate a local file when a user clicks a button: ```mermaid flowchart LR state((component\nstate)) ws(SheetJS\nWorksheet) wb(SheetJS\nWorkbook) file[(XLSX\nexport)] state --> |json_to_sheet\n\n| ws ws --> |book_new\nbook_append_sheet| wb wb --> |writeFile\n\n| file ``` ```ts title="src/app/app.component.ts" import { Component } from '@angular/core'; import { utils, writeFileXLSX } from 'xlsx'; interface President { Name: string; Index: number }; @Component({ /* ... component configuration options ... */ }) export class AppComponent { rows: President[] = [ { Name: "SheetJS", Index: 0 }]; /* get state data and export to XLSX */ onSave(): void { /* generate worksheet from state */ // highlight-next-line const ws = utils.json_to_sheet(this.rows); /* create workbook and append worksheet */ const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Data"); /* export to XLSX */ writeFileXLSX(wb, "SheetJSAngularAoO.xlsx"); } } ``` #### Complete Component This complete component example fetches a test file and displays the contents in a HTML table. When the export button is clicked, a callback will export a file: ```ts title="src/app/app.component.ts" import { Component } from '@angular/core'; import { read, utils, writeFileXLSX } from 'xlsx'; interface President { Name: string; Index: number }; @Component({ selector: 'app-root', template: `
// highlight-start // highlight-end
NameIndex
{{row.Name}} {{row.Index}}
` }) export class AppComponent { // highlight-next-line rows: President[] = [ { Name: "SheetJS", Index: 0 }]; ngOnInit(): void { (async() => { /* Download from https://docs.sheetjs.com/pres.numbers */ const f = await fetch("https://docs.sheetjs.com/pres.numbers"); const ab = await f.arrayBuffer(); /* parse workbook */ // highlight-next-line const wb = read(ab); /* update data */ // highlight-next-line this.rows = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); })(); } /* get state data and export to XLSX */ onSave(): void { // highlight-next-line const ws = utils.json_to_sheet(this.rows); const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Data"); writeFileXLSX(wb, "SheetJSAngularAoO.xlsx"); } } ```
```ts title="src/app/app.component.ts" import { Component } from '@angular/core'; import { read, utils, writeFileXLSX } from 'xlsx'; interface President { Name: string; Index: number }; @Component({ selector: 'app-root', standalone: true, template: `
// highlight-start @for(row of rows; track $index) { } // highlight-end
NameIndex
{{row.Name}} {{row.Index}}
` }) export class AppComponent { // highlight-next-line rows: President[] = [ { Name: "SheetJS", Index: 0 }]; ngOnInit(): void { (async() => { /* Download from https://docs.sheetjs.com/pres.numbers */ const f = await fetch("https://docs.sheetjs.com/pres.numbers"); const ab = await f.arrayBuffer(); /* parse workbook */ // highlight-next-line const wb = read(ab); /* update data */ // highlight-next-line this.rows = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); })(); } /* get state data and export to XLSX */ onSave(): void { // highlight-next-line const ws = utils.json_to_sheet(this.rows); const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Data"); writeFileXLSX(wb, "SheetJSAngularAoO.xlsx"); } } ```
How to run the example (click to hide) :::note Tested Deployments This demo was tested in the following environments: | Angular | Date | |:----------|:-----------| | `17.3.0` | 2024-03-13 | | `16.2.12` | 2024-03-13 | ::: 0) Disable telemetry: ```bash npx @angular/cli analytics disable -g ``` 1) Create a new project: ```bash npx @angular/cli@17.3.0 new --minimal --defaults --no-interactive sheetjs-angular ``` :::note pass The `@angular/cli` version controls the project version of Angular. For example, the following command uses Angular 16.2.12: ```bash npx @angular/cli@16.2.12 new --minimal --defaults --no-interactive sheetjs-angular ``` ::: 2) Install the SheetJS dependency and start the dev server: {`\ cd sheetjs-angular npm i npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz npx @angular/cli analytics disable npm start`} 3) Open a web browser and access the displayed URL (`http://localhost:4200`) 4) In the previous `src/app/app.component.ts` code snippet, select the tab for the appropriate version of Angular ("Angular 2-16" or "Angular 17+"), copy the code contents and replace `src/app/app.component.ts` in the project. The page will refresh and show a table with an Export button. Click the button and the page will attempt to download `SheetJSAngularAoO.xlsx`. Open the file with a spreadsheet editor. 5) Stop the dev server and build the site: ```bash npm run build ``` To test the generated site, start a web server: ```bash npx -y http-server dist/sheetjs-angular/ ``` ```bash npx -y http-server dist/sheetjs-angular/browser/ ``` Access the displayed URL (typically `http://localhost:8080`) with a web browser to test the bundled site.
### HTML The main disadvantage of the Array of Objects approach is the specific nature of the columns. For more general use, passing around an Array of Arrays works. However, this does not handle merge cells[^5] well! The `sheet_to_html` function generates HTML that is aware of merges and other worksheet features. The generated HTML does not contain any `