forked from sheetjs/docs.sheetjs.com
		
	ng17
This commit is contained in:
		
							parent
							
								
									a4ad3195f5
								
							
						
					
					
						commit
						8f9512b217
					
				| @ -1,5 +1,7 @@ | ||||
| --- | ||||
| title: Angular | ||||
| 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 | ||||
| @ -10,18 +12,24 @@ import Tabs from '@theme/Tabs'; | ||||
| import TabItem from '@theme/TabItem'; | ||||
| import CodeBlock from '@theme/CodeBlock'; | ||||
| 
 | ||||
| Angular is a JS library for building user interfaces. | ||||
| Angular is a JS library for building user interfaces.[^1] | ||||
| 
 | ||||
| This demo tries to cover common Angular data flow ideas and strategies. Angular | ||||
| and TypeScript familiarity is assumed. | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| **SheetJS plays nice with each version of Angular**. | ||||
| 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. | ||||
| 
 | ||||
| Other demos cover general Angular deployments, including: | ||||
| :::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 | ||||
| @ -61,31 +69,33 @@ import { read, utils, writeFile } from 'xlsx'; | ||||
| The various SheetJS APIs work with various data shapes.  The preferred state | ||||
| depends on the application. | ||||
| 
 | ||||
| :::warning 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.  For example, our | ||||
| [presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns: | ||||
| loaded into the site.  This sheet will have known columns. | ||||
| 
 | ||||
| #### State | ||||
| 
 | ||||
| The example [presidents sheet](https://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: | ||||
| 
 | ||||
| <table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| This naturally maps to an array of typed objects, as in the TS example below: | ||||
| 
 | ||||
| ```ts | ||||
| import { read, utils } from 'xlsx'; | ||||
| 
 | ||||
| interface President { | ||||
|   Name: string; | ||||
|   Index: number; | ||||
| } | ||||
| 
 | ||||
| const f = await (await fetch("https://sheetjs.com/pres.xlsx")).arrayBuffer(); | ||||
| const wb = read(f); | ||||
| const data = utils.sheet_to_json<President>(wb.Sheets[wb.SheetNames[0]]); | ||||
| console.log(data); | ||||
| ``` | ||||
| 
 | ||||
| `data` will be an array of objects: | ||||
| </td><td> | ||||
| 
 | ||||
| ```js | ||||
| [ | ||||
| @ -97,8 +107,199 @@ console.log(data); | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| A component will typically loop over the data using `*ngFor`. The following | ||||
| example generates a TABLE with a row for each President: | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| 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://sheetjs.com/pres.numbers */ | ||||
|     const f = await fetch("https://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<President>(ws); // generate objects | ||||
| 
 | ||||
|     /* update data */ | ||||
|     this.rows = data; | ||||
|     // highlight-end | ||||
|   })(); } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### Rendering Data | ||||
| 
 | ||||
| Components typically render HTML tables from arrays of objects. The `<tr>` 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]. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| <Tabs groupId="ngVer"> | ||||
|   <TabItem value="2" label="Angular 2-16"> | ||||
| 
 | ||||
| ```html title="Example Template for displaying arrays of objects (Angular 2-16)" | ||||
| <div class="content" role="main"><table> | ||||
|   <thead><th>Name</th><th>Index</th></thead> | ||||
|   <tbody> | ||||
| // highlight-start | ||||
|     <tr *ngFor="let row of rows"> | ||||
|       <td>{{row.Name}}</td> | ||||
|       <td>{{row.Index}}</td> | ||||
|     </tr> | ||||
| // highlight-end | ||||
|   </tbody> | ||||
| </table></div> | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="17" label="Angular 17+"> | ||||
| 
 | ||||
| ```html title="Example Template for displaying arrays of objects (Angular 17+)" | ||||
| <div class="content" role="main"><table> | ||||
|   <thead><th>Name</th><th>Index</th></thead> | ||||
|   <tbody> | ||||
| // highlight-start | ||||
|   @for(row of rows; track $index) { <tr> | ||||
|       <td>{{row.Name}}</td> | ||||
|       <td>{{row.Index}}</td> | ||||
|   </tr> } | ||||
| // highlight-end | ||||
|   </tbody> | ||||
| </table></div> | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| #### 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: | ||||
| 
 | ||||
| <Tabs groupId="ngVer"> | ||||
|   <TabItem value="2" label="Angular 2-16"> | ||||
| 
 | ||||
| ```ts title="src/app/app.component.ts" | ||||
| import { Component } from '@angular/core'; | ||||
| @ -152,11 +353,72 @@ export class AppComponent { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="17" label="Angular 17+"> | ||||
| 
 | ||||
| ```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: ` | ||||
| <div class="content" role="main"><table> | ||||
|   <thead><th>Name</th><th>Index</th></thead> | ||||
|   <tbody> | ||||
| // highlight-start | ||||
|   @for(row of rows; track $index) { | ||||
|     <tr> | ||||
|       <td>{{row.Name}}</td> | ||||
|       <td>{{row.Index}}</td> | ||||
|     </tr> | ||||
|   } | ||||
| // highlight-end | ||||
|   </tbody><tfoot> | ||||
|     <button (click)="onSave()">Export XLSX</button> | ||||
|   </tfoot> | ||||
| </table></div> | ||||
| ` | ||||
| }) | ||||
| export class AppComponent { | ||||
|   // highlight-next-line | ||||
|   rows: President[] = [ { Name: "SheetJS", Index: 0 }]; | ||||
|   ngOnInit(): void { (async() => { | ||||
|     /* Download from https://sheetjs.com/pres.numbers */ | ||||
|     const f = await fetch("https://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<President>(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"); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| <details open><summary><b>How to run the example</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| :::note Tested Deployments | ||||
| 
 | ||||
| This demo was last run on 2023-10-22 using Angular CLI `16.2.7` | ||||
| This demo was last run on 2023-11-18 using Angular 17.0.3 and CLI `17.0.1` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -169,7 +431,7 @@ npx @angular/cli analytics disable -g | ||||
| 1) Create a new project: | ||||
| 
 | ||||
| ```bash | ||||
| npx @angular/cli@16.2.7 new --minimal --defaults --no-interactive sheetjs-angular | ||||
| npx @angular/cli@17.0.1 new --minimal --defaults --no-interactive sheetjs-angular | ||||
| ``` | ||||
| 
 | ||||
| 2) Install the SheetJS dependency and start the dev server: | ||||
| @ -184,7 +446,9 @@ npm start`} | ||||
| 
 | ||||
| 3) Open a web browser and access the displayed URL (`http://localhost:4200`) | ||||
| 
 | ||||
| 4) Replace `src/app/app.component.ts` with the previous code snippet. | ||||
| 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 | ||||
| @ -198,10 +462,23 @@ npm run build | ||||
| 
 | ||||
