Skip to content

Commit de24043

Browse files
refactor(row-wrapper): use signals for internals
1 parent 1d61121 commit de24043

File tree

1 file changed

+86
-90
lines changed

1 file changed

+86
-90
lines changed

projects/ngx-datatable/src/lib/components/body/body-row-wrapper.component.ts

Lines changed: 86 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,24 @@ import { NgTemplateOutlet } from '@angular/common';
22
import {
33
booleanAttribute,
44
ChangeDetectionStrategy,
5-
ChangeDetectorRef,
65
Component,
76
DoCheck,
87
ElementRef,
9-
EventEmitter,
108
HostListener,
119
inject,
12-
Input,
1310
IterableDiffer,
1411
IterableDiffers,
1512
KeyValueDiffer,
1613
KeyValueDiffers,
17-
OnChanges,
1814
OnInit,
19-
Output,
2015
signal,
21-
SimpleChanges,
22-
ViewChild
16+
input,
17+
output,
18+
viewChild,
19+
linkedSignal
2320
} from '@angular/core';
2421

25-
import { Group, GroupContext, Row, RowDetailContext, RowOrGroup } from '../../types/public.types';
22+
import { Group, Row, RowOrGroup, RowDetailContext, GroupContext } from '../../types/public.types';
2623
import { DATATABLE_COMPONENT_TOKEN } from '../../utils/table-token';
2724
import { DatatableRowDetailDirective } from '../row-detail/row-detail.directive';
2825
import { DatatableGroupHeaderDirective } from './body-group-header.directive';
@@ -31,40 +28,42 @@ import { DatatableGroupHeaderDirective } from './body-group-header.directive';
3128
selector: 'datatable-row-wrapper',
3229
imports: [NgTemplateOutlet],
3330
template: `
34-
@if (isGroup(row) && groupHeader?.template) {
31+
@let row = this.row();
32+
@let groupHeader = this.groupHeader();
33+
@if (isGroup(row) && groupHeader && groupHeader.template) {
3534
<div
3635
class="datatable-group-header"
37-
[style.height.px]="groupHeaderRowHeight"
38-
[style.width.px]="innerWidth"
36+
[style.height.px]="groupHeaderRowHeight()"
37+
[style.width.px]="innerWidth()"
3938
>
4039
<div class="datatable-group-cell">
41-
@if (groupHeader!.checkboxable) {
40+
@if (groupHeader.checkboxable) {
4241
<div>
4342
<label class="datatable-checkbox">
4443
<input
4544
#select
4645
type="checkbox"
47-
[attr.aria-label]="ariaGroupHeaderCheckboxMessage"
46+
[attr.aria-label]="ariaGroupHeaderCheckboxMessage()"
4847
[checked]="selectedGroupRows().length === row.value.length"
4948
(change)="onCheckboxChange(select.checked, row)"
5049
/>
5150
</label>
5251
</div>
5352
}
5453
<ng-template
55-
[ngTemplateOutlet]="groupHeader!.template!"
56-
[ngTemplateOutletContext]="context"
54+
[ngTemplateOutlet]="groupHeader.template"
55+
[ngTemplateOutletContext]="context()"
5756
/>
5857
</div>
5958
</div>
6059
}
61-
@if ((groupHeader?.template && expanded) || !groupHeader || !groupHeader.template) {
60+
@if ((groupHeader?.template && expanded()) || !groupHeader || !groupHeader?.template) {
6261
<ng-content />
6362
}
64-
@let rowDetailTemplate = rowDetail?.template();
65-
@if (rowDetailTemplate && expanded) {
66-
<div class="datatable-row-detail" [style.height.px]="detailRowHeight">
67-
<ng-template [ngTemplateOutlet]="rowDetailTemplate" [ngTemplateOutletContext]="context" />
63+
@let rowDetailTemplate = rowDetail()?.template();
64+
@if (rowDetailTemplate && expanded()) {
65+
<div class="datatable-row-detail" [style.height.px]="detailRowHeight()">
66+
<ng-template [ngTemplateOutlet]="rowDetailTemplate" [ngTemplateOutletContext]="context()" />
6867
</div>
6968
}
7069
`,
@@ -74,99 +73,96 @@ import { DatatableGroupHeaderDirective } from './body-group-header.directive';
7473
class: 'datatable-row-wrapper'
7574
}
7675
})
77-
export class DataTableRowWrapperComponent<TRow extends Row = any>
78-
implements DoCheck, OnInit, OnChanges
79-
{
80-
@ViewChild('select') checkBoxInput!: ElementRef<HTMLInputElement>;
81-
@Input() innerWidth!: number;
82-
@Input() rowDetail?: DatatableRowDetailDirective;
83-
@Input() groupHeader?: DatatableGroupHeaderDirective;
84-
@Input() offsetX!: number;
85-
@Input() detailRowHeight!: number;
86-
@Input() groupHeaderRowHeight!: number;
87-
@Input() row!: RowOrGroup<TRow>;
88-
@Input() groupedRows?: Group<TRow>[];
89-
@Input() selected!: TRow[];
90-
@Input() disabled?: boolean;
91-
@Output() readonly rowContextmenu = new EventEmitter<{
76+
export class DataTableRowWrapperComponent<TRow extends Row = any> implements DoCheck, OnInit {
77+
readonly checkBoxInput = viewChild<ElementRef<HTMLInputElement>>('select');
78+
readonly innerWidth = input.required<number>();
79+
readonly rowDetail = input<DatatableRowDetailDirective>();
80+
readonly groupHeader = input<DatatableGroupHeaderDirective>();
81+
readonly offsetX = input.required<number>();
82+
readonly detailRowHeight = input.required<number>();
83+
readonly groupHeaderRowHeight = input.required<number>();
84+
readonly row = input.required<RowOrGroup<TRow>>();
85+
readonly groupedRows = input<Group<TRow>[]>();
86+
readonly selected = input.required<TRow[]>();
87+
readonly disabled = input<boolean>();
88+
readonly rowContextmenu = output<{
9289
event: MouseEvent;
9390
row: RowOrGroup<TRow>;
94-
}>(false);
91+
}>();
9592

96-
@Input() rowIndex!: number;
93+
readonly rowIndex = input.required<number>();
9794

9895
readonly selectedGroupRows = signal<TRow[]>([]);
9996

100-
@Input({ transform: booleanAttribute }) expanded = false;
101-
@Input({ required: true }) ariaGroupHeaderCheckboxMessage!: string;
97+
readonly expanded = input(false, { transform: booleanAttribute });
98+
readonly ariaGroupHeaderCheckboxMessage = input.required<string>();
10299

103-
context!: RowDetailContext<TRow> | GroupContext<TRow>;
100+
readonly context = linkedSignal<RowDetailContext<TRow> | GroupContext<TRow>>(() => {
101+
const row = this.row();
102+
if (this.isGroup(row)) {
103+
return {
104+
group: row,
105+
expanded: this.expanded(),
106+
rowIndex: this.rowIndex()
107+
};
108+
} else {
109+
return {
110+
row,
111+
expanded: this.expanded(),
112+
rowIndex: this.rowIndex(),
113+
disabled: this.disabled()
114+
};
115+
}
116+
});
104117

105118
private rowDiffer: KeyValueDiffer<keyof RowOrGroup<TRow>, any> = inject(KeyValueDiffers)
106119
.find({})
107120
.create();
108121
private iterableDiffers = inject(IterableDiffers);
109122
private selectedRowsDiffer!: IterableDiffer<TRow>;
110123
private tableComponent = inject(DATATABLE_COMPONENT_TOKEN);
111-
private cd = inject(ChangeDetectorRef);
112-
113-
ngOnChanges(changes: SimpleChanges): void {
114-
if (changes.row) {
115-
// this component renders either a group header or a row. Never both.
116-
if (this.isGroup(this.row)) {
117-
this.context = {
118-
group: this.row,
119-
expanded: this.expanded,
120-
rowIndex: this.rowIndex
121-
};
122-
} else {
123-
this.context = {
124-
row: this.row,
125-
expanded: this.expanded,
126-
rowIndex: this.rowIndex,
127-
disabled: this.disabled
128-
};
129-
}
130-
}
131-
if (changes.rowIndex) {
132-
this.context.rowIndex = this.rowIndex;
133-
}
134-
if (changes.expanded) {
135-
this.context.expanded = this.expanded;
136-
}
137-
}
138124

139125
ngOnInit(): void {
140-
this.selectedRowsDiffer = this.iterableDiffers.find(this.selected ?? []).create();
126+
this.selectedRowsDiffer = this.iterableDiffers.find(this.selected() ?? []).create();
141127
}
142128

143129
ngDoCheck(): void {
144-
if (this.rowDiffer.diff(this.row)) {
145-
if ('group' in this.context) {
146-
this.context.group = this.row as Group<TRow>;
130+
const row = this.row();
131+
if (this.rowDiffer.diff(row)) {
132+
if ('group' in this.context()) {
133+
this.context.set({
134+
group: row as Group<TRow>,
135+
expanded: this.expanded(),
136+
rowIndex: this.rowIndex()
137+
});
147138
} else {
148-
this.context.row = this.row as TRow;
139+
this.context.set({
140+
row: row as TRow,
141+
expanded: this.expanded(),
142+
rowIndex: this.rowIndex(),
143+
disabled: this.disabled()
144+
});
149145
}
150-
this.cd.markForCheck();
151146
}
152147
// When groupheader is used with chechbox we use iterableDiffer
153148
// on currently selected rows to check if it is modified
154149
// if any of the row of this group is not present in `selected` rows array
155150
// mark group header checkbox state as indeterminate
156151
if (
157-
this.isGroup(this.row) &&
158-
this.groupHeader?.checkboxable &&
159-
this.selectedRowsDiffer.diff(this.selected)
152+
this.isGroup(row) &&
153+
this.groupHeader()?.checkboxable &&
154+
this.selectedRowsDiffer.diff(this.selected())
160155
) {
161-
const thisRow = this.row;
162-
const selectedRows = this.selected.filter(row =>
163-
thisRow.value.find((item: TRow) => item === row)
156+
const thisRow = row;
157+
const selectedRows = this.selected().filter(rowItem =>
158+
thisRow.value.find((item: TRow) => item === rowItem)
164159
);
165-
if (this.checkBoxInput) {
166-
if (selectedRows.length && selectedRows.length !== this.row.value.length) {
167-
this.checkBoxInput.nativeElement.indeterminate = true;
160+
const checkBoxInput = this.checkBoxInput();
161+
if (checkBoxInput) {
162+
if (selectedRows.length && selectedRows.length !== row.value.length) {
163+
checkBoxInput.nativeElement.indeterminate = true;
168164
} else {
169-
this.checkBoxInput.nativeElement.indeterminate = false;
165+
checkBoxInput.nativeElement.indeterminate = false;
170166
}
171167
}
172168
this.selectedGroupRows.set(selectedRows);
@@ -175,27 +171,27 @@ export class DataTableRowWrapperComponent<TRow extends Row = any>
175171

176172
@HostListener('contextmenu', ['$event'])
177173
onContextmenu($event: MouseEvent): void {
178-
this.rowContextmenu.emit({ event: $event, row: this.row });
174+
this.rowContextmenu.emit({ event: $event, row: this.row() });
179175
}
180176

181177
onCheckboxChange(groupSelected: boolean, group: Group<TRow>): void {
182178
// First remove all rows of this group from `selected`
183-
this.selected = [
184-
...this.selected.filter(row => !group.value.find((item: TRow) => item === row))
179+
let selected = [
180+
...this.selected().filter(row => !group.value.find((item: TRow) => item === row))
185181
];
186182
// If checkbox is checked then add all rows of this group in `selected`
187183
if (groupSelected) {
188-
this.selected = [...this.selected, ...group.value];
184+
selected = [...this.selected(), ...group.value];
189185
}
190186
// Update `selected` of DatatableComponent with newly evaluated `selected`
191-
this.tableComponent.selected = [...this.selected];
187+
this.tableComponent.selected = [...selected];
192188
// Emit select event with updated values
193189
this.tableComponent.onBodySelect({
194-
selected: this.selected
190+
selected: [...selected]
195191
});
196192
}
197193

198194
isGroup(row: RowOrGroup<TRow>): row is Group<TRow> {
199-
return !!this.groupHeader;
195+
return !!this.groupHeader();
200196
}
201197
}

0 commit comments

Comments
 (0)