-
Notifications
You must be signed in to change notification settings - Fork 157
HF-122: Framework integration guides for React, Angular, Vue, Svelte #1653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
27b9582
Docs: add basic usage snippets to framework integration guides
marcin-kordas-hoc e920dcc
Docs: simplify snippets and clarify Demo link wording
marcin-kordas-hoc 88056b7
Docs: modernize framework integration guides with idiomatic patterns
marcin-kordas-hoc 9571737
Docs: add Next steps cross-links to framework integration guides
marcin-kordas-hoc bd84ce9
Docs: fix $page.buildDateURIEncoded template syntax in framework guides
marcin-kordas-hoc 87c2f46
Docs: fix setCellContents type in guide snippets (unknown → RawCellCo…
marcin-kordas-hoc 2f8b2be
Docs: add AGENTS.md with universal AI assistant instructions
marcin-kordas-hoc 2a07790
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba 683bc08
Docs: align Angular and Svelte guide snippets with Stackblitz demos
marcin-kordas-hoc 655274a
Docs: fix P0/P1 issues found in framework guide expert review
marcin-kordas-hoc b2cb63d
Docs: align guides with Stackblitz demos and fix review findings
marcin-kordas-hoc 76bcfdd
Docs: fix Stackblitz links using Vue v-bind instead of template inter…
marcin-kordas-hoc f8dfa07
Docs: remove untested framework patterns not present in demos
marcin-kordas-hoc 545ea1e
Docs: align all guide snippets with demo interaction patterns
marcin-kordas-hoc af96abc
Docs: fix AGENTS.md Vue SSR guard and align Svelte button labels
marcin-kordas-hoc d6a316c
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba 3bc3333
Merge branch 'develop' into feature/hf-122-framework-integration-guides
sequba 146142d
Docs: fix React TS import + Next.js SSR pattern + Svelte async onMoun…
marcin-kordas-hoc 36aa583
Docs: align Vue/Svelte/AGENTS.md SSR framing with React + drop dated …
marcin-kordas-hoc 3528710
Docs: hide Angular table on empty array (parity with React/Vue)
marcin-kordas-hoc b080681
Docs: actually apply sequba's verbatim review suggestions
marcin-kordas-hoc cc6626a
Merge branch 'develop' into feature/hf-122-framework-integration-guides
marcin-kordas-hoc c598774
Docs: remove AGENTS.md from HF-122 scope (belongs to HF-154)
marcin-kordas-hoc 17cf8bd
Docs: correct Svelte SSR warning (HF runs on server, does not crash)
marcin-kordas-hoc 1ecce54
Docs: align SSR section heading depth + add Angular Universal note
marcin-kordas-hoc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,127 @@ | ||
| # Integration with Angular | ||
|
|
||
| Installing HyperFormula in an Angular application works the same as with vanilla JavaScript. | ||
| The HyperFormula API is identical in an Angular app and in plain JavaScript. This guide demonstrates how HyperFormula is integrated with an Angular app (typically as an injectable service), how it is cleaned up, and how you bridge its values into the change-detection cycle. | ||
|
|
||
| For more details, see the [client-side installation](client-side-installation.md) section. | ||
| Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section. | ||
|
|
||
| ## Basic usage | ||
|
|
||
| Wrap the engine in an `@Injectable` service backed by a `BehaviorSubject`. Components subscribe to the observable with the `async` pipe, which handles subscription cleanup automatically. | ||
|
|
||
| ```typescript | ||
| // spreadsheet.service.ts | ||
| import { Injectable } from '@angular/core'; | ||
| import { BehaviorSubject } from 'rxjs'; | ||
| import { HyperFormula, type CellValue } from 'hyperformula'; | ||
|
|
||
| @Injectable({ providedIn: 'root' }) | ||
| export class SpreadsheetService { | ||
| private readonly hf: HyperFormula; | ||
|
|
||
| private readonly _values = new BehaviorSubject<CellValue[][]>([]); | ||
| readonly values$ = this._values.asObservable(); | ||
|
|
||
| constructor() { | ||
| this.hf = HyperFormula.buildFromArray( | ||
| [ | ||
| [1, 2, '=A1+B1'], | ||
| // your data rows go here | ||
| ], | ||
| { | ||
| licenseKey: 'gpl-v3', | ||
| // more configuration options go here | ||
| } | ||
| ); | ||
| this._values.next(this.hf.getSheetValues(0)); | ||
| } | ||
|
|
||
| calculate() { | ||
| this._values.next(this.hf.getSheetValues(0)); | ||
| } | ||
|
|
||
| reset() { | ||
| this._values.next([]); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Consume the service from a component and bind `values$ | async` in the template. Declare the component in your `AppModule` alongside `CommonModule`: | ||
|
|
||
| ```typescript | ||
| // spreadsheet.component.ts | ||
| import { Component } from '@angular/core'; | ||
| import { Observable } from 'rxjs'; | ||
| import { SpreadsheetService } from './spreadsheet.service'; | ||
| import { type CellValue } from 'hyperformula'; | ||
|
|
||
| @Component({ | ||
| selector: 'app-spreadsheet', | ||
| templateUrl: './spreadsheet.component.html', | ||
| }) | ||
| export class SpreadsheetComponent { | ||
| values$: Observable<CellValue[][]>; | ||
|
|
||
| constructor(private spreadsheetService: SpreadsheetService) { | ||
| this.values$ = this.spreadsheetService.values$; | ||
| } | ||
|
|
||
| runCalculations() { | ||
| this.spreadsheetService.calculate(); | ||
| } | ||
|
|
||
| reset() { | ||
| this.spreadsheetService.reset(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```html | ||
| <!-- spreadsheet.component.html --> | ||
| <button (click)="runCalculations()">Run calculations</button> | ||
| <button (click)="reset()">Reset</button> | ||
| <ng-container *ngIf="(values$ | async) as values"> | ||
| <table *ngIf="values.length"> | ||
| <tr *ngFor="let row of values"> | ||
| <td *ngFor="let cell of row">{{ cell }}</td> | ||
| </tr> | ||
| </table> | ||
| </ng-container> | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| ### Provider scope | ||
|
|
||
| `providedIn: 'root'` makes the service an application-wide singleton — suitable when a single HyperFormula instance is shared across the app. For per-feature or per-component instances (for example, several independent reports on one screen), provide the service at the component level via `providers: [SpreadsheetService]`; the service is then created and destroyed alongside the component. | ||
|
|
||
| ### Cleanup | ||
|
|
||
| Root-scoped services live for the application's full lifetime — `ngOnDestroy` fires only at app shutdown. If you scope the service to a component (`providers: [SpreadsheetService]`), implement `OnDestroy` to release the engine: | ||
|
|
||
| ```typescript | ||
| import { Injectable, OnDestroy } from '@angular/core'; | ||
|
|
||
| @Injectable() | ||
| export class SpreadsheetService implements OnDestroy { | ||
| // ... | ||
|
|
||
| ngOnDestroy() { | ||
| this.hf.destroy(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Server-side rendering (Angular Universal) | ||
|
|
||
| The service above is already SSR-safe — HyperFormula has no browser-only API dependency. To skip the (otherwise wasted) server-side instantiation in Angular Universal, gate the engine init with [`isPlatformBrowser`](https://angular.dev/api/common/isPlatformBrowser) from `@angular/common`. | ||
|
|
||
| ## Next steps | ||
|
|
||
| - [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options | ||
| - [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets | ||
| - [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions | ||
| - [Custom functions](custom-functions.md) — register your own formulas | ||
|
|
||
| ## Demo | ||
|
|
||
| Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}). | ||
| For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=' + $page.buildDateURIEncoded">Angular demo on Stackblitz</a>. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,117 @@ | ||
| # Integration with React | ||
|
|
||
| Installing HyperFormula in a React application works the same as with vanilla JavaScript. | ||
| The HyperFormula API is identical in a React app and in plain JavaScript. This guide demonstrates how HyperFormula is integrated with the React component tree and how its lifecycle maps to React hooks. | ||
|
|
||
| For more details, see the [client-side installation](client-side-installation.md) section. | ||
| Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section. | ||
|
|
||
| ## Basic usage | ||
|
|
||
| Hold the HyperFormula instance in a `useRef` so it survives re-renders. Initialize it inside `useEffect` and release it in the cleanup function. Use `useState` to toggle between raw formulas and computed values. | ||
|
|
||
| ```tsx | ||
| import { useEffect, useRef, useState } from 'react'; | ||
| import { HyperFormula } from 'hyperformula'; | ||
| import type { CellValue } from 'hyperformula'; | ||
|
|
||
| export default function SpreadsheetComponent() { | ||
| const hfRef = useRef<HyperFormula | null>(null); | ||
| const [values, setValues] = useState<CellValue[][]>([]); | ||
|
|
||
| useEffect(() => { | ||
| const hf = HyperFormula.buildFromArray( | ||
| [ | ||
| [1, 2, '=A1+B1'], | ||
| // your data rows go here | ||
| ], | ||
| { | ||
| licenseKey: 'gpl-v3', | ||
| // more configuration options go here | ||
| } | ||
| ); | ||
| hfRef.current = hf; | ||
|
|
||
| return () => { | ||
| hf.destroy(); | ||
| hfRef.current = null; | ||
| }; | ||
| }, []); | ||
|
|
||
| function runCalculations() { | ||
| if (!hfRef.current) return; | ||
| setValues(hfRef.current.getSheetValues(0)); | ||
| } | ||
|
|
||
| function reset() { | ||
| setValues([]); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <button onClick={runCalculations}>Run calculations</button> | ||
| <button onClick={reset}>Reset</button> | ||
| {values.length > 0 && ( | ||
| <table> | ||
| <tbody> | ||
| {values.map((row, r) => ( | ||
| <tr key={r}> | ||
| {row.map((cell, c) => ( | ||
| <td key={c}>{String(cell ?? '')}</td> | ||
| ))} | ||
| </tr> | ||
| ))} | ||
| </tbody> | ||
| </table> | ||
| )} | ||
| </> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| If you use JavaScript instead of TypeScript, drop the type annotations — the rest of the pattern is unchanged. | ||
|
|
||
| ## `React.StrictMode` double invocation | ||
|
|
||
| In development, React runs effects twice (mount → unmount → mount) to surface cleanup bugs. The pattern above is correct for StrictMode because `destroy()` runs before the re-mount creates a new instance, so no work leaks between the two lifecycles. Do not switch to a module-scoped singleton as a workaround — it will break StrictMode semantics. | ||
|
|
||
| ## Server-side rendering (Next.js App Router) | ||
|
|
||
| The component above is already SSR-safe — the engine is constructed in `useEffect`, which never runs on the server. If you still want to skip the initial bundle on the server (it is a few hundred kB), wrap it in a client-only dynamic import. | ||
|
|
||
| In the App Router, `dynamic(..., { ssr: false })` is only allowed inside a client component. Put the dynamic call in a `'use client'` wrapper and import the wrapper from your server page: | ||
|
|
||
| ```tsx | ||
| // app/spreadsheet/SpreadsheetLazy.tsx | ||
| 'use client'; | ||
| import dynamic from 'next/dynamic'; | ||
|
|
||
| const SpreadsheetComponent = dynamic( | ||
| () => import('./SpreadsheetComponent'), | ||
| { ssr: false } | ||
| ); | ||
|
|
||
| export default function SpreadsheetLazy() { | ||
| return <SpreadsheetComponent />; | ||
| } | ||
| ``` | ||
|
|
||
| ```tsx | ||
| // app/spreadsheet/page.tsx ← server component, no 'use client' | ||
| import SpreadsheetLazy from './SpreadsheetLazy'; | ||
|
|
||
| export default function Page() { | ||
| return <SpreadsheetLazy />; | ||
| } | ||
| ``` | ||
|
|
||
| In the Pages Router, the same `dynamic(..., { ssr: false })` call works directly in the page file without a wrapper. | ||
|
|
||
| ## Next steps | ||
|
|
||
| - [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options | ||
| - [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets | ||
| - [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions | ||
| - [Custom functions](custom-functions.md) — register your own formulas | ||
|
|
||
| ## Demo | ||
|
|
||
| Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}). | ||
| For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=' + $page.buildDateURIEncoded">React demo on Stackblitz</a>. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,126 @@ | ||
| # Integration with Svelte | ||
|
|
||
| Installing HyperFormula in a Svelte application works the same as with vanilla JavaScript. | ||
| The HyperFormula API is identical in a Svelte app and in plain JavaScript. This guide demonstrates how HyperFormula integrates with the Svelte component's lifecycle and how you bridge its values into Svelte's reactivity. | ||
|
|
||
| For more details, see the [client-side installation](client-side-installation.md) section. | ||
| Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section. | ||
|
|
||
| ::: warning SvelteKit SSR | ||
| The primary snippet below assumes a browser environment. If you use SvelteKit with default SSR, skip to [Server-side rendering](#server-side-rendering-sveltekit) — `HyperFormula.buildFromArray` at `<script>` top level will run on every server render, which is unnecessary work. | ||
| ::: | ||
|
|
||
| ## Basic usage | ||
|
|
||
| Declare the engine at the top of `<script>` so it lives for the component's lifetime. Call `getCellValue` on demand and display results in the template. Release the engine with `onDestroy`. | ||
|
|
||
| ```html | ||
| <script> | ||
| import { onDestroy } from 'svelte'; | ||
| import { HyperFormula } from 'hyperformula'; | ||
|
|
||
| const data = [ | ||
| [1, 2, '=A1+B1'], | ||
| // your data rows go here | ||
| ]; | ||
|
|
||
| const hf = HyperFormula.buildFromArray(data, { | ||
| licenseKey: 'gpl-v3', | ||
| // more configuration options go here | ||
| }); | ||
|
|
||
| const sheetId = 0; | ||
| /** @type {import('hyperformula').CellValue} */ | ||
| let result = null; | ||
|
|
||
| function calculate() { | ||
| result = hf.getCellValue({ sheet: sheetId, row: 0, col: 2 }); | ||
| } | ||
|
|
||
| function reset() { | ||
| result = null; | ||
| } | ||
|
|
||
| onDestroy(() => hf.destroy()); | ||
| </script> | ||
|
|
||
| <button on:click={calculate}>Run calculations</button> | ||
| <button on:click={reset}>Reset</button> | ||
| {#if result !== null} | ||
| <p>Result: <strong>{result}</strong></p> | ||
| {/if} | ||
|
|
||
| <table> | ||
| <tbody> | ||
| {#each data as row, r} | ||
| <tr> | ||
| {#each row as cell, c} | ||
| <td> | ||
| {#if hf.doesCellHaveFormula({ sheet: sheetId, row: r, col: c })} | ||
| {hf.getCellFormula({ sheet: sheetId, row: r, col: c })} | ||
| {:else} | ||
| {hf.getCellValue({ sheet: sheetId, row: r, col: c })} | ||
| {/if} | ||
| </td> | ||
| {/each} | ||
| </tr> | ||
| {/each} | ||
| </tbody> | ||
| </table> | ||
| ``` | ||
|
|
||
| ## Server-side rendering (SvelteKit) | ||
|
|
||
| In SvelteKit, top-level statements in `<script>` run on the server too. HyperFormula has no browser-only API dependency, but instantiating it during SSR is wasted work. Move the initialization into `onMount` so it only runs on the client: | ||
|
|
||
| `onMount` is allowed to be `async`, but any cleanup function returned from an async callback is silently ignored — an async function always returns a `Promise`, not the cleanup. Put the teardown in a separate `onDestroy` instead: | ||
|
|
||
| ```html | ||
| <script> | ||
| // Svelte 4 + SvelteKit | ||
| import { onDestroy, onMount } from 'svelte'; | ||
|
|
||
| let hf; | ||
| /** @type {import('hyperformula').CellValue} */ | ||
| let result = null; | ||
|
|
||
| onMount(async () => { | ||
| const { HyperFormula } = await import('hyperformula'); | ||
| hf = HyperFormula.buildFromArray( | ||
| [ | ||
| [1, 2, '=A1+B1'], | ||
| // your data rows go here | ||
| ], | ||
| { licenseKey: 'gpl-v3' } | ||
| ); | ||
| }); | ||
|
|
||
| // Separate onDestroy — async onMount cannot return a cleanup. | ||
| onDestroy(() => hf?.destroy()); | ||
|
|
||
| function calculate() { | ||
| if (!hf) return; | ||
| result = hf.getCellValue({ sheet: 0, row: 0, col: 2 }); | ||
| } | ||
|
|
||
| function reset() { | ||
| result = null; | ||
| } | ||
| </script> | ||
|
|
||
| <button on:click={calculate}>Run calculations</button> | ||
| <button on:click={reset}>Reset</button> | ||
| {#if result !== null} | ||
| <p>Result: <strong>{result}</strong></p> | ||
| {/if} | ||
| ``` | ||
|
|
||
|
|
||
| ## Next steps | ||
|
|
||
| - [Configuration options](configuration-options.md) — full list of `buildFromArray` / `buildEmpty` options | ||
| - [Basic operations](basic-operations.md) — CRUD on cells, rows, columns, sheets | ||
| - [Advanced usage](advanced-usage.md) — multi-sheet workbooks, named expressions | ||
| - [Custom functions](custom-functions.md) — register your own formulas | ||
|
|
||
| ## Demo | ||
|
|
||
| Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}). | ||
| For a more advanced example, check out the <a :href="'https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=' + $page.buildDateURIEncoded">Svelte demo on Stackblitz</a>. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.