--- title: Native Sheets in NativeScript sidebar_label: NativeScript pagination_prev: demos/static/index pagination_next: demos/desktop/index sidebar_position: 2 sidebar_custom_props: summary: JS + Native Elements --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; export const g = {style: {color:"green"}}; export const r = {style: {color:"red"}}; export const y = {style: {color:"yellow"}}; [NativeScript](https://nativescript.org/) is a mobile app framework. It builds iOS and Android apps that use JavaScript for describing layouts and events. [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing data from spreadsheets. This demo uses NativeScript and SheetJS to process and generate spreadsheets. We'll explore how to load SheetJS in a NativeScript app; parse and generate spreadsheets stored on the device; and fetch and parse remote files. The "Complete Example" creates an app that looks like the screenshots below:
iOS Android
![iOS screenshot](pathname:///nativescript/ios.png) ![Android screenshot](pathname:///nativescript/and.png)
:::info pass The discussion covers the NativeScript + Angular integration. Familiarity with Angular and TypeScript is assumed. ::: :::warning Telemetry Before starting this demo, manually disable telemetry. NativeScript 8.6.0 split the telemetry into two parts: "usage" and "error". Both must be disabled separately: ```bash npx -p nativescript ns usage-reporting disable npx -p nativescript ns error-reporting disable ``` To verify telemetry was disabled: ```bash npx -p nativescript ns usage-reporting status npx -p nativescript ns error-reporting status ``` ::: ## Integration Details The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be imported from any component or script in the app. The `@nativescript/core/file-system` package provides classes for file access. The `File` class does not support binary data, but the file access singleton from `@nativescript/core` does support reading and writing `ArrayBuffer`. Reading and writing data require a URL. The following snippet searches typical document folders for a specified filename: ```ts import { Folder, knownFolders, path } from '@nativescript/core/file-system'; function get_url_for_filename(filename: string): string { const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic(); return path.normalize(target.path + "///" + filename); } ``` ### Reading Local Files `getFileAccess().readBufferAsync` can read data into an `ArrayBuffer` object. The SheetJS `read` method[^1] can parse this data into a workbook object.[^2] ```ts import { getFileAccess } from '@nativescript/core'; import { read } from 'xlsx'; /* find appropriate path */ const url = get_url_for_filename("SheetJSNS.xls"); /* get data */ const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url); /* read workbook */ const wb = read(ab); ``` After parsing into a workbook, the `sheet_to_json`[^3] method can generate row data objects: ```ts import { utils } from 'xlsx'; /* grab first sheet */ const wsname: string = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; /* generate array of row objects */ const data = utils.sheet_to_json(ws); ``` ### Writing Local Files The SheetJS `write` method[^4] with the option `type: "binary"` will generate `Uint8Array` objects. `getFileAccess().writeBufferAsync` can write data from a `Uint8Array` object to the device. iOS supports `Uint8Array` directly but Android requires a true array of numbers: ```ts import { getFileAccess } from '@nativescript/core'; import { write } from 'xlsx'; /* find appropriate path */ const url = get_url_for_filename("SheetJSNS.xls"); /* generate Uint8Array */ const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'binary' }); /* attempt to save Uint8Array to file */ await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8); ``` A worksheet can be generated from an array of row objects with the SheetJS `json_to_sheet` method[^5]. After generating an array, the `book_new` and `book_append_sheet` methods[^6] can create the workbook. ### Fetching Remote Files `getFile` from `@nativescript/core/http` can download files. After storing the file in a temporary folder, `getFileAccess().readBufferAsync` can read the data and the SheetJS `read` method[^7] can parse the file: ```ts import { knownFolders, path, getFileAccess } from '@nativescript/core' import { getFile } from '@nativescript/core/http'; import { read } from 'xlsx'; /* generate temporary path for the new file */ const temp: string = path.join(knownFolders.temp().path, "pres.xlsx"); /* download file */ const file = await getFile("https://sheetjs.com/pres.xlsx", temp) /* get data */ const ab: ArrayBuffer = await getFileAccess().readBufferAsync(file.path); /* read workbook */ const wb = read(ab); ``` ## Complete Example :::note The project was last tested on 2023 October 12. NativeScript version (as verified with `npx -p nativescript ns --version`) was `8.6.0`. The iOS demo was last tested on 2023-10-12 with `@nativescript/ios` version `8.6.1` on an emulated iPhone 15 Pro Max + iOS 17.0 The Android demo was last tested on 2023-10-12 with `@nativescript/android` version `8.6.2` on an emulated Pixel 3 + Android 13 ("Tiramisu") API 33. ::: ### Platform Configuration 0) Disable telemetry: ```bash npx -p nativescript ns usage-reporting disable npx -p nativescript ns error-reporting disable ``` 1) Follow the official Environment Setup instructions[^8]. :::caution pass When the demo was last tested, the latest version of the Android API was 34. NativeScript did not support that API level. The exact error message from `npx -p nativescript ns doctor ios` clearly stated supported versions: (x is red, body text is yellow) ``` ✖ No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '>=23 <=33'. ``` The SDK Platform `Android 13.0 ("Tiramisu")` was compatible with NativeScript. Until NativeScript properly supports API level 34, "Tiramisu" must be used. This requires installing the following packages from Android Studio: - `Android 13.0 ("Tiramisu")` API Level `33` - `Android SDK Build-Tools` Version `33.0.2` ::: 2) Test the local system configuration for Android development: ```bash npx -p nativescript ns doctor android ``` In the last macOS test, the following output was displayed:
Expected output (click to hide)
 Getting environment information{'\n'}
{'\n'}
No issues were detected.{'\n'}
 Your ANDROID_HOME environment variable is set and points to correct directory.{'\n'}
 Your adb from the Android SDK is correctly installed.{'\n'}
 The Android SDK is installed.{'\n'}
 A compatible Android SDK for compilation is found.{'\n'}
 Javac is installed and is configured properly.{'\n'}
 The Java Development Kit (JDK) is installed and is configured properly.{'\n'}
 Getting NativeScript components versions information...{'\n'}
 Component nativescript has 8.6.0 version and is up to date.
3) Test the local system configuration for iOS development (macOS only): ```bash npx -p nativescript ns doctor ios ``` In the last macOS test, the following output was displayed:
Expected output (click to hide)
 Getting environment information{'\n'}
{'\n'}
No issues were detected.{'\n'}
 Xcode is installed and is configured properly.{'\n'}
 xcodeproj is installed and is configured properly.{'\n'}
 CocoaPods are installed.{'\n'}
 CocoaPods update is not required.{'\n'}
 CocoaPods are configured properly.{'\n'}
 Your current CocoaPods version is newer than 1.0.0.{'\n'}
 Python installed and configured correctly.{'\n'}
 The Python 'six' package is found.{'\n'}
 Xcode version 15.0.0 satisfies minimum required version 10.{'\n'}
 Getting NativeScript components versions information...{'\n'}
 Component nativescript has 8.6.0 version and is up to date.
### Base Project 4) Create a skeleton NativeScript + Angular app: ```bash npx -p nativescript ns create SheetJSNS --ng ``` 5) Launch the app in the android simulator to verify the app: ```bash cd SheetJSNS npx -p nativescript ns run android ``` (this may take a while) Once the simulator launches and the test app is displayed, end the script by selecting the terminal and entering the key sequence `CTRL + C` 6) From the project folder, install the library: {`\ npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`} ### Add SheetJS :::note pass The goal of this section is to display the SheetJS library version number. ::: 7) Edit `src/app/item/items.component.ts` so that the component imports the SheetJS version string and adds it to a `version` variable in the component: ```ts title="src/app/item/items.component.ts" // highlight-next-line import { version } from 'xlsx'; import { Component, OnInit } from '@angular/core' // ... export class ItemsComponent implements OnInit { items: Array // highlight-next-line version = `SheetJS - ${version}`; constructor(private itemService: ItemService) {} // ... ``` 8) Edit the template `src/app/item/items.component.html` to reference `version` in the title of the action bar: ```xml title="src/app/item/items.component.html" ``` 9) Relaunch the app in the Android simulator: ```bash npx -p nativescript ns run android ``` The title bar should show the version. ![NativeScript Step 4](pathname:///nativescript/step4.png) ### Local Files 10) Add the Import and Export buttons to the template: ```xml title="src/app/item/items.component.html" ``` 11) Add the `import` and `export` methods in the component script: ```ts title="src/app/item/items.component.ts" // highlight-start import { version, utils, read, write } from 'xlsx'; import { Dialogs, getFileAccess } from '@nativescript/core'; import { Folder, knownFolders, path } from '@nativescript/core/file-system'; // highlight-end import { Component, OnInit } from '@angular/core' import { Item } from './item' import { ItemService } from './item.service' // highlight-start function get_url_for_filename(filename: string): string { const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic(); return path.normalize(target.path + "///" + filename); } // highlight-end @Component({ selector: 'ns-items', templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit { items: Array version: string = `SheetJS - ${version}`; constructor(private itemService: ItemService) {} ngOnInit(): void { this.items = this.itemService.getItems() } // highlight-start /* Import button */ async import() { } /* Export button */ async export() { } // highlight-end } ``` 12) Restart the app process. Two buttons should show up at the top: ![NativeScript Step 5](pathname:///nativescript/step5.png) 13) Implement import and export by adding the highlighted lines: ```ts title="src/app/item/items.component.ts" /* Import button */ async import() { // highlight-start /* find appropriate path */ const url = get_url_for_filename("SheetJSNS.xls"); try { await Dialogs.alert(`Attempting to read from SheetJSNS.xls at ${url}`); /* get data */ const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url); /* read workbook */ const wb = read(ab); /* grab first sheet */ const wsname: string = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; /* update table */ this.items = utils.sheet_to_json(ws); } catch(e) { await Dialogs.alert(e.message); } // highlight-end } /* Export button */ async export() { // highlight-start /* find appropriate path */ const url = get_url_for_filename("SheetJSNS.xls"); try { /* create worksheet from data */ const ws = utils.json_to_sheet(this.items); /* create workbook from worksheet */ const wb = utils.book_new(); utils.book_append_sheet(wb, ws, "Sheet1"); /* generate Uint8Array */ const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'buffer' }); /* attempt to save Uint8Array to file */ await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8); await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`); } catch(e) { await Dialogs.alert(e.message); } // highlight-end } ``` ### Android 14) Launch the app in the Android Simulator: ```bash npx -p nativescript ns run android ```` If the app does not automatically launch, manually open the `SheetJSNS` app. 15) Tap "Export File". A dialog will print where the file was written. Typically the URL is `/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls` 16) Pull the file from the simulator: ```bash adb root adb pull /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls SheetJSNS.xls ``` If the emulator cannot be rooted: ```bash adb shell "run-as org.nativescript.SheetJSNS cat /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" > SheetJSNS.xls ``` 17) Open `SheetJSNS.xls` with a spreadsheet editor. After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library: ``` id | name | role 0 | SheetJS | Library 1 | Ter Stegen | Goalkeeper 3 | Piqué | Defender ... ``` 18) Push the file back to the simulator: ```bash adb push SheetJSNS.xls /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls ``` If the emulator cannot be rooted: ```bash dd if=SheetJSNS.xls | adb shell "run-as org.nativescript.SheetJSNS dd of=/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" ``` 19) Tap "Import File". A dialog will print the path of the file that was read. The first item in the list will change. ### iOS 20) Launch the app in the iOS Simulator: ```bash npx -p nativescript ns run ios ``` 21) Tap "Export File". A dialog will print where the file was written. 22) Open the file with a spreadsheet editor. After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library: ``` id | name | role 0 | SheetJS | Library 1 | Ter Stegen | Goalkeeper 3 | Piqué | Defender ... ``` 23) Restart the app after saving the file. 24) Tap "Import File". A dialog will print the path of the file that was read. The first item in the list will change: ![NativeScript Step 7](pathname:///nativescript/step7.png) ### Fetching Files 25) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous: ```ts title="src/app/item/items.component.ts" async ngOnInit(): Promise { this.items = await this.itemService.getItems() } ``` 26) Replace `item.service.ts` with the following: ```ts title="src/app/item/item.service.ts" import { Injectable } from '@angular/core' import { knownFolders, path, getFileAccess } from '@nativescript/core' import { getFile } from '@nativescript/core/http'; import { read, utils } from 'xlsx'; import { Item } from './item' interface IPresident { Name: string; Index: number }; @Injectable({ providedIn: 'root' }) export class ItemService { private items: Array; async getItems(): Promise> { /* fetch https://sheetjs.com/pres.xlsx */ const temp: string = path.join(knownFolders.temp().path, "pres.xlsx"); const ab = await getFile("https://sheetjs.com/pres.xlsx", temp) /* read the temporary file */ const wb = read(await getFileAccess().readBufferAsync(ab.path)); /* translate the first worksheet to the required Item type */ const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); return this.items = data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item)); } getItem(id: number): Item { return this.items.filter((item) => item.id === id)[0] } } ``` 27) Relaunch the app in the Android simulator: ```bash npx -p nativescript ns run android ```` The app should show Presidential data. [^1]: See [`read` in "Reading Files"](/docs/api/parse-options) [^2]: See ["Workbook Object"](/docs/csf/book) [^3]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output) [^4]: See [`write` in "Writing Files"](/docs/api/write-options) [^5]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input) [^6]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. [^7]: See [`read` in "Reading Files"](/docs/api/parse-options) [^8]: See ["Local setup"](https://docs.nativescript.org/setup/#local-setup) in the NativeScript documentation. For Windows and Linux, follow the "Android" instructions. For macOS, follow both the iOS and Android instructions.