diff --git a/projects/ngx-datatable/src/lib/components/body/body-cell.component.spec.ts b/projects/ngx-datatable/src/lib/components/body/body-cell.component.spec.ts
index fd0ca2b5a..85eb9d748 100644
--- a/projects/ngx-datatable/src/lib/components/body/body-cell.component.spec.ts
+++ b/projects/ngx-datatable/src/lib/components/body/body-cell.component.spec.ts
@@ -115,7 +115,6 @@ describe('DataTableBodyCellComponent', () => {
component.setInput('row', { id: 1 });
const columns = toInternalColumn([{ name: 'Tree', prop: 'id', isTreeColumn: true }]);
component.setInput('column', columns[0]);
- component.setInput('treeStatus', '');
});
it('should render tree toggle button when isTreeColumn is true', async () => {
diff --git a/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts b/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts
index e1b4c80bc..e580f9818 100644
--- a/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts
+++ b/projects/ngx-datatable/src/lib/components/body/body-cell.component.ts
@@ -1,16 +1,16 @@
import { NgTemplateOutlet } from '@angular/common';
import {
+ booleanAttribute,
ChangeDetectionStrategy,
- ChangeDetectorRef,
Component,
+ computed,
DoCheck,
ElementRef,
- EventEmitter,
- HostBinding,
- HostListener,
inject,
- Input,
- Output
+ input,
+ linkedSignal,
+ output,
+ signal
} from '@angular/core';
import { NgxDatatableConfig } from '../../ngx-datatable.config';
@@ -22,20 +22,24 @@ import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ENTER } from '../../util
selector: 'datatable-body-cell',
imports: [NgTemplateOutlet],
template: `
+ @let column = this.column();
+ @let row = this.row();
- @if (column.checkboxable && (!displayCheck || displayCheck(row, column, value))) {
+ @let displayCheck = this.displayCheck();
+ @if (column.checkboxable && (!displayCheck || displayCheck(row, column, value()))) {
}
@if (column.isTreeColumn) {
@if (!column.treeToggleTemplate) {
+ @let treeStatus = this.treeStatus() ?? 'collapsed';
} @else {
}
}
@if (!column.cellTemplate) {
@if (column.bindAsUnsafeHtml) {
-
+
} @else {
- {{ value }}
+ {{ value() }}
}
} @else {
}
`,
styleUrl: './body-cell.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ '[class]': 'columnCssClasses()',
+ '[style.width.px]': 'column().width',
+ '[style.minWidth.px]': 'column().minWidth',
+ '[style.maxWidth.px]': 'column().maxWidth',
+ '[style.height]': 'height()',
+ '(focus)': 'onFocus()',
+ '(blur)': 'onBlur()',
+ '(click)': 'onClick($event)',
+ '(dblclick)': 'onDblClick($event)',
+ '(keydown)': 'onKeyDown($event)'
+ }
})
export class DataTableBodyCellComponent implements DoCheck {
- private cd = inject(ChangeDetectorRef);
+ readonly displayCheck = input<(row: TRow, column: TableColumnInternal, value: any) => boolean>();
- @Input() displayCheck?: (row: TRow, column: TableColumnInternal, value: any) => boolean;
+ readonly disabled = input(false, { transform: booleanAttribute });
- @Input() set disabled(value: boolean | undefined) {
- this.cellContext.disabled = value;
- this._disabled = value;
- }
+ readonly group = input();
- get disabled(): boolean | undefined {
- return this._disabled;
- }
+ readonly rowHeight = input(0);
- @Input() set group(group: TRow[] | undefined) {
- this._group = group;
- this.cellContext.group = group;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
+ readonly isSelected = input(false, { transform: booleanAttribute });
- get group() {
- return this._group;
- }
+ readonly rowIndex = input();
- @Input() set rowHeight(val: number) {
- this._rowHeight = val;
- this.cellContext.rowHeight = val;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
+ readonly column = input.required();
- get rowHeight() {
- return this._rowHeight;
- }
-
- @Input() set isSelected(val: boolean | undefined) {
- this._isSelected = val;
- this.cellContext.isSelected = val;
- this.cd.markForCheck();
- }
-
- get isSelected(): boolean | undefined {
- return this._isSelected;
- }
-
- @Input() set expanded(val: boolean | undefined) {
- this._expanded = val;
- this.cellContext.expanded = val;
- this.cd.markForCheck();
- }
-
- get expanded(): boolean | undefined {
- return this._expanded;
- }
-
- @Input() set rowIndex(val: RowIndex) {
- this._rowIndex = val;
- this.cellContext.rowIndex = val?.index;
- this.cellContext.rowInGroupIndex = val?.indexInGroup;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
-
- get rowIndex(): RowIndex {
- return this._rowIndex;
- }
-
- @Input() set column(column: TableColumnInternal) {
- this._column = column;
- this.cellContext.column = column;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
-
- get column(): TableColumnInternal {
- return this._column;
- }
-
- @Input() set row(row: TRow) {
- this._row = row;
- this.cellContext.row = row;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
+ readonly row = input.required();
- get row(): TRow {
- return this._row;
- }
+ readonly treeStatus = input('collapsed');
- @Input() set treeStatus(status: TreeStatus | undefined) {
- if (
- status !== 'collapsed' &&
- status !== 'expanded' &&
- status !== 'loading' &&
- status !== 'disabled'
- ) {
- this._treeStatus = 'collapsed';
- } else {
- this._treeStatus = status;
- }
- this.cellContext.treeStatus = this._treeStatus;
- this.checkValueUpdates();
- this.cd.markForCheck();
- }
+ readonly ariaRowCheckboxMessage = input.required();
- get treeStatus(): TreeStatus | undefined {
- return this._treeStatus;
- }
+ readonly cssClasses = input.required['cssClasses']>>();
- @Input({ required: true }) ariaRowCheckboxMessage!: string;
- @Input({ required: true }) cssClasses!: Partial['cssClasses']>;
+ readonly expanded = input(false, { transform: booleanAttribute });
- @Output() readonly activate = new EventEmitter>();
+ readonly activate = output>();
- @Output() readonly treeAction = new EventEmitter();
+ readonly treeAction = output();
- @HostBinding('class')
- get columnCssClasses(): string {
+ protected readonly columnCssClasses = computed(() => {
let cls = 'datatable-body-cell';
- if (this.column.cellClass) {
- if (typeof this.column.cellClass === 'string') {
- cls += ' ' + this.column.cellClass;
- } else if (typeof this.column.cellClass === 'function') {
- const res = this.column.cellClass({
+ const column = this.column();
+ if (column.cellClass) {
+ if (typeof column.cellClass === 'string') {
+ cls += ' ' + column.cellClass;
+ } else if (typeof column.cellClass === 'function') {
+ const res = column.cellClass({
row: this.row,
- group: this.group,
- column: this.column,
- value: this.value,
- rowHeight: this.rowHeight
+ group: this.group(),
+ column: column,
+ value: this.value(),
+ rowHeight: this.rowHeight()
});
if (typeof res === 'string') {
@@ -224,87 +152,67 @@ export class DataTableBodyCellComponent implements DoChe
}
}
}
- if (this.isFocused && !this._disabled) {
+ if (this.isFocused() && !this.disabled()) {
cls += ' active';
}
- if (this._disabled) {
+ if (this.disabled()) {
cls += ' row-disabled';
}
return cls;
- }
+ });
- @HostBinding('style.width.px')
- get width(): number {
- return this.column.width;
- }
-
- @HostBinding('style.minWidth.px')
- get minWidth(): number | undefined {
- return this.column.minWidth;
- }
-
- @HostBinding('style.maxWidth.px')
- get maxWidth(): number | undefined {
- return this.column.maxWidth;
- }
-
- @HostBinding('style.height')
- get height(): string | number {
- const height = this.rowHeight;
+ protected readonly height = computed(() => {
+ const height = this.rowHeight();
if (isNaN(height)) {
return height;
}
return height + 'px';
- }
-
- sanitizedValue!: string;
- value: any;
- isFocused = false;
-
- cellContext: CellContext;
-
- private _isSelected?: boolean;
- private _column!: TableColumnInternal;
- private _row!: TRow;
- private _group?: TRow[];
- private _rowHeight!: number;
- private _rowIndex!: RowIndex;
- private _expanded?: boolean;
- private _element = inject>(ElementRef).nativeElement;
- private _treeStatus?: TreeStatus;
- private _disabled?: boolean;
-
- constructor() {
- this.cellContext = {
+ });
+
+ protected readonly sanitizedValue = computed(() => {
+ const value = this.value();
+ return value !== null && value !== undefined ? this.stripHtml(value) : value;
+ });
+ readonly value = linkedSignal(() => this.getComputedValue());
+ protected readonly cellContext = computed>(() => {
+ return {
onCheckboxChangeFn: (event: Event) => this.onCheckboxChange(event),
activateFn: (event: ActivateEvent) => this.activate.emit(event),
- row: this.row,
- group: this.group,
- value: this.value,
- column: this.column,
- rowHeight: this.rowHeight,
- isSelected: this.isSelected,
- rowIndex: this.rowIndex?.index,
- rowInGroupIndex: this.rowIndex?.indexInGroup,
- treeStatus: this.treeStatus,
- disabled: this._disabled,
+ row: this.row(),
+ group: this.group(),
+ value: this.value(),
+ column: this.column(),
+ rowHeight: this.rowHeight(),
+ isSelected: this.isSelected(),
+ rowIndex: this.rowIndex()?.index ?? 0,
+ rowInGroupIndex: this.rowIndex()?.indexInGroup,
+ treeStatus: this.treeStatus() ?? 'collapsed',
+ disabled: this.disabled(),
+ expanded: this.expanded(),
onTreeAction: () => this.onTreeAction()
};
- }
+ });
+
+ private readonly isFocused = signal(false);
+ private _element = inject>(ElementRef).nativeElement;
ngDoCheck(): void {
- this.checkValueUpdates();
+ const value = this.getComputedValue();
+ if (value !== this.value()) {
+ this.value.set(value);
+ }
}
- checkValueUpdates(): void {
+ private getComputedValue(): any {
let value = '';
-
- if (!this.row || this.column?.prop == undefined) {
+ const column = this.column();
+ const row = this.row();
+ if (!row || column.prop == undefined) {
value = '';
} else {
- const val = this.column.$$valueGetter(this.row, this.column.prop);
- const userPipe = this.column.pipe;
+ const val = column.$$valueGetter(row, column.prop);
+ const userPipe = column.pipe;
if (userPipe) {
value = userPipe.transform(val);
@@ -312,56 +220,44 @@ export class DataTableBodyCellComponent implements DoChe
value = val;
}
}
-
- if (this.value !== value) {
- this.value = value;
- this.cellContext.value = value;
- this.cellContext.disabled = this._disabled;
- this.sanitizedValue = value !== null && value !== undefined ? this.stripHtml(value) : value;
- this.cd.markForCheck();
- }
+ return value;
}
- @HostListener('focus')
- onFocus(): void {
- this.isFocused = true;
+ protected onFocus(): void {
+ this.isFocused.set(true);
}
- @HostListener('blur')
- onBlur(): void {
- this.isFocused = false;
+ protected onBlur(): void {
+ this.isFocused.set(false);
}
- @HostListener('click', ['$event'])
- onClick(event: MouseEvent): void {
+ protected onClick(event: MouseEvent): void {
this.activate.emit({
type: 'click',
event,
- row: this.row,
- group: this.group,
- rowHeight: this.rowHeight,
- column: this.column,
- value: this.value,
+ row: this.row(),
+ group: this.group(),
+ rowHeight: this.rowHeight(),
+ column: this.column(),
+ value: this.value(),
cellElement: this._element
});
}
- @HostListener('dblclick', ['$event'])
- onDblClick(event: MouseEvent): void {
+ protected onDblClick(event: MouseEvent): void {
this.activate.emit({
type: 'dblclick',
event,
- row: this.row,
- group: this.group,
- rowHeight: this.rowHeight,
- column: this.column,
- value: this.value,
+ row: this.row(),
+ group: this.group(),
+ rowHeight: this.rowHeight(),
+ column: this.column(),
+ value: this.value(),
cellElement: this._element
});
}
- @HostListener('keydown', ['$event'])
- onKeyDown(event: KeyboardEvent): void {
+ protected onKeyDown(event: KeyboardEvent): void {
const key = event.key;
const isTargetCell = event.target === this._element;
@@ -379,11 +275,11 @@ export class DataTableBodyCellComponent implements DoChe
this.activate.emit({
type: 'keydown',
event,
- row: this.row,
- group: this.group,
- rowHeight: this.rowHeight,
- column: this.column,
- value: this.value,
+ row: this.row(),
+ group: this.group(),
+ rowHeight: this.rowHeight(),
+ column: this.column(),
+ value: this.value(),
cellElement: this._element
});
}
@@ -393,11 +289,11 @@ export class DataTableBodyCellComponent implements DoChe
this.activate.emit({
type: 'checkbox',
event,
- row: this.row,
- group: this.group,
- rowHeight: this.rowHeight,
- column: this.column,
- value: this.value,
+ row: this.row(),
+ group: this.group(),
+ rowHeight: this.rowHeight(),
+ column: this.column(),
+ value: this.value(),
cellElement: this._element,
treeStatus: 'collapsed'
});
@@ -410,8 +306,8 @@ export class DataTableBodyCellComponent implements DoChe
return html.replace(/<\/?[^>]+(>|$)/g, '');
}
- onTreeAction() {
- this.treeAction.emit(this.row);
+ protected onTreeAction() {
+ this.treeAction.emit(this.row());
}
calcLeftMargin(column: TableColumnInternal, row: RowOrGroup): number {