From 7ae8ca7300c3db8201991030ec64bb739d436667 Mon Sep 17 00:00:00 2001 From: Maximilian Koeller Date: Mon, 3 Nov 2025 19:40:19 +0100 Subject: [PATCH] feat: convert `selected` to signal model with two-way binding Convert the selected input to a signal model. BREAKING CHANGE: The selected input no longer mutates the original array passed to the component. Applications that rely on the array being updated in place must switch to two-way binding with [(selected)] to maintain reactivity. DEPRECATED: The `DatatableComponent.select` output is deprecated; use (selectedChange) or two-way binding instead . Before: ```html ``` After: ```html ``` --- .../lib/components/datatable.component.html | 4 +- .../src/lib/components/datatable.component.ts | 51 ++++++++++++------- .../src/lib/types/public.types.ts | 1 + .../selection/selection-single.component.ts | 15 ++---- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/projects/ngx-datatable/src/lib/components/datatable.component.html b/projects/ngx-datatable/src/lib/components/datatable.component.html index 59dc33003..1758f8381 100644 --- a/projects/ngx-datatable/src/lib/components/datatable.component.html +++ b/projects/ngx-datatable/src/lib/components/datatable.component.html @@ -51,7 +51,6 @@ [offsetX]="_offsetX" [rowDetail]="rowDetail" [groupHeader]="groupHeader" - [selected]="selected" [innerWidth]="_innerWidth" [bodyHeight]="bodyHeight" [selectionType]="selectionType()" @@ -72,6 +71,7 @@ [rowDragEvents]="rowDragEvents" [rowDefTemplate]="rowDefTemplate" [cssClasses]="cssClasses" + [(selected)]="selected" (page)="onBodyPage($event)" (activate)="activate.emit($event)" (rowContextmenu)="onRowContextmenu($event)" @@ -105,7 +105,7 @@ [pagerLeftArrowIcon]="cssClasses.pagerLeftArrow" [pagerRightArrowIcon]="cssClasses.pagerRightArrow" [pagerPreviousIcon]="cssClasses.pagerPrevious" - [selectedCount]="selected.length" + [selectedCount]="selected().length" [selectedMessage]="!!selectionType() && (messages.selectedMessage ?? 'selected')" [pagerNextIcon]="cssClasses.pagerNext" (page)="onFooterPage($event)" diff --git a/projects/ngx-datatable/src/lib/components/datatable.component.ts b/projects/ngx-datatable/src/lib/components/datatable.component.ts index 394fe3d7a..94a36de5d 100644 --- a/projects/ngx-datatable/src/lib/components/datatable.component.ts +++ b/projects/ngx-datatable/src/lib/components/datatable.component.ts @@ -17,6 +17,7 @@ import { Input, IterableDiffer, IterableDiffers, + model, numberAttribute, OnDestroy, OnInit, @@ -195,7 +196,7 @@ export class DatatableComponent * represented as selected in the grid. * Default value: `[]` */ - @Input() selected: TRow[] = []; + readonly selected = model([]); /** * Enable vertical scrollbars @@ -494,6 +495,19 @@ export class DatatableComponent /** * A cell or row was selected. + * @deprecated Use two-way binding on `selected` instead. + * + * Before: + * ```html + * + * ``` + * + * After: + * ```html + * + * + * + * ``` */ @Output() readonly select = new EventEmitter>(); @@ -683,15 +697,16 @@ export class DatatableComponent * Returns if all rows are selected. */ get allRowsSelected(): boolean { - let allRowsSelected = this.rows && this.selected && this.selected.length === this.rows.length; + const selected = this.selected(); + let allRowsSelected = this.rows && selected && selected.length === this.rows.length; if (this.bodyComponent && this.selectAllRowsOnPage()) { const indexes = this.bodyComponent.indexes; const rowsOnPage = indexes().last - indexes().first; - allRowsSelected = this.selected.length === rowsOnPage; + allRowsSelected = selected.length === rowsOnPage; } - return this.selected && this.rows?.length !== 0 && allRowsSelected; + return selected && this.rows?.length !== 0 && allRowsSelected; } element = inject>(ElementRef).nativeElement; @@ -1049,9 +1064,9 @@ export class DatatableComponent }); if (this.selectAllRowsOnPage()) { - this.selected = []; + this.selected.set([]); this.select.emit({ - selected: this.selected + selected: this.selected() }); } } @@ -1189,9 +1204,9 @@ export class DatatableComponent onColumnSort(event: SortEvent): void { // clean selected rows if (this.selectAllRowsOnPage()) { - this.selected = []; + this.selected.set([]); this.select.emit({ - selected: this.selected + selected: this.selected() }); } @@ -1233,14 +1248,13 @@ export class DatatableComponent // before we splice, chk if we currently have all selected const first = this.bodyComponent.indexes().first; const last = this.bodyComponent.indexes().last; - const allSelected = this.selected.length === last - first; - - // remove all existing either way - this.selected = []; + const allSelected = this.selected().length === last - first; // do the opposite here if (!allSelected) { - this.selected.push(...this._internalRows.slice(first, last).filter(row => !!row)); + this.selected.set(this._internalRows.slice(first, last).filter(row => !!row) as TRow[]); + } else { + this.selected.set([]); } } else { let relevantRows: TRow[]; @@ -1253,17 +1267,17 @@ export class DatatableComponent relevantRows = this.rows.filter(row => !!row); } // before we splice, chk if we currently have all selected - const allSelected = this.selected.length === relevantRows.length; - // remove all existing either way - this.selected = []; + const allSelected = this.selected().length === relevantRows.length; // do the opposite here if (!allSelected) { - this.selected.push(...relevantRows); + this.selected.set(relevantRows); + } else { + this.selected.set([]); } } this.select.emit({ - selected: this.selected + selected: this.selected() }); } @@ -1271,7 +1285,6 @@ export class DatatableComponent * A row was selected from body */ onBodySelect(selected: TRow[]): void { - this.selected.splice(0, this.selected.length, ...selected); this.select.emit({ selected }); } diff --git a/projects/ngx-datatable/src/lib/types/public.types.ts b/projects/ngx-datatable/src/lib/types/public.types.ts index d5af9cdfc..2029f4616 100644 --- a/projects/ngx-datatable/src/lib/types/public.types.ts +++ b/projects/ngx-datatable/src/lib/types/public.types.ts @@ -238,6 +238,7 @@ export const SelectionType = { export type SelectionType = (typeof SelectionType)[keyof typeof SelectionType]; +/** @deprecated. Use two-way binding instead. See {@link DatatableComponent.select} */ export interface SelectEvent { selected: TRow[]; } diff --git a/src/app/selection/selection-single.component.ts b/src/app/selection/selection-single.component.ts index a2fa17fd4..a9f115945 100644 --- a/src/app/selection/selection-single.component.ts +++ b/src/app/selection/selection-single.component.ts @@ -1,10 +1,5 @@ import { Component, inject } from '@angular/core'; -import { - ActivateEvent, - DatatableComponent, - SelectEvent, - TableColumn -} from '@siemens/ngx-datatable'; +import { ActivateEvent, DatatableComponent, TableColumn } from '@siemens/ngx-datatable'; import { Employee } from '../data.model'; import { DataService } from '../data.service'; @@ -43,9 +38,9 @@ import { DataService } from '../data.service'; [headerHeight]="50" [footerHeight]="50" [limit]="5" - [selected]="selected" + [(selected)]="selected" (activate)="onActivate($event)" - (select)="onSelect($event)" + (selectedChange)="onSelect($event)" /> @@ -80,9 +75,9 @@ export class SingleSelectionComponent { }); } - onSelect({ selected }: SelectEvent) { + onSelect(employees: Employee[]) { // eslint-disable-next-line no-console - console.log('Select Event', selected, this.selected); + console.log('Select Event', employees); } onActivate(event: ActivateEvent) {