Skip to content

Commit d4195b5

Browse files
KrzysztofZiesequbaGreenFluxclaudekzielinskipl
authored
HF-238 - Review "Integration with Angular" guide and demo (#1691)
### Context - Added a modern Angular (v20+) section — an example in the same style as the demo: a service exposing a signal, a component using inject() + OnPush, a template with @if/@for, and bootstrap with provideZonelessChangeDetection. - Marked the old example as the older-versions variant (BehaviorSubject + async pipe), while fixing its compatibility: the component is now standalone with imports: [CommonModule], so it works without an NgModule. - Added a path for very old Angular (≤13) — an NgModule-based apps subsection at the end of the "older" section, completing the three tiers: modern → older/standalone → NgModule. - Moved the Demo section right under the modern Angular code (StackBlitz link, without describing the demo's internal standards). - Clarified the "Provider scope" and "Cleanup" notes to state they apply to both variants (they depend on DI scope, not on signals/RxJS). In short: the guide was rewritten to show the modern pattern (matching the demo) while preserving backward compatibility, with a clear split by Angular version. ### How did you test your changes? <!--- Describe in detail how you tested your changes. --> ### Types of changes <!--- What types of changes does your code introduce? Put an `x` in each box that applies. --> - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Fixes #... 2. 3. ### Checklist: <!--- Go through the points below, and put an `x` in each box that applies. --> <!--- If you're unsure about any of these, contact us. We're always glad to help! --> - [ ] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only changes to the Angular integration guide; no runtime or library code affected. > > **Overview** > Rewrites the **Integration with Angular** guide into a **version-tiered** layout: a new **modern Angular (v20+)** path (signals, `inject()`, OnPush, `@if`/`@for`, `provideZonelessChangeDetection`) and a preserved **older** path (`BehaviorSubject` + `async` pipe). > > The legacy example is updated for **standalone** components (`standalone: true`, `imports: [CommonModule]`) and adds an **`NgModule`-based** subsection for Angular 13 and below. Sample sheet data in snippets changes from `[1, 2, '=A1+B1']` to `[1, 4, '=A1+B1']`. **Provider scope** and **Cleanup** notes now state they apply to both signal and RxJS variants. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 210c1ac. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com> Co-authored-by: GreenFlux <support@greenflux.us> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com> Co-authored-by: krzysztof.zielinski <kzielinski@speednet.pl>
1 parent 79f2f15 commit d4195b5

1 file changed

Lines changed: 126 additions & 4 deletions

File tree

docs/guide/integration-with-angular.md

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,111 @@ The HyperFormula API is identical in an Angular app and in plain JavaScript. Thi
44

55
Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section.
66

7-
## Basic usage
7+
## Basic usage (modern Angular)
88

9-
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.
9+
For modern Angular (v20+) we recommend a service that exposes a **signal**, **standalone** components, the new control flow (`@if` / `@for`) and **zoneless** change detection. Wrap the engine in an `@Injectable` service and expose its values as a read-only signal; the template reads the signal directly and Angular refreshes the view whenever it changes.
10+
11+
```typescript
12+
// spreadsheet.service.ts
13+
import { Injectable, signal } from '@angular/core';
14+
import { HyperFormula, type CellValue } from 'hyperformula';
15+
16+
@Injectable({ providedIn: 'root' })
17+
export class SpreadsheetService {
18+
private readonly hf: HyperFormula;
19+
20+
private readonly _values = signal<CellValue[][]>([]);
21+
readonly values = this._values.asReadonly();
22+
23+
constructor() {
24+
this.hf = HyperFormula.buildFromArray(
25+
[
26+
[1, 4, '=A1+B1'],
27+
// your data rows go here
28+
],
29+
{
30+
licenseKey: 'gpl-v3',
31+
// more configuration options go here
32+
}
33+
);
34+
this._values.set(this.hf.getSheetValues(0));
35+
}
36+
37+
calculate() {
38+
this._values.set(this.hf.getSheetValues(0));
39+
}
40+
41+
reset() {
42+
this._values.set([]);
43+
}
44+
}
45+
```
46+
47+
Inject the service with `inject()`, expose its signal, and use `OnPush` change detection:
48+
49+
```typescript
50+
// spreadsheet.component.ts
51+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
52+
import { SpreadsheetService } from './spreadsheet.service';
53+
54+
@Component({
55+
selector: 'app-spreadsheet',
56+
templateUrl: './spreadsheet.component.html',
57+
changeDetection: ChangeDetectionStrategy.OnPush,
58+
})
59+
export class SpreadsheetComponent {
60+
private readonly spreadsheetService = inject(SpreadsheetService);
61+
62+
readonly values = this.spreadsheetService.values;
63+
64+
runCalculations() {
65+
this.spreadsheetService.calculate();
66+
}
67+
68+
reset() {
69+
this.spreadsheetService.reset();
70+
}
71+
}
72+
```
73+
74+
Read the signal in the template with the new control flow:
75+
76+
```html
77+
<!-- spreadsheet.component.html -->
78+
<button (click)="runCalculations()">Run calculations</button>
79+
<button (click)="reset()">Reset</button>
80+
@if (values().length) {
81+
<table>
82+
@for (row of values(); track $index) {
83+
<tr>
84+
@for (cell of row; track $index) {
85+
<td>{{ cell }}</td>
86+
}
87+
</tr>
88+
}
89+
</table>
90+
}
91+
```
92+
93+
Bootstrap the app with zoneless change detection:
94+
95+
```typescript
96+
// main.ts
97+
import { provideZonelessChangeDetection } from '@angular/core';
98+
import { bootstrapApplication } from '@angular/platform-browser';
99+
import { AppComponent } from './app/app.component';
100+
101+
bootstrapApplication(AppComponent, {
102+
providers: [provideZonelessChangeDetection()],
103+
}).catch((err) => console.error(err));
104+
```
105+
106+
> Signals require Angular 16+, the new control flow Angular 17+, and `provideZonelessChangeDetection` Angular 20+. For earlier versions, use the `BehaviorSubject` approach below.
107+
108+
109+
## Basic usage (older Angular versions)
110+
111+
For broader compatibility — including Angular versions without signals or zoneless change detection — 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.
10112

11113
```typescript
12114
// spreadsheet.service.ts
@@ -24,7 +126,7 @@ export class SpreadsheetService {
24126
constructor() {
25127
this.hf = HyperFormula.buildFromArray(
26128
[
27-
[1, 2, '=A1+B1'],
129+
[1, 4, '=A1+B1'],
28130
// your data rows go here
29131
],
30132
{
@@ -45,17 +147,20 @@ export class SpreadsheetService {
45147
}
46148
```
47149

48-
Consume the service from a component and bind `values$ | async` in the template. Declare the component in your `AppModule` alongside `CommonModule`:
150+
Consume the service from a component and bind `values$ | async` in the template. The component below is **standalone** (the default since Angular 17) and imports `CommonModule` directly, so it works without an `NgModule`. The structural directives `*ngIf` / `*ngFor` and the `async` pipe all come from `CommonModule`:
49151

50152
```typescript
51153
// spreadsheet.component.ts
52154
import { Component } from '@angular/core';
155+
import { CommonModule } from '@angular/common';
53156
import { Observable } from 'rxjs';
54157
import { SpreadsheetService } from './spreadsheet.service';
55158
import { type CellValue } from 'hyperformula';
56159

57160
@Component({
58161
selector: 'app-spreadsheet',
162+
standalone: true,
163+
imports: [CommonModule],
59164
templateUrl: './spreadsheet.component.html',
60165
})
61166
export class SpreadsheetComponent {
@@ -88,10 +193,27 @@ export class SpreadsheetComponent {
88193
</ng-container>
89194
```
90195

196+
### `NgModule`-based apps (Angular 13 and older)
197+
198+
Standalone components require Angular 14 or newer. If your project still uses `NgModule`s (or targets Angular 13 or older), drop the `standalone: true` and `imports` fields from the component above, then declare it in your module and import `CommonModule` there instead:
199+
200+
```typescript
201+
// app.module.ts
202+
@NgModule({
203+
declarations: [SpreadsheetComponent],
204+
imports: [BrowserModule, CommonModule],
205+
})
206+
export class AppModule {}
207+
```
208+
209+
The service and template above are unchanged — only the way the component is wired up differs.
210+
91211
## Notes
92212

93213
### Provider scope
94214

215+
The notes below apply to both the modern and older approaches — provider scope and cleanup depend on how the service is registered, not on whether it exposes a signal or a `BehaviorSubject`.
216+
95217
`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.
96218

97219
### Cleanup

0 commit comments

Comments
 (0)