Skip to content

Commit f55cbe2

Browse files
authored
feat(angular)!: migrate Angular-Slickgrid to Zoneless (#2341)
* feat(angular)!: migrate Angular-Slickgrid to Zoneless
1 parent 89e8e26 commit f55cbe2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+381
-379
lines changed

frameworks/angular-slickgrid/angular.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
"index": "src/index.html",
1919
"browser": "src/main.ts",
20-
"polyfills": ["zone.js"],
20+
"polyfills": [],
2121
"tsConfig": "tsconfig.app.json",
2222
"allowedCommonJsDependencies": ["@fnando/sparkline", "core-js", "html2canvas", "raf", "rgbcolor"],
2323
"assets": [

frameworks/angular-slickgrid/docs/column-functionalities/filters/single-search-filter.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ Some users might want to have 1 main single search for filtering the grid data i
1717
<label>Single Search: </label>
1818
<select class="form-control" name="selectedColumn" [(ngModel)]="selectedColumn"
1919
(ngModelChange)="updateFilter()">
20-
<option [ngValue]="field" *ngFor="let field of columnDefinitions">{{field.name}}</option>
20+
@for (field of columnDefinitions; track field) {
21+
<option [ngValue]="field">{{field.name}}</option>
22+
}
2123
</select>
2224
<select class="form-control" name="selectedOperator" [(ngModel)]="selectedOperator"
2325
(ngModelChange)="updateFilter()">
24-
<option [ngValue]="operator" *ngFor="let operator of operatorList">{{operator}}</option>
25-
</select>
26+
@for (operator of operatorList; track operator) {
27+
<option [ngValue]="operator">{{operator}}</option>
28+
}
29+
</select>
2630

2731
<input type="text" class="form-control" name="searchValue" placeholder="search value" autocomplete="off"
2832
(input)="updateFilter()" [(ngModel)]="searchValue">

frameworks/angular-slickgrid/docs/migrations/migration-to-10.x.md

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,24 @@ Because of the Angular v21 upgrade, the user (you) will also need to upgrade [`n
105105
For the complete list of changes, please follow `ngx-translate` migration from their website:
106106
- https://ngx-translate.org/getting-started/migration-guide/
107107

108+
### Angular Zoneless Mode
109+
110+
Starting with v10, Angular-Slickgrid itself now runs in zoneless mode by default. However, your application can still use `zone.js` if you wish and this is entirely at your discretion. For more information about zoneless Angular, see the official Angular documentation: https://angular.dev/guide/zoneless
111+
112+
### Supporting Both Zone.js and Zoneless Users
113+
114+
Angular-Slickgrid now works out-of-the-box in zoneless Angular apps, but still supports applications using `zone.js`:
115+
116+
- If your app uses `zone.js`, you do not need to change anything, manual change detection (e.g., `markForCheck()`, `detectChanges()`) is still not required.
117+
- If your app is zoneless, you do not need to add `zone.js` and should follow the zoneless setup instructions above.
118+
- The library no longer calls `markForCheck()` or `detectChanges()` internally, so UI updates are handled automatically in both modes.
119+
- If you have custom code that relies on manual change detection, review and update it as needed.
120+
- For example, I had to use Signal to ensure UI changes were detected in my OData/GraphQL demos when using the `BackendServiceApi` with a `postProcess` callback and you might need changes when using Pagination as well (in my case I switched to Signals).
121+
122+
> **Tip:** In zoneless Angular, always use signals for any state that should update the UI. For example, if you have a property like `selectedLanguage`, declare it as a signal (`selectedLanguage = signal('en')`) and update it with `selectedLanguage.set('fr')`. In your template, use `selectedLanguage()` to display or bind the value. This ensures UI updates are automatic and you never need manual change detection.
123+
124+
For more details, review the official Angular documentation: https://angular.dev/guide/zoneless
125+
108126
### Migrating to Standalone Component
109127

110128
Angular-Slickgrid is now a Standalone Component and the `AngularSlickgridModule` was dropped, this also requires you to make some small changes in your App `main.ts` and in all your components that use Angular-Slickgrid.
@@ -171,23 +189,23 @@ Below is a list of Enums that you need to replace with their associated string l
171189
| | ... | ... |
172190
| `FieldType` | `FieldType.boolean` | `'boolean'` |
173191
| | `FieldType.number` | `'number'` |
174-
| | `FieldType.dateIso` | `'dateIso'` |
192+
| | `FieldType.dateIso` | `'dateIso'` |
175193
| | ... | ... |
176-
| `FileType` | `FileType.csv` | `'csv'` |
194+
| `FileType` | `FileType.csv` | `'csv'` |
177195
| | `FileType.xlsx` | `'xlsx'` |
178196
| | ... | ... |
179197
| `GridStateType` | `GridStateType.columns` | `'columns'` |
180-
| | `GridStateType.filters` | `'filters'` |
181-
| | `GridStateType.sorters` | `'sorters'` |
198+
| | `GridStateType.filters` | `'filters'` |
199+
| | `GridStateType.sorters` | `'sorters'` |
182200
| | ... | ... |
183201
| `OperatorType` | `OperatorType.greaterThan` | `'>'` or `'GT'` | See [Operator](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/enums/operator.type.ts) list for all available operators |
184-
| | `OperatorType.lessThanOrEqual` | `'<='` or `'LE'` |
185-
| | `OperatorType.contains` | `'Contains'` or `'CONTAINS'` | Operators are written as PascalCase |
186-
| | `OperatorType.equal` | `'='` or `'EQ'` |
187-
| | `OperatorType.rangeExclusive` | `'RangeExclusive'` |
202+
| | `OperatorType.lessThanOrEqual` | `'<='` or `'LE'` |
203+
| | `OperatorType.contains` | `'Contains'` or `'CONTAINS'` | Operators are written as PascalCase |
204+
| | `OperatorType.equal` | `'='` or `'EQ'` |
205+
| | `OperatorType.rangeExclusive` | `'RangeExclusive'` |
188206
| | ... | ... |
189-
| `SortDirection` | `SortDirection.ASC` | `'ASC'` or `'asc'` |
190-
| | `SortDirection.DESC` | `'DESC'` or `'desc'` |
207+
| `SortDirection` | `SortDirection.ASC` | `'ASC'` or `'asc'` |
208+
| | `SortDirection.DESC` | `'DESC'` or `'desc'` |
191209
| | ... | ... |
192210

193211
**Hint** You can use VSCode search & replace, but make sure it's set to Regular Expression pattern
@@ -274,18 +292,18 @@ Wait, are we talking already about version 11 when version 10 just shipped? That
274292

275293
Deprecating `ExtensionName` enum which will be replaced by its string literal type, for example:
276294

277-
| Enum Name | from `enum` | to string `type` |
278-
| ---------------- | ------------------- | ------------------- |
279-
| `ExtensionName` | `ExtensionName.autoTooltip` | `'autoTooltip'` |
280-
| | `ExtensionName.draggableGrouping` | `'draggableGrouping'` |
281-
| | `ExtensionName.rowDetail` | `'rowDetail'` |
295+
| Enum Name | from `enum` | to string `type` |
296+
| ---------------- | --------------------------------- | --------------------- |
297+
| `ExtensionName` | `ExtensionName.autoTooltip` | `'autoTooltip'` |
298+
| | `ExtensionName.draggableGrouping` | `'draggableGrouping'` |
299+
| | `ExtensionName.rowDetail` | `'rowDetail'` |
282300
| ... | ... | ... |
283301

284302
**Hint** You can use VSCode search & replace, but make sure it's set to Regular Expression pattern
285303

286-
| Search (regex) | Replace |
304+
| Search (regex) | Replace |
287305
| ------------------------------ | -------- |
288-
| `ExtensionName\.([a-z_]+)(.*)` | `'$1'$2` |
306+
| `ExtensionName\.([a-z_]+)(.*)` | `'$1'$2` |
289307

290308
### Potential but Postponed Code Change
291309

frameworks/angular-slickgrid/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@
116116
"typescript": "~5.9.3",
117117
"vite": "catalog:",
118118
"vitest": "catalog:",
119-
"yaml": "^2.8.2",
120-
"zone.js": "~0.16.0"
119+
"yaml": "^2.8.2"
121120
}
122121
}

frameworks/angular-slickgrid/src/demos/examples/example05.component.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ <h2>
4747
</div>
4848
</div>
4949
<div class="col-sm-3">
50-
@if (errorStatus) {
50+
@if (errorStatus()) {
5151
<div class="alert alert-danger" data-test="error-status">
52-
<em><strong>Backend Error:</strong> <span [innerHTML]="errorStatus"></span></em>
52+
<em><strong>Backend Error:</strong> <span [innerHTML]="errorStatus()"></span></em>
5353
</div>
5454
}
5555
</div>
5656
</div>
5757

5858
<div class="row">
5959
<div class="col-sm-2">
60-
<div [class]="status.class" role="alert" data-test="status">
61-
<strong>Status: </strong> {{ status.text }}
62-
<span [hidden]="!processing">
60+
<div [class]="status().class" role="alert" data-test="status">
61+
<strong>Status: </strong> {{ status().text }}
62+
<span [hidden]="!processing()">
6363
<i class="mdi mdi-sync mdi-spin-1s"></i>
6464
</span>
6565
</div>
@@ -80,10 +80,10 @@ <h2>
8080
Set Sorting Dynamically
8181
</button>
8282
<br />
83-
@if (metrics) {
83+
@if (metrics()) {
8484
<span>
85-
<b>Metrics:</b> {{ metrics.endTime | date: "yyyy-MM-dd hh:mm aaaaa'm'" }} | {{ metrics.executionTime }}ms |
86-
{{ metrics.totalItemCount }} items
85+
<b>Metrics:</b> {{ metrics()?.endTime | date: "yyyy-MM-dd hh:mm aaaaa'm'" }} | {{ metrics()?.executionTime }}ms |
86+
{{ metrics()?.totalItemCount }} items
8787
</span>
8888
}
8989
</div>
@@ -146,7 +146,7 @@ <h2>
146146
gridId="grid5"
147147
[columns]="columnDefinitions"
148148
[options]="gridOptions"
149-
[paginationOptions]="paginationOptions"
149+
[paginationOptions]="paginationOptions()"
150150
[dataset]="dataset"
151151
(onAngularGridCreated)="angularGridReady($event.detail)"
152152
(onGridStateChanged)="gridStateChanged($event.detail)"

frameworks/angular-slickgrid/src/demos/examples/example05.component.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { DatePipe } from '@angular/common';
22
import { HttpClient } from '@angular/common/http';
3-
import { ChangeDetectorRef, Component, type OnInit } from '@angular/core';
3+
import { Component, signal, type OnInit } from '@angular/core';
44
import { GridOdataService, type OdataOption, type OdataServiceApi } from '@slickgrid-universal/odata';
55
import {
66
AngularSlickgridComponent,
@@ -29,23 +29,20 @@ export class Example5Component implements OnInit {
2929
gridOptions!: GridOption;
3030
dataset = [];
3131
hideSubTitle = false;
32-
metrics!: Metrics;
33-
paginationOptions!: Pagination;
32+
metrics = signal<Metrics | undefined>(undefined);
33+
paginationOptions = signal<Pagination | undefined>(undefined);
3434

3535
isCountEnabled = true;
3636
isSelectEnabled = false;
3737
isExpandEnabled = false;
3838
odataVersion = 2;
3939
odataQuery = '';
40-
processing = true;
41-
errorStatus = '';
40+
processing = signal(true);
41+
errorStatus = signal('');
4242
isPageErrorTest = false;
43-
status = { text: 'processing...', class: 'alert alert-danger' };
43+
status = signal({ text: 'processing...', class: 'alert alert-danger' });
4444

45-
constructor(
46-
private readonly cd: ChangeDetectorRef,
47-
private http: HttpClient
48-
) {}
45+
constructor(private http: HttpClient) {}
4946

5047
angularGridReady(angularGrid: AngularGridInstance) {
5148
this.angularGrid = angularGrid;
@@ -143,32 +140,32 @@ export class Example5Component implements OnInit {
143140
version: this.odataVersion, // defaults to 2, the query string is slightly different between OData 2 and 4
144141
},
145142
onError: (error: Error) => {
146-
this.errorStatus = error.message;
143+
this.errorStatus.set(error.message);
147144
this.displaySpinner(false, true);
148145
},
149146
preProcess: () => {
150-
this.errorStatus = '';
147+
this.errorStatus.set('');
151148
this.displaySpinner(true);
152149
},
153150
process: (query) => this.getCustomerApiCall(query),
154151
postProcess: (response) => {
155-
this.metrics = response.metrics;
152+
this.metrics.set(response.metrics);
156153
this.displaySpinner(false);
157154
this.getCustomerCallback(response);
158-
this.cd.detectChanges();
159155
},
160156
} as OdataServiceApi,
161157
};
162158
}
163159

164160
displaySpinner(isProcessing: boolean, isError?: boolean) {
165-
this.processing = isProcessing;
161+
this.processing.set(isProcessing);
166162
if (isError) {
167-
this.status = { text: 'ERROR!!!', class: 'alert alert-danger' };
163+
this.status.set({ text: 'ERROR!!!', class: 'alert alert-danger' });
168164
} else {
169-
this.status = isProcessing ? { text: 'loading', class: 'alert alert-warning' } : { text: 'finished', class: 'alert alert-success' };
165+
this.status.set(
166+
isProcessing ? { text: 'loading', class: 'alert alert-warning' } : { text: 'finished', class: 'alert alert-success' }
167+
);
170168
}
171-
this.cd.detectChanges();
172169
}
173170

174171
getCustomerCallback(data: any) {
@@ -178,12 +175,12 @@ export class Example5Component implements OnInit {
178175
if (this.isCountEnabled) {
179176
totalItemCount = this.odataVersion === 4 ? data['@odata.count'] : data['d']['__count'];
180177
}
181-
if (this.metrics) {
182-
this.metrics.totalItemCount = totalItemCount;
178+
if (this.metrics()) {
179+
this.metrics.set({ ...this.metrics()!, totalItemCount });
183180
}
184181

185182
// once pagination totalItems is filled, we can update the dataset
186-
this.paginationOptions = { ...this.gridOptions.pagination, totalItems: totalItemCount } as Pagination;
183+
this.paginationOptions.set({ ...this.gridOptions.pagination, totalItems: totalItemCount } as Pagination);
187184
this.dataset = this.odataVersion === 4 ? data.value : data.d.results;
188185
this.odataQuery = data['query'];
189186
}

frameworks/angular-slickgrid/src/demos/examples/example06.component.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ <h2>
4848

4949
<div class="row">
5050
<div class="col-sm-5">
51-
<div [class]="status.class" role="alert" data-test="status">
52-
<strong>Status: </strong> {{ status.text }}
53-
<span [hidden]="!processing">
51+
<div [class]="status().class" role="alert" data-test="status">
52+
<strong>Status: </strong> {{ status().text }}
53+
<span [hidden]="!processing()">
5454
<i class="mdi mdi-sync mdi-spin-1s"></i>
5555
</span>
5656
</div>
@@ -94,7 +94,7 @@ <h2>
9494
</button>
9595
<strong>Locale:</strong>
9696
<span style="font-style: italic" data-test="selected-locale">
97-
{{ selectedLanguage + '.json' }}
97+
{{ selectedLanguage() + '.json' }}
9898
</span>
9999
</div>
100100

@@ -125,10 +125,10 @@ <h2>
125125
</span>
126126
</div>
127127
<br />
128-
@if (metrics) {
128+
@if (metrics()) {
129129
<div style="margin: 10px 0px">
130-
<b>Metrics:</b> {{ metrics.endTime | date: "yyyy-MM-dd hh:mm aaaaa'm'" }} | {{ metrics.executionTime }}ms |
131-
{{ metrics.totalItemCount }} items
130+
<b>Metrics:</b> {{ metrics()?.endTime | date: "yyyy-MM-dd hh:mm aaaaa'm'" }} | {{ metrics()?.executionTime }}ms |
131+
{{ metrics()?.totalItemCount }} items
132132
</div>
133133
}
134134
<div class="row mb-2">

frameworks/angular-slickgrid/src/demos/examples/example06.component.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DatePipe } from '@angular/common';
2-
import { ChangeDetectorRef, Component, type OnDestroy, type OnInit } from '@angular/core';
2+
import { Component, signal, type OnDestroy, type OnInit } from '@angular/core';
33
import { FormsModule } from '@angular/forms';
44
import { addDay, format as tempoFormat } from '@formkit/tempo';
55
import { TranslateService } from '@ngx-translate/core';
@@ -39,23 +39,20 @@ export class Example6Component implements OnInit, OnDestroy {
3939
columnDefinitions!: Column[];
4040
gridOptions!: GridOption;
4141
dataset = [];
42-
metrics!: Metrics;
42+
metrics = signal<Metrics | undefined>(undefined);
4343
hideSubTitle = false;
4444
isWithCursor = false;
4545
graphqlQuery = '';
46-
processing = true;
47-
status = { text: 'processing...', class: 'alert alert-danger' };
48-
selectedLanguage: string;
46+
processing = signal(true);
47+
status = signal({ text: 'processing...', class: 'alert alert-danger' });
48+
selectedLanguage = signal('');
4949
serverWaitDelay = FAKE_SERVER_DELAY; // server simulation with default of 250ms but 50ms for Cypress tests
5050

51-
constructor(
52-
private readonly cd: ChangeDetectorRef,
53-
private translate: TranslateService
54-
) {
51+
constructor(private translate: TranslateService) {
5552
// always start with English for Cypress E2E tests to be consistent
5653
const defaultLang = 'en';
5754
this.translate.use(defaultLang);
58-
this.selectedLanguage = defaultLang;
55+
this.selectedLanguage.set(defaultLang);
5956
}
6057

6158
ngOnDestroy() {
@@ -271,9 +268,8 @@ export class Example6Component implements OnInit, OnDestroy {
271268
preProcess: () => this.displaySpinner(true),
272269
process: (query) => this.getCustomerApiCall(query),
273270
postProcess: (result: GraphqlPaginatedResult) => {
274-
this.metrics = result.metrics as Metrics;
271+
this.metrics.set(result.metrics as Metrics);
275272
this.displaySpinner(false);
276-
this.cd.detectChanges();
277273
},
278274
} as GraphqlServiceApi,
279275
};
@@ -284,10 +280,10 @@ export class Example6Component implements OnInit, OnDestroy {
284280
}
285281

286282
displaySpinner(isProcessing: boolean) {
287-
this.processing = isProcessing;
288-
this.status = isProcessing
289-
? { text: 'processing...', class: 'alert alert-danger' }
290-
: { text: 'finished', class: 'alert alert-success' };
283+
this.processing.set(isProcessing);
284+
this.status.set(
285+
isProcessing ? { text: 'processing...', class: 'alert alert-danger' } : { text: 'finished', class: 'alert alert-success' }
286+
);
291287
}
292288

293289
/**
@@ -443,10 +439,10 @@ export class Example6Component implements OnInit, OnDestroy {
443439
}
444440

445441
switchLanguage() {
446-
const nextLanguage = this.selectedLanguage === 'en' ? 'fr' : 'en';
442+
const nextLanguage = this.selectedLanguage() === 'en' ? 'fr' : 'en';
447443
this.subscriptions.push(
448444
this.translate.use(nextLanguage).subscribe(() => {
449-
this.selectedLanguage = nextLanguage;
445+
this.selectedLanguage.set(nextLanguage);
450446
})
451447
);
452448
}

frameworks/angular-slickgrid/src/demos/examples/example08.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h2>
4545
</button>
4646
<strong>Locale:</strong>
4747
<span style="font-style: italic" data-test="selected-locale">
48-
{{ selectedLanguage + '.json' }}
48+
{{ selectedLanguage() + '.json' }}
4949
</span>
5050

5151
<div class="col-sm-12">

0 commit comments

Comments
 (0)