| To test the generated site, start a web server: | ||||
| 
 | ||||
| <Tabs groupId="ngVer"> | ||||
|   <TabItem value="2" label="Angular 2-16"> | ||||
| 
 | ||||
| ```bash | ||||
| npx -y http-server dist/sheetjs-angular/ | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="17" label="Angular 17+"> | ||||
| 
 | ||||
| ```bash | ||||
| npx -y http-server dist/sheetjs-angular/browser/ | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| Access `http://localhost:8080` with a web browser to test the bundled site. | ||||
| 
 | ||||
| </details> | ||||
| @ -215,7 +492,10 @@ However, this does not handle merge cells well! | ||||
| The `sheet_to_html` function generates HTML that is aware of merges and other | ||||
| worksheet features.  The generated HTML does not contain any `<script>` tags, | ||||
| and should therefore be safe to pass to an `innerHTML`-bound variable, but the | ||||
| `DomSanitizer` approach is strongly recommended: | ||||
| `DomSanitizer` approach[^5] is strongly recommended: | ||||
| 
 | ||||
| <Tabs groupId="ngVer"> | ||||
|   <TabItem value="2" label="Angular 2-16"> | ||||
| 
 | ||||
| ```ts title="src/app/app.component.ts" | ||||
| import { Component, ElementRef, ViewChild } from '@angular/core'; | ||||
| @ -260,11 +540,61 @@ export class AppComponent { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="17" label="Angular 17+"> | ||||
| 
 | ||||
| ```ts title="src/app/app.component.ts" | ||||
| import { Component, ElementRef, ViewChild } from '@angular/core'; | ||||
| // highlight-next-line | ||||
| import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; | ||||
| import { read, utils, writeFileXLSX } from 'xlsx'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-root', | ||||
|   standalone: true, | ||||
| // highlight-next-line | ||||
|   template: `<div class="content" role="main" [innerHTML]="html" #tableau></div> | ||||
|     <button (click)="onSave()">Export XLSX</button>` | ||||
| }) | ||||
| export class AppComponent { | ||||
|   // highlight-start | ||||
|   constructor(private sanitizer: DomSanitizer) {} | ||||
|   html: SafeHtml = ""; | ||||
|   @ViewChild('tableau') tabeller!: ElementRef<HTMLDivElement>; | ||||
|   // highlight-end | ||||
|   ngOnInit(): void { (async() => { | ||||
|     /* Download from https://sheetjs.com/pres.numbers */ | ||||
|     const f = await fetch("https://sheetjs.com/pres.numbers"); | ||||
|     const ab = await f.arrayBuffer(); | ||||
| 
 | ||||
|     /* parse workbook */ | ||||
|     const wb = read(ab); | ||||
| 
 | ||||
|     /* update data */ | ||||
|     // highlight-start | ||||
|     const h = utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
|     this.html = this.sanitizer.bypassSecurityTrustHtml(h); | ||||
|     // highlight-end | ||||
|   })(); } | ||||
|   /* get live table and export to XLSX */ | ||||
|   onSave(): void { | ||||
|     // highlight-start | ||||
|     const elt = this.tabeller.nativeElement.getElementsByTagName("TABLE")[0]; | ||||
|     const wb = utils.table_to_book(elt); | ||||
|     // highlight-end | ||||
|     writeFileXLSX(wb, "SheetJSAngularHTML.xlsx"); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| <details open><summary><b>How to run the example</b> (click to show)</summary> | ||||
| 
 | ||||
| :::note | ||||
| :::note Tested Deployments | ||||
| 
 | ||||
| This demo was last run on 2023-10-22 using Angular CLI `16.2.7` | ||||
| This demo was last run on 2023-11-18 using Angular 17.0.3 and CLI `17.0.1` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -277,7 +607,7 @@ npx @angular/cli analytics disable -g | ||||
| 1) Create a new project: | ||||
| 
 | ||||
| ```bash | ||||
| npx @angular/cli@16.2.7 new --minimal --defaults --no-interactive sheetjs-angular | ||||
| npx @angular/cli@17.0.1 new --minimal --defaults --no-interactive sheetjs-angular | ||||
| ``` | ||||
| 
 | ||||
| 2) Install the SheetJS dependency and start the dev server: | ||||
| @ -292,7 +622,9 @@ npm start`} | ||||
| 
 | ||||
| 3) Open a web browser and access the displayed URL (`http://localhost:4200`) | ||||
| 
 | ||||
