From de240430f0e15e1667637bf8336d7dfb4c05eab4 Mon Sep 17 00:00:00 2001 From: chintankavathia Date: Wed, 17 Sep 2025 16:00:31 +0530 Subject: [PATCH] refactor(row-wrapper): use signals for internals --- .../body/body-row-wrapper.component.ts | 176 +++++++++--------- 1 file changed, 86 insertions(+), 90 deletions(-) diff --git a/projects/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts b/projects/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts index c529fa924..b7fb4e5d1 100644 --- a/projects/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts +++ b/projects/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts @@ -2,27 +2,24 @@ import { NgTemplateOutlet } from '@angular/common'; import { booleanAttribute, ChangeDetectionStrategy, - ChangeDetectorRef, Component, DoCheck, ElementRef, - EventEmitter, HostListener, inject, - Input, IterableDiffer, IterableDiffers, KeyValueDiffer, KeyValueDiffers, - OnChanges, OnInit, - Output, signal, - SimpleChanges, - ViewChild + input, + output, + viewChild, + linkedSignal } from '@angular/core'; -import { Group, GroupContext, Row, RowDetailContext, RowOrGroup } from '../../types/public.types'; +import { Group, Row, RowOrGroup, RowDetailContext, GroupContext } from '../../types/public.types'; import { DATATABLE_COMPONENT_TOKEN } from '../../utils/table-token'; import { DatatableRowDetailDirective } from '../row-detail/row-detail.directive'; import { DatatableGroupHeaderDirective } from './body-group-header.directive'; @@ -31,20 +28,22 @@ import { DatatableGroupHeaderDirective } from './body-group-header.directive'; selector: 'datatable-row-wrapper', imports: [NgTemplateOutlet], template: ` - @if (isGroup(row) && groupHeader?.template) { + @let row = this.row(); + @let groupHeader = this.groupHeader(); + @if (isGroup(row) && groupHeader && groupHeader.template) {
- @if (groupHeader!.checkboxable) { + @if (groupHeader.checkboxable) {
}
} - @if ((groupHeader?.template && expanded) || !groupHeader || !groupHeader.template) { + @if ((groupHeader?.template && expanded()) || !groupHeader || !groupHeader?.template) { } - @let rowDetailTemplate = rowDetail?.template(); - @if (rowDetailTemplate && expanded) { -
- + @let rowDetailTemplate = rowDetail()?.template(); + @if (rowDetailTemplate && expanded()) { +
+
} `, @@ -74,33 +73,47 @@ import { DatatableGroupHeaderDirective } from './body-group-header.directive'; class: 'datatable-row-wrapper' } }) -export class DataTableRowWrapperComponent - implements DoCheck, OnInit, OnChanges -{ - @ViewChild('select') checkBoxInput!: ElementRef; - @Input() innerWidth!: number; - @Input() rowDetail?: DatatableRowDetailDirective; - @Input() groupHeader?: DatatableGroupHeaderDirective; - @Input() offsetX!: number; - @Input() detailRowHeight!: number; - @Input() groupHeaderRowHeight!: number; - @Input() row!: RowOrGroup; - @Input() groupedRows?: Group[]; - @Input() selected!: TRow[]; - @Input() disabled?: boolean; - @Output() readonly rowContextmenu = new EventEmitter<{ +export class DataTableRowWrapperComponent implements DoCheck, OnInit { + readonly checkBoxInput = viewChild>('select'); + readonly innerWidth = input.required(); + readonly rowDetail = input(); + readonly groupHeader = input(); + readonly offsetX = input.required(); + readonly detailRowHeight = input.required(); + readonly groupHeaderRowHeight = input.required(); + readonly row = input.required>(); + readonly groupedRows = input[]>(); + readonly selected = input.required(); + readonly disabled = input(); + readonly rowContextmenu = output<{ event: MouseEvent; row: RowOrGroup; - }>(false); + }>(); - @Input() rowIndex!: number; + readonly rowIndex = input.required(); readonly selectedGroupRows = signal([]); - @Input({ transform: booleanAttribute }) expanded = false; - @Input({ required: true }) ariaGroupHeaderCheckboxMessage!: string; + readonly expanded = input(false, { transform: booleanAttribute }); + readonly ariaGroupHeaderCheckboxMessage = input.required(); - context!: RowDetailContext | GroupContext; + readonly context = linkedSignal | GroupContext>(() => { + const row = this.row(); + if (this.isGroup(row)) { + return { + group: row, + expanded: this.expanded(), + rowIndex: this.rowIndex() + }; + } else { + return { + row, + expanded: this.expanded(), + rowIndex: this.rowIndex(), + disabled: this.disabled() + }; + } + }); private rowDiffer: KeyValueDiffer, any> = inject(KeyValueDiffers) .find({}) @@ -108,65 +121,48 @@ export class DataTableRowWrapperComponent private iterableDiffers = inject(IterableDiffers); private selectedRowsDiffer!: IterableDiffer; private tableComponent = inject(DATATABLE_COMPONENT_TOKEN); - private cd = inject(ChangeDetectorRef); - - ngOnChanges(changes: SimpleChanges): void { - if (changes.row) { - // this component renders either a group header or a row. Never both. - if (this.isGroup(this.row)) { - this.context = { - group: this.row, - expanded: this.expanded, - rowIndex: this.rowIndex - }; - } else { - this.context = { - row: this.row, - expanded: this.expanded, - rowIndex: this.rowIndex, - disabled: this.disabled - }; - } - } - if (changes.rowIndex) { - this.context.rowIndex = this.rowIndex; - } - if (changes.expanded) { - this.context.expanded = this.expanded; - } - } ngOnInit(): void { - this.selectedRowsDiffer = this.iterableDiffers.find(this.selected ?? []).create(); + this.selectedRowsDiffer = this.iterableDiffers.find(this.selected() ?? []).create(); } ngDoCheck(): void { - if (this.rowDiffer.diff(this.row)) { - if ('group' in this.context) { - this.context.group = this.row as Group; + const row = this.row(); + if (this.rowDiffer.diff(row)) { + if ('group' in this.context()) { + this.context.set({ + group: row as Group, + expanded: this.expanded(), + rowIndex: this.rowIndex() + }); } else { - this.context.row = this.row as TRow; + this.context.set({ + row: row as TRow, + expanded: this.expanded(), + rowIndex: this.rowIndex(), + disabled: this.disabled() + }); } - this.cd.markForCheck(); } // When groupheader is used with chechbox we use iterableDiffer // on currently selected rows to check if it is modified // if any of the row of this group is not present in `selected` rows array // mark group header checkbox state as indeterminate if ( - this.isGroup(this.row) && - this.groupHeader?.checkboxable && - this.selectedRowsDiffer.diff(this.selected) + this.isGroup(row) && + this.groupHeader()?.checkboxable && + this.selectedRowsDiffer.diff(this.selected()) ) { - const thisRow = this.row; - const selectedRows = this.selected.filter(row => - thisRow.value.find((item: TRow) => item === row) + const thisRow = row; + const selectedRows = this.selected().filter(rowItem => + thisRow.value.find((item: TRow) => item === rowItem) ); - if (this.checkBoxInput) { - if (selectedRows.length && selectedRows.length !== this.row.value.length) { - this.checkBoxInput.nativeElement.indeterminate = true; + const checkBoxInput = this.checkBoxInput(); + if (checkBoxInput) { + if (selectedRows.length && selectedRows.length !== row.value.length) { + checkBoxInput.nativeElement.indeterminate = true; } else { - this.checkBoxInput.nativeElement.indeterminate = false; + checkBoxInput.nativeElement.indeterminate = false; } } this.selectedGroupRows.set(selectedRows); @@ -175,27 +171,27 @@ export class DataTableRowWrapperComponent @HostListener('contextmenu', ['$event']) onContextmenu($event: MouseEvent): void { - this.rowContextmenu.emit({ event: $event, row: this.row }); + this.rowContextmenu.emit({ event: $event, row: this.row() }); } onCheckboxChange(groupSelected: boolean, group: Group): void { // First remove all rows of this group from `selected` - this.selected = [ - ...this.selected.filter(row => !group.value.find((item: TRow) => item === row)) + let selected = [ + ...this.selected().filter(row => !group.value.find((item: TRow) => item === row)) ]; // If checkbox is checked then add all rows of this group in `selected` if (groupSelected) { - this.selected = [...this.selected, ...group.value]; + selected = [...this.selected(), ...group.value]; } // Update `selected` of DatatableComponent with newly evaluated `selected` - this.tableComponent.selected = [...this.selected]; + this.tableComponent.selected = [...selected]; // Emit select event with updated values this.tableComponent.onBodySelect({ - selected: this.selected + selected: [...selected] }); } isGroup(row: RowOrGroup): row is Group { - return !!this.groupHeader; + return !!this.groupHeader(); } }