| 4) Replace `src/app/app.component.ts` with the previous code snippet. | ||||
| 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 `SheetJSAngularHTML.xlsx`. Open the file | ||||
| @ -306,10 +638,23 @@ npm run build | ||||
| 
 | ||||
| To test the generated site, start a web server: | ||||
| 
 | ||||
| <Tabs groupId="ngVer"> | ||||
|   <TabItem value="2" label="Angular 2-16"> | ||||
| 
 | ||||
| ```bash | ||||
| npx -y http-server dist/sheetjs-angular/ | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
|   <TabItem value="17" label="Angular 17+"> | ||||
| 
 | ||||
| ```bash | ||||
| npx -y http-server dist/sheetjs-angular/browser/ | ||||
| ``` | ||||
| 
 | ||||
|   </TabItem> | ||||
| </Tabs> | ||||
| 
 | ||||
| Access `http://localhost:8080` with a web browser to test the bundled site. | ||||
| 
 | ||||
| </details> | ||||
| @ -654,4 +999,10 @@ will have an `ng-version` attribute. | ||||
| 
 | ||||
| ```bash | ||||
| npm run build | ||||
| ``` | ||||
| ``` | ||||
| 
 | ||||
| [^1]: The main website for Angular versions 2-16 is <https://angular.io/> . The project moved to a new domain <https://angular.dev/> during the Angular 17 launch. | ||||
| [^2]: See `OnInit` in the [Angular 2-16 docs](https://angular.io/api/core/OnInit) or [Angular 17 docs](https://angular.dev/guide/components/lifecycle#ngoninit) | ||||
| [^3]: See [`ngFor`](https://angular.io/api/common/NgFor) in the Angular 2-16 docs. | ||||
| [^4]: See [`@for`](https://angular.dev/api/core/@for) in the Angular 17 docs. | ||||
| [^5]: See `DomSanitizer` in the [Angular 2-16 docs](https://angular.io/api/platform-browser/DomSanitizer) or [Angular 17 docs](https://angular.dev/api/platform-browser/DomSanitizer) | ||||
| @ -1,5 +1,7 @@ | ||||
| --- | ||||
| title: AngularJS | ||||
| title: Sheets in AngularJS Sites | ||||
| sidebar_label: AngularJS | ||||
| description: Build interactive websites with AngularJS. 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: 7 | ||||
| @ -19,11 +21,18 @@ This demo is for the legacy AngularJS framework (version 1). | ||||
| 
 | ||||
| [AngularJS](https://angularjs.org/) is a JS library for building user interfaces. | ||||
| 
 | ||||
| ## Demo | ||||
| [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | ||||
| data from spreadsheets. | ||||
| 
 | ||||
| :::note | ||||
| This demo uses AngularJS and SheetJS to process and generate spreadsheets. We'll | ||||
| explore how to load SheetJS in AngularJS projects and compare common state | ||||
| models and data flow strategies. | ||||
| 
 | ||||
| This demo was last run on 2023 August 27 using AngularJS `1.8.2` | ||||
| ## Live Demo | ||||
| 
 | ||||
| :::note Tested Deployments | ||||
| 
 | ||||
| This demo was last run on 2023 November 19 using AngularJS `1.8.2` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -38,27 +47,94 @@ input element for loading user-submitted files. | ||||
| The [SheetJS Standalone scripts](/docs/getting-started/installation/standalone) | ||||
| can be referenced in a `SCRIPT` tag from the HTML entry point page. | ||||
| 
 | ||||
| The `$http` service can request binary data using `"arraybuffer"` response type. | ||||
| This maps to the `"array"` input format for `XLSX.read`: | ||||
| The script adds the `XLSX` global variable. | ||||
| 
 | ||||
| ## Data Sources | ||||
| 
 | ||||
| Modern browsers support a number of convenient APIs for receiving files and | ||||
| allowing users to submit files. | ||||
| 
 | ||||
| AngularJS, relevant in an era before the APIs were available, provides wrappers | ||||
| to simplify network and file processing. | ||||
| 
 | ||||
| ### Remote Files | ||||
| 
 | ||||
| To download files from a remote location, the `$http` service can perform GET | ||||
| requests[^1] | ||||
| 
 | ||||
| The `responseType` option is directly passed to `XMLHttpRequest`. Setting the | ||||
| property to `"arraybuffer"` ensures the result is an `ArrayBuffer` object. | ||||
| 
 | ||||
| The SheetJS [`read`](/docs/api/parse-options) method can parse the `ArrayBuffer` | ||||
| and return a SheetJS workbook object[^2]. | ||||
| 
 | ||||
| The following controller fetches [a sample file](https://sheetjs.com/pres.xlsx), | ||||
| parses the result into a workbook, extracts the first worksheet, and uses the | ||||
| SheetJS [`sheet_to_html`](/docs/api/utilities/html#html-table-output) method to | ||||
| generate a HTML table: | ||||
| 
 | ||||
| ```js | ||||
| /* The controller function must accept a `$http` argument */ | ||||
| app.controller('sheetjs', function($scope, $http) { | ||||
|   /* fetch https://sheetjs.com/pres.xlsx */ | ||||
|   $http({ | ||||
|     method:'GET', url:'https://sheetjs.com/pres.xlsx', | ||||
|     // highlight-next-line | ||||
|     /* ensure the result is an ArrayBuffer object */ | ||||
|     responseType:'arraybuffer' | ||||
|   }).then(function(data) { | ||||
|     // highlight-next-line | ||||
|     var wb = XLSX.read(data.data, {type:"array"}); | ||||
|     /* DO SOMETHING WITH wb HERE */ | ||||
|     $scope.data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|     var wb = XLSX.read(data.data); | ||||
|     /* generate HTML from first worksheet*/ | ||||
|     var ws = wb.Sheets[wb.SheetNames[0]]; | ||||
|     var html = XLSX.utils.sheet_to_html(ws); | ||||
|     /* assign to the `tbl` scope property */ | ||||
|     $scope.tbl = html; | ||||
|   }, function(err) { console.log(err); }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| <details><summary><b>Parsing User-Submitted Files</b> (click to show)</summary> | ||||
| ### User-Submitted Files | ||||
| 
 | ||||
| For file input elements, a general import directive is fairly straightforward: | ||||
| Users can submit files using HTML file input elements. A DOM `change` event is | ||||
| created when users select a file. | ||||
| 
 | ||||
| In AngularJS, standard DOM event handlers are created using custom directives | ||||
| with the `link` option[^3]. | ||||
| 
 | ||||
| The following directive function creates a `change` event handler that will use | ||||
| a `FileReader` to generate an `ArrayBuffer` object with the file data, parse the | ||||
| file data using the SheetJS [`read`](/docs/api/parse-options) method, generate a | ||||
| HTML table using [`sheet_to_html`](/docs/api/utilities/html#html-table-output), | ||||
| and store the result in the `tbl` property of the app state: | ||||
| 
 | ||||
| ```js | ||||
| function SheetJSImportDirective() { return { | ||||
|   scope: false, | ||||
|   /* $elm will be a reference to the file input DOM element */ | ||||
|   link: function ($scope, $elm) { | ||||
|     /* add a `change` event handler */ | ||||
|     $elm.on('change', function (changeEvent) { | ||||
|       /* use a FileReader to read the file */ | ||||
|       var reader = new FileReader(); | ||||
|       reader.onload = function (e) { | ||||
|         /* this event handler will be called once the data is read */ | ||||
|         var wb = XLSX.read(e.target.result); | ||||
| 
 | ||||
|         /* generate HTML from first worksheet*/ | ||||
|         var ws = wb.Sheets[wb.SheetNames[0]]; | ||||
|         var html = XLSX.utils.sheet_to_html(ws); | ||||
| 
 | ||||
|         /* assign to the `tbl` scope property */ | ||||
|         $scope.apply(function() { $scope.tbl = html; }); | ||||
|       }; | ||||
|       /* read */ | ||||
|       reader.readAsArrayBuffer(changeEvent.target.files[0]); | ||||
|     }); | ||||
|   } | ||||
| }; } | ||||
| ``` | ||||
| 
 | ||||
| This functionality can be added to the app in two steps: | ||||
| 
 | ||||
| 1) Add an `INPUT` element with attribute `import-sheet-js=""`: | ||||
| 
 | ||||
| @ -66,35 +142,18 @@ For file input elements, a general import directive is fairly straightforward: | ||||
| <input type="file" import-sheet-js="" multiple="false"  /> | ||||
| ``` | ||||
| 
 | ||||
| 2) Define the `SheetJSImportDirective` directive function: | ||||
| 
 | ||||
| ```js | ||||
| function SheetJSImportDirective() { return { | ||||
|   scope: false, | ||||
|   link: function ($scope, $elm) { | ||||
|     $elm.on('change', function (changeEvent) { | ||||
|       var reader = new FileReader(); | ||||
|       reader.onload = function (e) { | ||||
|         var wb = XLSX.read(e.target.result); | ||||
| 
 | ||||
|         /* DO SOMETHING WITH wb HERE */ | ||||
|         $scope.apply(function() { | ||||
|           $scope.data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|         }); | ||||
|       }; | ||||
|       reader.readAsArrayBuffer(changeEvent.target.files[0]); | ||||
|     }); | ||||
|   } | ||||
| }; } | ||||
| ``` | ||||
| 
 | ||||
| 3) Define the `importSheetJs` directive in the app: | ||||
| 2) Define the `importSheetJs` directive that attaches `SheetJSImportDirective`: | ||||
| 
 | ||||
| ```js | ||||
| app.directive("importSheetJs", [SheetJSImportDirective]); | ||||
| ``` | ||||
| 
 | ||||
| </details> | ||||
| :::note pass | ||||
| 
 | ||||
| AngularJS normalizes the hyphenated attribute `import-sheet-js` to the | ||||
| `importSheetJs` camel-case directive name. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## Internal State | ||||
| 
 | ||||
| @ -103,25 +162,15 @@ depends on the application. | ||||
| 
 | ||||
| ### 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.  For example, our | ||||
| [presidents sheet](https://sheetjs.com/pres.xlsx) has "Name" / "Index" columns: | ||||
| The example [presidents sheet](https://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: | ||||
| 
 | ||||
| <table><thead><tr><th>Spreadsheet</th><th>State</th></tr></thead><tbody><tr><td> | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| This naturally maps to an array of typed objects, as in the example below: | ||||
| 
 | ||||
| ```js | ||||
| app.controller('sheetjs', function($scope, $http) { | ||||
|   $http({ method:'GET', url:'https://sheetjs.com/pres.xlsx', responseType:'arraybuffer' }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data, {type:"array"}); | ||||
|     // highlight-next-line | ||||
|     $scope.data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|   }, function(err) { console.log(err); }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| `data` will be an array of objects: | ||||
| </td><td> | ||||
| 
 | ||||
| ```js | ||||
| [ | ||||
| @ -133,6 +182,25 @@ app.controller('sheetjs', function($scope, $http) { | ||||
| ] | ||||
| ``` | ||||
| 
 | ||||
| </td></tr></tbody></table> | ||||
| 
 | ||||
| The SheetJS [`sheet_to_json`](/docs/api/utilities/array#array-output) method | ||||
| generates row objects from worksheets. The following controller parses a remote | ||||
| file, generates row objects, and stores the array in the state: | ||||
| 
 | ||||
| ```js | ||||
| app.controller('sheetjs', function($scope, $http) { | ||||
|   $http({ | ||||
|     url:'https://sheetjs.com/pres.xlsx', | ||||
|     method:'GET', responseType:'arraybuffer' | ||||
|   }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data); | ||||
|     // highlight-next-line | ||||
|     $scope.data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|   }, function(err) { console.log(err); }); | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| A component will typically loop over the data using `ng-repeat`. The following | ||||
| template generates a TABLE with a row for each President: | ||||
| 
 | ||||
| @ -146,7 +214,8 @@ template generates a TABLE with a row for each President: | ||||
| </table> | ||||
| ``` | ||||
| 
 | ||||
| `XLSX.utils.json_to_sheet` can generate a worksheet from the data: | ||||
| The [`json_to_sheet`](/docs/api/utilities/array#array-of-objects-input) method | ||||
| can generate a worksheet from the data: | ||||
| 
 | ||||
| ```js | ||||
| /* assuming $scope.data is an array of objects */ | ||||
| @ -198,11 +267,10 @@ app.controller('sheetjs', function($scope, $http) { | ||||
|     XLSX.writeFile(wb, "SheetJSAngularJSAoO.xlsx"); | ||||
|   }; | ||||
|   $http({ | ||||
|     method:'GET', | ||||
|     url:'https://sheetjs.com/pres.xlsx', | ||||
|     responseType:'arraybuffer' | ||||
|     method:'GET', responseType:'arraybuffer' | ||||
|   }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data, {type:"array"}); | ||||
|     var wb = XLSX.read(data.data); | ||||
|     var data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); | ||||
|     $scope.data = data; | ||||
|   }, function(err) { console.log(err); }); | ||||
| @ -238,8 +306,11 @@ requires the `ngSanitize` plugin. | ||||
| // highlight-next-line | ||||
| var app = angular.module('s5s', ['ngSanitize']); | ||||
| app.controller('sheetjs', function($scope, $http) { | ||||
|   $http({ method:'GET', url:'https://sheetjs.com/pres.xlsx', responseType:'arraybuffer' }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data, {type:"array"}); | ||||
|   $http({ | ||||
|     url:'https://sheetjs.com/pres.xlsx', | ||||
|     method:'GET', responseType:'arraybuffer' | ||||
|   }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data); | ||||
|     // highlight-next-line | ||||
|     $scope.data = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
|   }, function(err) { console.log(err); }); | ||||
| @ -247,7 +318,7 @@ app.controller('sheetjs', function($scope, $http) { | ||||
| </script> | ||||
| ``` | ||||
| 
 | ||||
| The HTML table can be directly exported with `XLSX.utils.table_to_book`: | ||||
| The HTML table can be directly exported with [`table_to_book`](/docs/api/utilities/html#html-table-input): | ||||
| 
 | ||||
| ```js | ||||
|   $scope.exportSheetJS = function() { | ||||
| @ -291,11 +362,10 @@ app.controller('sheetjs', function($scope, $http) { | ||||
|     XLSX.writeFile(wb, "SheetJSAngularJSHTML.xlsx"); | ||||
|   }; | ||||
|   $http({ | ||||
|     method:'GET', | ||||
|     url:'https://sheetjs.com/pres.xlsx', | ||||
|     responseType:'arraybuffer' | ||||
|     method:'GET', responseType:'arraybuffer' | ||||
|   }).then(function(data) { | ||||
|     var wb = XLSX.read(data.data, {type:"array"}); | ||||
|     var wb = XLSX.read(data.data); | ||||
|     $scope.data = XLSX.utils.sheet_to_html(wb.Sheets[wb.SheetNames[0]]); | ||||
|   }, function(err) { console.log(err); }); | ||||
| }); | ||||
| @ -308,3 +378,7 @@ app.controller('sheetjs', function($scope, $http) { | ||||
| URL with a web browser (typically `http://localhost:8080`) | ||||
| 
 | ||||
| </details> | ||||
| 
 | ||||
| [^1]: See [`$http`](https://docs.angularjs.org/api/ng/service/$http) in the AngularJS documentation. | ||||
| [^2]: See ["Workbook Object"](/docs/csf/book) | ||||
| [^3]: See ["Creating Directives"](https://docs.angularjs.org/guide/directive#creating-a-directive-that-manipulates-the-dom) in the AngularJS documentation. | ||||
| @ -257,7 +257,7 @@ This demo was tested in the following environments: | ||||
| 
 | ||||
| | OS and Version | Arch | Tauri    | Date       | | ||||
| |:---------------|:-----|:---------|:-----------| | ||||
| | macOS 13.5.1   | x64  | `v1.5.0` | 2023-09-30 | | ||||
| | macOS 14.1.1   | x64  | `v1.5.6` | 2023-11-17 | | ||||
| | macOS 14.0     | ARM  | `v1.5.2` | 2023-10-18 | | ||||
| | Windows 10     | x64  | `v1.5.0` | 2023-10-01 | | ||||
| | Windows 11     | ARM  | `v1.4.1` | 2023-09-26 | | ||||
| @ -287,14 +287,15 @@ If required dependencies are installed, the output will show a checkmark next to | ||||
| 
 | ||||
| ``` | ||||
| [✔] Environment | ||||
|     - OS: Mac OS 13.5.1 X64 | ||||
|     - OS: Mac OS 14.1.1 X64 | ||||
|     ✔ Xcode Command Line Tools: installed | ||||
|     ✔ rustc: 1.72.1 (d5c2e9c34 2023-09-13) | ||||
|     ✔ Cargo: 1.72.1 (103a7ff2e 2023-08-15) | ||||
|     ✔ rustup: 1.26.0+1046 (d4c684485 2023-08-30) | ||||
|     ✔ rustc: 1.74.0 (79e9716c9 2023-11-13) | ||||
|     ✔ cargo: 1.74.0 (ecb9851af 2023-10-18) | ||||
|     ✔ rustup: 1.26.0+198 (393e187b7 2023-11-16) | ||||
|     ✔ Rust toolchain: stable-x86_64-apple-darwin (default) | ||||
|     - node: 16.20.2 | ||||
|     - npm: 8.19.4 | ||||
|     - node: 20.9.0 | ||||
|     - npm: 10.1.0 | ||||
|     - bun: 1.0.2 | ||||
| ``` | ||||
| 
 | ||||
| :::caution pass | ||||
| @ -309,17 +310,9 @@ build step will correctly detect the platform architecture. | ||||
| 1) Create a new Tauri app: | ||||
| 
 | ||||
| ```bash | ||||
| npm create tauri-app | ||||
| npm create tauri-app@latest -- -m npm -t vue-ts SheetJSTauri -y | ||||
| ``` | ||||
| 
 | ||||
| When prompted: | ||||
| 
 | ||||
| - Project Name: `SheetJSTauri` | ||||
| - Choose which language to use for your frontend: `TypeScript / JavaScript` | ||||
| - Choose your package manager: `npm` | ||||
| - Choose your UI template: `Vue` | ||||
| - Choose your UI flavor: `TypeScript` | ||||
| 
 | ||||
| 2) Enter the directory and install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| @ -378,17 +371,7 @@ At the end, it will print the path to the generated installer. | ||||
| 
 | ||||
| :::info pass | ||||
| 
 | ||||
| During the last Linux test, the build had failed with an error: | ||||
| 
 | ||||
| ``` | ||||
| 'openssl/opensslv.h' file not found | ||||
| ``` | ||||
| 
 | ||||
| This error was resolved installing OpenSSL. On Arch Linux and HoloOS: | ||||
| 
 | ||||
| ```bash | ||||
| sudo pacman -S openssl | ||||
| ``` | ||||
| If the build fails, see ["Troubleshooting"](#troubleshooting) for more details. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -428,6 +411,51 @@ The following features should be manually verified: | ||||
| - Edit the file in a spreadsheet editor, then click "Load Data" and select the | ||||
|   edited file. The table will refresh with new contents. | ||||
| 
 | ||||
| #### Troubleshooting | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| During the last Linux test, the build failed with the following error message: | ||||
| 
 | ||||
| ``` | ||||
| 'openssl/opensslv.h' file not found | ||||
| ``` | ||||
| 
 | ||||
| This error was resolved installing OpenSSL. On Arch Linux and HoloOS: | ||||
| 
 | ||||
| ```bash | ||||
| sudo pacman -S openssl | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| :::note pass | ||||
| 
 | ||||
| During the last macOS test, the build failed with the following error message: | ||||
| 
 | ||||
| ``` | ||||
|        Error failed to bundle project: error running bundle_dmg.sh | ||||
| ``` | ||||
| 
 | ||||
| The root cause of the error can be discovered by running | ||||
| 
 | ||||
| ```bash | ||||
| npm run tauri build -- --verbose | ||||
| ``` | ||||
| 
 | ||||
| The most recent test failed with a message: | ||||
| 
 | ||||
| ``` | ||||
| execution error: Not authorized to send Apple events to Finder | ||||
| ``` | ||||
| 
 | ||||
| This error was resolved by allowing Terminal to control Finder. | ||||
| 
 | ||||
| In the "System Settings" app, select "Privacy & Security" in the left column and | ||||
| select "Automation" in the body. Look for "Terminal", expand the section, and enable "Finder". | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| [^1]: See ["Security"](https://tauri.app/v1/references/architecture/security#allowing-api) in the Tauri documentation | ||||
| [^2]: See [`FsAllowlistConfig`](https://tauri.app/v1/api/config/#fsallowlistconfig) in the Tauri documentation | ||||
| [^3]: See [`DialogAllowlistConfig`](https://tauri.app/v1/api/config/#dialogallowlistconfig) in the Tauri documentation | ||||
|  | ||||
| @ -208,10 +208,10 @@ const aoa = [ ["Hash"], [key] ].concat(Object.entries(values)); | ||||
| 
 | ||||
| ## Complete Example | ||||
| 
 | ||||
| :::note | ||||
| :::note Tested Deployments | ||||
| 
 | ||||
| This demo was last tested on 2023 August 22 with Redis 7.2.0, Redis connector | ||||
| module 4.6.7 and NodeJS 20.5.1. | ||||
| This demo was last tested on 2023 November 18 with Redis 7.2.3, Redis connector | ||||
| module 4.6.10 and NodeJS 20.9.0. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| @ -225,7 +225,7 @@ this demo also requires NodeJS version 18 or later. | ||||
| 
 | ||||
| 0) Set up and start a local Redis server. | ||||
| 
 | ||||
| :::note | ||||
| :::note pass | ||||
| 
 | ||||
| This demo was last tested on macOS.  Redis was installed with: | ||||
| 
 | ||||
| @ -254,7 +254,7 @@ curl -LO https://docs.sheetjs.com/nosql/SheetJSRedisTest.mjs | ||||
| 2) Install dependencies: | ||||
| 
 | ||||
| <CodeBlock language="bash">{`\ | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz redis@4.6.7`} | ||||
| npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz redis@4.6.10`} | ||||
| </CodeBlock> | ||||
| 
 | ||||
| 3) Run the test script: | ||||
|  | ||||
| @ -27,7 +27,7 @@ This demo was verified by NetSuite consultants in the following deployments: | ||||
| | ScheduledScript | 2.1            | 2023-08-18 | | ||||
| | Restlet         | 2.1            | 2023-10-05 | | ||||
| | Suitelet        | 2.1            | 2023-10-27 | | ||||
| | MapReduceScript | 2.1            | 2023-07-31 | | ||||
| | MapReduceScript | 2.1            | 2023-11-16 | | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
|  | ||||
| @ -33,7 +33,7 @@ in the [issue tracker](https://git.sheetjs.com/sheetjs/docs.sheetjs.com/issues) | ||||
| - [`React`](/docs/demos/frontend/react) | ||||
| - [`Svelte`](/docs/demos/frontend/svelte) | ||||
| - [`VueJS`](/docs/demos/frontend/vue) | ||||
| - [`Angular.JS`](/docs/demos/frontend/angularjs) | ||||
| - [`AngularJS`](/docs/demos/frontend/angularjs) | ||||
| - [`Dojo`](/docs/demos/frontend/legacy#dojo-toolkit) | ||||
| - [`Knockout`](/docs/demos/frontend/legacy#knockoutjs) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								docz/static/dta/dta.min.js
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								docz/static/dta/dta.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user