- @if (!noFilter && (standaloneFilter || noActions)) {
-
- }
- @if (!noActions) {
-
-
-
-
- @if (!noFilter && !standaloneFilter) {
-
-
-
- }
-
- @if (!noFilter && !standaloneFilter) {
-
-
- @if (filterControl.value) {
-
- }
-
- }
-
-
-
-
-
- }
-
-
-
- Quantity:
- {{
- progress
- ? '...'
- : data()?.length
- ? data().length + (hasMore ? ' (more available)' : ' (all)')
- : '0'
- }}
- @if (filterControl.value && !externalFilter()) {
- | Filtered: {{ filteredDataLength ?? '...' }}
- }
- @if (selected?.length) {
- |
- Selected: {{ selected.length }}
- }
-
-
-
- @if (progress || (!externalFilter() && (filterProgress$ | async))) {
-
- }
-
-
- |
-
-
- reorder
-
-
- |
-
-
- @if (rowSelectable) {
-
- }
- @for (col of columnsObjects.values(); track col) {
-
- @if (col.sortable) {
-
- {{ col.header }}
- |
- } @else {
-
- {{ col.header }}
- |
- }
-
+
+
+
+
+
+
+
+
+ |
+
+
+ reorder
-
-
- @if (!col.cellTemplate && !cellTemplate[col.field]) {
-
- } @else {
-
- }
-
-
- |
-
- }
-
-
-
-
-
- {{ index + 1 }}-->
+
+ |
+
+ @if (rowSelectable()) {
+
+ }
+ @for (col of displayedNormColumns$ | async; track col; let colIndex = $index) {
+ @let stickyStart = col.params.sticky === 'start';
+ @let stickyEnd = col.params.sticky === 'end';
+ @let columnClasses =
+ {
+ column: true,
+ 'column__sticky-start': stickyStart,
+ 'column__sticky-end': stickyEnd,
+ };
+
+
- {{
- progress
- ? 'Loading...'
- : !externalFilter() && (filterProgress$ | async)
- ? 'Filtering...'
- : filteredDataLength === 0
- ? 'No records found'
- : 'No records'
- }}
+
+ |
+
+
+ @let cell = columnsData?.get?.(element)?.get(col);
+
+
+ |
+
+
+
|
+ }
-
-
-
-
- @if (hasShowMore) {
-
- }
+ |
+
+
+
+
-
-
-
-
-
-
-
- @if (!externalFilter()) {
-
- }
-
diff --git a/projects/ng-core/src/lib/components/table/table.component.scss b/projects/ng-core/src/lib/components/table/table.component.scss
index 2d949cc..b620fe4 100644
--- a/projects/ng-core/src/lib/components/table/table.component.scss
+++ b/projects/ng-core/src/lib/components/table/table.component.scss
@@ -1,3 +1,72 @@
+:host {
+ min-height: 0;
+}
+
+.wrapper {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: max-content;
+ gap: 24px;
+ height: 100%;
+
+ .card {
+ width: 100%;
+ height: 100%;
+ min-height: 300px;
+ overflow: auto;
+ transform: translateZ(0);
+
+ ::ng-deep .mdc-data-table__row:last-child .mat-mdc-cell {
+ border-bottom-color: var(
+ --mat-table-row-item-outline-color,
+ rgba(0, 0, 0, 0.12)
+ ) !important;
+ border-bottom-width: var(--mat-table-row-item-outline-width, 1px) !important;
+ border-bottom-style: solid !important;
+ }
+ }
+}
+
+table,
+::ng-deep .cdk-drag-preview {
+ ::ng-deep .cdk-drag-placeholder {
+ background: #eee;
+ }
+
+ .position {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .dragCursor {
+ cursor: move;
+ }
+ }
+
+ .column {
+ max-width: max(20px, 30vw);
+
+ &__sticky-start,
+ &__sticky-end {
+ width: 0;
+ }
+
+ &__sticky-start {
+ border-right: 1px solid;
+ }
+
+ &__sticky-end {
+ border-left: 1px solid;
+ }
+ }
+
+ .row__hidden {
+ display: none;
+ }
+}
+
+// Drag
+
::ng-deep .cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
@@ -18,94 +87,8 @@
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
-.table {
- display: grid;
- grid-template-columns: 1fr;
- gap: 24px;
-}
-
-.table,
::ng-deep .cdk-drag-preview {
- ::ng-deep .cdk-drag-placeholder {
- background: #eee;
- }
-
- .position {
- display: flex;
- align-items: center;
- gap: 8px;
-
- .dragCursor {
- cursor: move;
- }
- }
-
- .toolbar {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
-
- ::ng-deep & > * {
- flex: 1;
- flex-basis: 200px;
- }
- }
-
- .v-table-filter,
- .v-table-filter-standalone {
- ::ng-deep .mat-mdc-form-field-subscript-wrapper {
- height: 0;
- }
- }
-
- .table-card-details {
- display: grid;
- grid-template-columns: 1fr;
- gap: 8px;
-
- .details {
- display: flex;
- justify-content: space-between;
-
- & > *,
- mat-icon {
- font-size: 12px;
- color: rgba(0, 0, 0, 0.54);
- }
-
- .action {
- cursor: pointer;
- text-decoration: underline;
- text-decoration-style: dotted;
- }
- }
-
- .table-card {
- width: 100%;
- overflow: auto;
-
- .progress-bar {
- position: absolute;
- z-index: 999;
- }
-
- .pinned-right,
- .pinned-left {
- width: 0;
- }
-
- .pinned-right {
- border-left: 1px solid;
- }
-
- .pinned-left {
- border-right: 1px solid;
- }
-
- .no-records {
- text-align: center;
- height: 52px * 3;
- }
- }
+ ::ng-deep td {
+ border: none !important;
}
}
diff --git a/projects/ng-core/src/lib/components/table/table.component.ts b/projects/ng-core/src/lib/components/table/table.component.ts
index f372c99..881d63e 100644
--- a/projects/ng-core/src/lib/components/table/table.component.ts
+++ b/projects/ng-core/src/lib/components/table/table.component.ts
@@ -1,506 +1,428 @@
-import { CdkDragDrop } from '@angular/cdk/drag-drop';
+import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
+import { CommonModule } from '@angular/common';
import {
- AfterViewInit,
ChangeDetectionStrategy,
Component,
- ContentChild,
- DestroyRef,
- EventEmitter,
- Input,
- OnChanges,
- OnInit,
- Output,
- ViewChild,
- numberAttribute,
- booleanAttribute,
- OnDestroy,
input,
+ computed,
+ DestroyRef,
+ booleanAttribute,
+ numberAttribute,
+ signal,
+ output,
Injector,
+ ElementRef,
+ runInInjectionContext,
+ OnInit,
+ ViewChild,
+ ContentChild,
+ model,
+ ChangeDetectorRef,
} from '@angular/core';
-import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
-import { FormControl } from '@angular/forms';
-import { MatSort, Sort } from '@angular/material/sort';
-import { MatTableDataSource, MatTable } from '@angular/material/table';
-// eslint-disable-next-line @typescript-eslint/naming-convention
-import Fuse from 'fuse.js';
+import { toObservable, takeUntilDestroyed, outputFromObservable } from '@angular/core/rxjs-interop';
+import { MatIconButton, MatButton } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatIcon } from '@angular/material/icon';
+import { MatSort, MatSortModule, Sort } from '@angular/material/sort';
+import { MatTableModule, MatTable, MatRow } from '@angular/material/table';
+import { MatTooltip } from '@angular/material/tooltip';
import {
combineLatest,
- map,
- of,
- take,
- debounceTime,
+ Observable,
switchMap,
+ take,
forkJoin,
BehaviorSubject,
+ debounceTime,
+ first,
+ merge,
tap,
- Observable,
- catchError,
+ defer,
+ Subject,
} from 'rxjs';
-import { distinctUntilChanged, startWith } from 'rxjs/operators';
-
-import { QueryParamsService, QueryParamsNamespace } from '../../services';
-import { Progressable } from '../../types/progressable';
import {
- compareDifferentTypes,
- ComponentChanges,
- select,
- getPossiblyAsyncObservable,
-} from '../../utils';
+ shareReplay,
+ map,
+ distinctUntilChanged,
+ delay,
+ filter,
+ startWith,
+ share,
+} from 'rxjs/operators';
-import { TableActionsComponent } from './components/table-actions.component';
+import { downloadFile, createCsv, arrayAttribute, ArrayAttributeTransform } from '../../utils';
+import { ContentLoadingComponent } from '../content-loading';
+import { ProgressModule } from '../progress';
+import { ValueComponent, ValueListComponent } from '../value';
+
+import { InfinityScrollDirective } from './components/infinity-scroll.directive';
+import { NoRecordsComponent } from './components/no-records.component';
+import { SelectColumnComponent } from './components/select-column.component';
+import { TableInfoBarComponent } from './components/table-info-bar/table-info-bar.component';
import { TableInputsComponent } from './components/table-inputs.component';
+import { TableProgressBarComponent } from './components/table-progress-bar.component';
import {
- DEFAULT_DEBOUNCE_TIME_MS,
+ DEBOUNCE_TIME_MS,
+ DEFAULT_LOADED_LAZY_ROWS_COUNT,
DEFAULT_SORT,
- COMPLETE_MISMATCH_SCORE,
- sortDataByDefault,
+ COLUMN_DEFS,
} from './consts';
-import { Column, ColumnObject, UpdateOptions, DragDrop } from './types';
-import { createColumnsObjects } from './utils/create-columns-objects';
-import { createUniqueColumnDef } from './utils/create-unique-column-def';
-import { OnePageTableDataSourcePaginator } from './utils/one-page-table-data-source-paginator';
+import { TreeData } from './tree-data';
+import { Column, UpdateOptions, NormColumn, DragDrop } from './types';
+import { columnsDataToFilterSearchData, filterData, sortData } from './utils/filter-sort';
+import { TableDataSource } from './utils/table-data-source';
+import { tableToCsvObject } from './utils/table-to-csv-object';
+import {
+ toObservableColumnsData,
+ toColumnsData,
+ DisplayedDataItem,
+ DisplayedData,
+} from './utils/to-columns-data';
@Component({
+ standalone: true,
selector: 'v-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ CommonModule,
+ MatTableModule,
+ MatCardModule,
+ ValueComponent,
+ TableProgressBarComponent,
+ NoRecordsComponent,
+ TableInfoBarComponent,
+ ContentLoadingComponent,
+ MatIcon,
+ MatTooltip,
+ MatIconButton,
+ InfinityScrollDirective,
+ ValueListComponent,
+ SelectColumnComponent,
+ ProgressModule,
+ MatSortModule,
+ TableInputsComponent,
+ MatButton,
+ CdkDrag,
+ CdkDropList,
+ ],
})
-export class TableComponent
- implements OnInit, Progressable, OnChanges, AfterViewInit, OnDestroy
-{
- data = input([]);
-
- @Input() columns!: Column[];
- @Input() cellTemplate: Record['field'], ColumnObject['cellTemplate']> = {};
- @Input() progress?: boolean | number | null = false;
- @Input() preloadedLazyRowsCount = 3;
-
- @Input({ transform: numberAttribute }) size: number = 25;
- @Input() preloadSize: number = 1000;
- @Input() name?: string;
-
- @Input({ transform: booleanAttribute }) hasMore: boolean = false;
- @Output() update = new EventEmitter();
- @Output() more = new EventEmitter();
-
- // Actions
- @Input({ transform: booleanAttribute }) noActions: boolean = false;
- @ContentChild(TableActionsComponent) actions!: TableActionsComponent;
- @ContentChild(TableInputsComponent) inputs!: TableInputsComponent;
-
- // Sort
- @Input() sort: Sort = DEFAULT_SORT;
- @Output() sortChange = new EventEmitter();
- @Input({ transform: booleanAttribute }) sortOnFront: boolean = false;
- @ViewChild(MatSort) sortComponent!: MatSort;
-
- // Select
- @Input({ transform: booleanAttribute }) rowSelectable: boolean = false;
- @Input() rowSelected!: T[];
- @Output() rowSelectedChange = new EventEmitter();
- selectColumnDef = createUniqueColumnDef('select');
- selected: T[] = [];
+export class TableComponent implements OnInit {
+ data = input();
+ treeData = input>();
+ columns = input[], ArrayAttributeTransform>>([], {
+ transform: arrayAttribute,
+ });
+ progress = input(false, { transform: Boolean });
+ hasMore = input(false, { transform: booleanAttribute });
+ size = input(25, { transform: numberAttribute });
+ maxSize = input(1000, { transform: numberAttribute });
+ noDownload = input(false, { transform: booleanAttribute });
// Filter
- @Input({ transform: booleanAttribute }) noFilter: boolean = false;
- @Input({ transform: booleanAttribute }) standaloneFilter: boolean = false;
+ filter = model('');
+ filter$ = toObservable(this.filter).pipe(
+ map((v) => (v || '').trim()),
+ distinctUntilChanged(),
+ debounceTime(DEBOUNCE_TIME_MS),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
+ standaloneFilter = input(false, { transform: booleanAttribute });
externalFilter = input(false, { transform: booleanAttribute });
- @Input() filter = '';
- // TODO: filter by rendered column fields, it will be useful if you save the render in memory
- @Input() filterByColumns?: string[];
- @Output() filterChange = new EventEmitter();
- filterControl = new FormControl('');
- exactFilterControl = new FormControl(1);
- scores = new Map();
- filteredDataLength?: number;
- filterProgress$ = new BehaviorSubject(false);
+ filteredSortData$ = new BehaviorSubject | null>(null);
+ displayedData$ = combineLatest([
+ defer(() => this.dataSource.data$),
+ this.filteredSortData$.pipe(distinctUntilChanged()),
+ defer(() => this.columnsDataProgress$),
+ ]).pipe(
+ map(
+ ([data, filteredSortData, columnsDataProgress]) =>
+ (filteredSortData && !columnsDataProgress ? filteredSortData : data) || [],
+ ),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
+ displayedCount$ = this.displayedData$.pipe(
+ map((data) => data.length),
+ distinctUntilChanged(),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
- @Input({
+ // Select
+ rowSelectable = input(false, { transform: booleanAttribute });
+ rowSelected = model>([]);
+
+ // Sort
+ sort = model(DEFAULT_SORT);
+ @ViewChild(MatSort) sortComponent!: MatSort;
+
+ // Drag & drop
+ rowDragDrop = input(false, {
transform: (v: boolean | string[]) => {
if (Array.isArray(v)) {
return v;
}
return booleanAttribute(v);
},
- })
- rowDragDrop: boolean | string[] = false;
- @Output() rowDropped = new EventEmitter>();
+ });
+ rowDropped = output>>();
dragDisabled = true;
- columnsObjects = new Map['field'], ColumnObject>([]);
+ update$ = new Subject();
+ update = outputFromObservable(this.update$);
+ more = output();
- isPreload = false;
+ loadedLazyItems = new WeakMap, boolean>();
- dataSource = new MatTableDataSource();
+ dataSource = new TableDataSource();
+ normColumns = computed[]>(() => this.columns().map((c) => new NormColumn(c)));
+ displayedNormColumns$ = toObservable(this.normColumns).pipe(
+ switchMap((cols) =>
+ combineLatest(cols.map((c) => c.hidden)).pipe(
+ map((c) => cols.filter((_, idx) => !c[idx])),
+ ),
+ ),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
+ columnsData$$ = combineLatest({
+ isTree: this.dataSource.isTreeData$,
+ data: this.dataSource.data$,
+ cols: toObservable(this.normColumns),
+ }).pipe(toObservableColumnsData, shareReplay({ refCount: true, bufferSize: 1 }));
+ columnsDataProgress$ = new BehaviorSubject(false);
+ columnsData$ = this.columnsData$$.pipe(
+ tap(() => {
+ this.columnsDataProgress$.next(true);
+ }),
+ toColumnsData,
+ tap(() => {
+ this.columnsDataProgress$.next(false);
+ }),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
+ isPreload = signal(false);
+ loadSize = computed(() => (this.isPreload() ? this.maxSize() : this.size()));
+ hasAutoShowMore$ = combineLatest([
+ toObservable(this.hasMore),
+ this.dataSource.data$.pipe(map((d) => d?.length)),
+ this.displayedCount$,
+ this.dataSource.paginator.page.pipe(
+ startWith(null),
+ map(() => this.dataSource.paginator.pageSize),
+ ),
+ ]).pipe(
+ map(
+ ([hasMore, dataCount, displayedDataCount, size]) =>
+ (hasMore && displayedDataCount !== 0 && displayedDataCount >= dataCount) ||
+ displayedDataCount > size,
+ ),
+ distinctUntilChanged(),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
- displayedColumns: string[] = [];
+ displayedColumns$ = combineLatest([
+ this.displayedNormColumns$,
+ toObservable(this.rowSelectable),
+ toObservable(this.sort),
+ toObservable(this.rowDragDrop),
+ ]).pipe(
+ map(([normColumns, rowSelectable, sort, rowDragDrop]) => [
+ ...((
+ Array.isArray(rowDragDrop)
+ ? sort?.direction && rowDragDrop.includes(sort?.active)
+ : rowDragDrop
+ )
+ ? [this.columnDefs.drag]
+ : []),
+ ...(rowSelectable ? [this.columnDefs.select] : []),
+ ...normColumns.map((c) => c.field),
+ ]),
+ shareReplay({ refCount: true, bufferSize: 1 }),
+ );
+ columnDefs = COLUMN_DEFS;
- scoreColumnDef = createUniqueColumnDef('score');
- noRecordsColumnDef = createUniqueColumnDef('no-records');
- dragColumnDef = createUniqueColumnDef('drag');
-
- preloadedLazyCells = new Map();
-
- @ViewChild('table', { static: true }) table!: MatTable;
-
- get displayedPages() {
- return this.paginator.displayedPages;
- }
-
- get currentSize() {
- return this.isPreload ? this.preloadSize : this.size;
- }
-
- get hasShowMore() {
- return (
- this.hasMore ||
- (this.filteredDataLength ?? this.data()?.length) > this.size * this.displayedPages
- );
- }
-
- get isNoRecords() {
- return !this.data()?.length || this.filteredDataLength === 0;
- }
-
- private paginator!: OnePageTableDataSourcePaginator;
- private qp?: QueryParamsNamespace<{ filter: string; exact?: boolean }>;
+ @ViewChild('scrollViewport', { read: ElementRef }) scrollViewport!: ElementRef;
+ @ViewChild('matTable', { static: false }) table!: MatTable;
+ @ContentChild(TableInputsComponent, { read: ElementRef }) tableInputsContent!: ElementRef;
+ @ViewChild(MatRow, { static: false }) tableRow!: ElementRef;
constructor(
- private destroyRef: DestroyRef,
- private queryParamsService: QueryParamsService,
+ private dr: DestroyRef,
private injector: Injector,
- ) {
- this.updatePaginator();
- }
+ private cdr: ChangeDetectorRef,
+ ) {}
ngOnInit() {
- const startValue = this.filterControl.value;
- const filter$ = this.filterControl.valueChanges.pipe(
- ...((startValue ? [startWith(startValue)] : []) as []),
- map((value) => (value || '').trim()),
+ const sort$ = toObservable(this.sort, { injector: this.injector }).pipe(
distinctUntilChanged(),
- debounceTime(DEFAULT_DEBOUNCE_TIME_MS),
+ share(),
);
- filter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((filter) => {
- this.filterChange.emit(filter);
- this.qp?.patch?.({ filter });
- });
- const exactFilter$ = this.exactFilterControl.valueChanges.pipe(
- startWith(this.exactFilterControl.value),
- map(Boolean),
- distinctUntilChanged(),
- );
- exactFilter$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((exact) => {
- this.qp?.patch?.({ exact });
- });
+
+ toObservable(this.data, { injector: this.injector })
+ .pipe(filter(Boolean), takeUntilDestroyed(this.dr))
+ .subscribe((data) => {
+ this.dataSource.setData(data);
+ });
+ toObservable(this.treeData, { injector: this.injector })
+ .pipe(filter(Boolean), takeUntilDestroyed(this.dr))
+ .subscribe((data) => {
+ this.dataSource.setTreeData(data);
+ });
combineLatest([
- filter$,
- exactFilter$,
- toObservable(this.data, { injector: this.injector }),
+ this.filter$,
+ sort$,
+ this.dataSource.data$,
+ runInInjectionContext(this.injector, () =>
+ this.columnsData$.pipe(columnsDataToFilterSearchData),
+ ),
+ this.dataSource.isTreeData$,
+ toObservable(this.normColumns, { injector: this.injector }),
toObservable(this.externalFilter, { injector: this.injector }),
])
.pipe(
- tap(() => {
- this.filterProgress$.next(true);
- delete this.filteredDataLength;
- }),
- debounceTime(DEFAULT_DEBOUNCE_TIME_MS),
- switchMap(([filter, exact]): Observable<[Map, string]> => {
- if (!filter || this.externalFilter() || !this.data()?.length) {
- return of([new Map(), filter]);
+ map(([search, sort, source, data, isTreeData, columns, isExternalFilter]) => {
+ if (isTreeData) {
+ return source;
}
- const cols = this.filterByColumns
- ? this.filterByColumns.map(
- (c) => this.columnsObjects.get(c) as ColumnObject,
- )
- : Array.from(this.columnsObjects.values());
- // TODO: Refactor
- return (
- cols.length
- ? forkJoin([
- forkJoin(
- this.data().map((sourceValue, index) =>
- combineLatest(
- cols.map((colDef) =>
- colDef.lazy
- ? of('')
- : getPossiblyAsyncObservable(
- select(
- sourceValue,
- colDef.formatter ?? colDef.field,
- '',
- [index, colDef] as never,
- ),
- ).pipe(catchError(() => of(''))),
- ),
- ).pipe(take(1)),
- ),
- ),
- forkJoin(
- this.data().map((sourceValue, index) =>
- combineLatest(
- cols.map((colDef) =>
- colDef.description && !colDef.lazy
- ? getPossiblyAsyncObservable(
- select(
- sourceValue,
- colDef.description,
- '',
- [index, colDef] as never,
- ),
- ).pipe(catchError(() => of('')))
- : of(''),
- ),
- ).pipe(take(1)),
- ),
- ),
- ])
- : of([[] as unknown[], [] as unknown[]])
- ).pipe(
- map(([formattedValues, formattedDescription]) => {
- const fuseData = this.data().map((item, idx) => ({
- // TODO: add weights
- value: JSON.stringify(item),
- formattedValue: JSON.stringify(formattedValues[idx]), // TODO: split columns
- formattedDescription: JSON.stringify(formattedDescription[idx]),
- }));
- const fuse = new Fuse(fuseData, {
- keys: Object.keys(fuseData[0]),
- includeScore: true,
- includeMatches: true,
- findAllMatches: true,
- ignoreLocation: true,
- threshold: exact ? 0 : 0.6,
- });
- const filterResult = fuse.search(filter);
- return [
- new Map(
- filterResult.map(({ refIndex, score }) => [
- this.data()[refIndex],
- { score: score ?? COMPLETE_MISMATCH_SCORE },
- ]),
- ),
- filter,
- ];
- }),
- );
+ const filteredData =
+ !isExternalFilter && search ? filterData(data, search) : source;
+ return sortData(filteredData, data, columns, sort);
}),
- takeUntilDestroyed(this.destroyRef),
+ takeUntilDestroyed(this.dr),
)
- .subscribe(([scores, filter]) => {
- this.scores = scores;
- this.sortChanged(
- filter && !this.externalFilter()
- ? { active: this.scoreColumnDef, direction: 'asc' }
- : this.sortComponent.active === this.scoreColumnDef
- ? DEFAULT_SORT
- : this.sort,
- );
- this.filterProgress$.next(false);
+ .subscribe((filtered) => {
+ this.updateSortFilter(filtered);
+ this.updateLoadedLazyItems(filtered);
+ this.cdr.markForCheck();
+ });
+ merge(
+ this.filter$.pipe(filter(Boolean)),
+ toObservable(this.hasMore, { injector: this.injector }).pipe(filter(Boolean)),
+ )
+ .pipe(takeUntilDestroyed(this.dr))
+ .subscribe(() => {
+ this.sort.set(DEFAULT_SORT);
+ });
+ merge(this.filter$, sort$)
+ .pipe(takeUntilDestroyed(this.dr))
+ .subscribe(() => {
+ this.reset();
+ });
+ // TODO: 2, 3 column is torn away from the previous one, fixed by calling update
+ this.dataSource.data$
+ .pipe(
+ filter((v) => !!v?.length),
+ first(),
+ delay(100),
+ takeUntilDestroyed(this.dr),
+ )
+ .subscribe(() => {
+ this.table.updateStickyColumnStyles();
});
}
- ngAfterViewInit() {
- this.dataSource.sort = this.sortComponent;
- this.updateSort();
- }
-
- ngOnChanges(changes: ComponentChanges>) {
- if (changes.columns) {
- this.updateColumns();
+ load() {
+ if (this.isPreload()) {
+ this.isPreload.set(false);
}
- if (changes.columns || changes.rowSelectable || changes.rowDragDrop) {
- this.updateDisplayedColumns();
- }
- if (changes.data) {
- this.dataSource.data = this.data();
- this.preloadedLazyCells = new Map();
- }
- if (this.dataSource.sort && changes.sort) {
- this.updateSort();
- }
- if (changes.size) {
- this.updatePaginator();
- }
- if (changes.filter) {
- this.filterControl.setValue(this.filter ?? '');
- }
- if (changes.name && this.name) {
- if (this.qp) {
- this.qp.destroy();
- }
- this.qp = this.queryParamsService.createNamespace(this.name);
- const filter = this.qp.params?.filter ?? '';
- if (filter) {
- this.filterControl.patchValue(filter);
- }
- const exact = this.qp.params?.exact ?? this.exactFilterControl.value;
- if (exact !== this.exactFilterControl.value) {
- this.exactFilterControl.setValue(1);
- }
- }
- if (changes.sortOnFront || changes.data) {
- this.tryFrontSort();
- }
- }
-
- ngOnDestroy() {
- this.qp?.destroy?.();
- }
-
- updateColumns(columns: ColumnObject[] = createColumnsObjects(this.columns)) {
- this.columnsObjects = new Map((columns || []).map((c) => [c.field, c]));
- }
-
- load(isPreload = false) {
- if (this.isPreload !== isPreload) {
- this.isPreload = isPreload;
- }
- this.update.emit({ size: this.currentSize });
- this.paginator.reload();
+ this.reload();
}
preload() {
- if (this.isPreload && this.hasMore) {
- this.more.emit({ size: this.currentSize });
- return;
+ if (!this.isPreload()) {
+ this.isPreload.set(true);
+ this.reload();
+ } else if (this.hasMore()) {
+ this.more.emit({ size: this.loadSize() });
}
- this.load(true);
}
showMore() {
- this.paginator.more();
- if (this.hasMore && this.displayedPages * this.size > this.data()?.length) {
- this.more.emit({ size: this.currentSize });
+ this.dataSource.paginator.more();
+ if (this.hasMore() && this.dataSource.paginator.pageSize > this.dataSource.data.length) {
+ this.more.emit({ size: this.loadSize() });
}
+ this.refreshTable();
}
- sortChanged(sort: Sort) {
- this.sortChange.emit(sort);
- this.tryFrontSort(sort);
- this.updateDisplayedColumns();
+ downloadCsv() {
+ this.generateCsvData()
+ .pipe(takeUntilDestroyed(this.dr))
+ .subscribe((csvData) => {
+ downloadFile(csvData, 'csv');
+ });
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
drop(event: CdkDragDrop) {
this.dragDisabled = true;
- const item = event.item.data;
- const { previousIndex, currentIndex } = event;
- const previousData = this.dataSource.sortData(this.data(), this.sortComponent);
- const currentData = previousData.slice();
- let currentDataIndex = 0;
- if (previousIndex > currentIndex) {
- currentData.splice(previousIndex, 1);
- currentData.splice(currentIndex, 0, event.item.data);
- currentDataIndex = currentIndex;
- } else {
- currentData.splice(currentIndex, 0, event.item.data);
- currentData.splice(previousIndex, 1);
- currentDataIndex = currentIndex - 1;
- }
- this.rowDropped.emit({
- previousIndex,
- currentIndex,
- item,
- previousData,
- currentData,
- currentDataIndex,
- sort: this?.sortComponent,
+ this.filteredSortData$.pipe(filter(Boolean), first()).subscribe((data) => {
+ const item = event.item.data;
+ const { previousIndex, currentIndex } = event;
+ const previousData = data;
+ const currentData = previousData.slice();
+ let currentDataIndex = 0;
+ if (previousIndex > currentIndex) {
+ currentData.splice(previousIndex, 1);
+ currentData.splice(currentIndex, 0, event.item.data);
+ currentDataIndex = currentIndex;
+ } else {
+ currentData.splice(currentIndex, 0, event.item.data);
+ currentData.splice(previousIndex, 1);
+ currentDataIndex = currentIndex - 1;
+ }
+ this.rowDropped.emit({
+ previousIndex,
+ currentIndex,
+ item,
+ previousData,
+ currentData,
+ currentDataIndex,
+ sort: this?.sortComponent,
+ });
});
}
- private tryFrontSort({ active, direction }: Partial = this.sortComponent || {}) {
- const data = this.data();
- if (!data?.length || !active || !direction) {
- this.updateDataSourceSort();
- return;
- }
- if (active === this.scoreColumnDef && this.filterControl.value) {
- let sortedData = data
- .filter(
- (data) =>
- (this.scores.get(data)?.score ?? COMPLETE_MISMATCH_SCORE) <
- COMPLETE_MISMATCH_SCORE,
- )
- .sort(
- (a, b) =>
- (this.scores.get(a)?.score ?? COMPLETE_MISMATCH_SCORE) -
- (this.scores.get(b)?.score ?? COMPLETE_MISMATCH_SCORE),
- );
- if (direction === 'desc') {
- sortedData = sortedData.reverse();
- }
- this.filteredDataLength = sortedData.length;
- this.updateDataSourceSort(sortedData);
- return;
- }
- if (!this.sortOnFront) {
- this.updateDataSourceSort();
- return;
- }
- if (this.filterControl.value) {
- this.filterControl.setValue('');
- }
- const colDef = this.columnsObjects.get(active);
- if (!colDef) {
- this.updateDataSourceSort();
- return;
- }
- combineLatest(
- data.map((sourceValue, index) =>
- getPossiblyAsyncObservable(
- select(sourceValue, colDef.formatter ?? colDef.field, '', [
- index,
- colDef,
- ] as never),
- ).pipe(map((value) => ({ value, sourceValue }))),
+ private generateCsvData(): Observable {
+ return combineLatest([
+ this.displayedNormColumns$.pipe(
+ switchMap((cols) => forkJoin(cols.map((c) => c.header.pipe(take(1))))),
),
- )
- .pipe(take(1), takeUntilDestroyed(this.destroyRef))
- .subscribe((loadedData) => {
- let sortedData = loadedData
- .sort((a, b) => compareDifferentTypes(a.value, b.value))
- .map((v) => v.sourceValue);
- if (direction === 'desc') {
- sortedData = sortedData.reverse();
- }
- this.updateDataSourceSort(sortedData);
- });
+ this.columnsData$.pipe(take(1)),
+ ]).pipe(
+ map(([cols, data]) =>
+ createCsv(runInInjectionContext(this.injector, () => tableToCsvObject(cols, data))),
+ ),
+ );
}
- private updateDataSourceSort(sortedData?: T[]) {
- this.dataSource.sortData = sortedData ? () => sortedData : sortDataByDefault;
- // TODO: hack for update
+ private reload() {
+ this.update$.next({ size: this.loadSize() });
+ this.reset();
+ }
+
+ private updateSortFilter(filtered: DisplayedData) {
+ this.filteredSortData$.next(filtered);
+ this.dataSource.sortData = () => filtered;
this.dataSource.sort = this.sortComponent;
}
- private updatePaginator() {
- this.paginator = new OnePageTableDataSourcePaginator(this.size);
- this.dataSource.paginator = this.paginator as never;
+ private reset() {
+ (this.scrollViewport?.nativeElement as HTMLElement)?.scrollTo?.({ top: 0 });
+ this.dataSource.paginator.reload();
+ this.refreshTable();
}
- private updateSort() {
- this.sortComponent.active = this.sort.active;
- this.sortComponent.direction = this.sort.direction;
- this.tryFrontSort();
+ // TODO: Refresh table when pagination is updated
+ private refreshTable() {
+ // eslint-disable-next-line no-self-assign
+ this.dataSource.data = this.dataSource.data;
}
- private updateDisplayedColumns() {
- this.displayedColumns = [
- ...((
- Array.isArray(this.rowDragDrop)
- ? (this.sortComponent?.direction ?? this.sort?.direction) &&
- this.rowDragDrop.includes(this.sortComponent?.active ?? this.sort?.active)
- : this.rowDragDrop
- )
- ? [this.dragColumnDef]
- : []),
- this.scoreColumnDef,
- ...(this.rowSelectable ? [this.selectColumnDef] : []),
- ...Array.from(this.columnsObjects.values())
- .filter((c) => !c.hide)
- .map((c) => c.field),
- ];
+ private updateLoadedLazyItems(items: DisplayedData) {
+ const lazyLoadedItems = items.slice(0, DEFAULT_LOADED_LAZY_ROWS_COUNT);
+ for (const item of lazyLoadedItems) {
+ this.loadedLazyItems.set(item, true);
+ }
}
}
diff --git a/projects/ng-core/src/lib/components/table/table.module.ts b/projects/ng-core/src/lib/components/table/table.module.ts
index 5e01777..62e5410 100644
--- a/projects/ng-core/src/lib/components/table/table.module.ts
+++ b/projects/ng-core/src/lib/components/table/table.module.ts
@@ -1,71 +1,11 @@
-import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop';
-import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
-import { ReactiveFormsModule } from '@angular/forms';
-import { MatButtonModule } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
-import { MatCheckboxModule } from '@angular/material/checkbox';
-import { MatChipsModule } from '@angular/material/chips';
-import { MatIconModule } from '@angular/material/icon';
-import { MatInputModule } from '@angular/material/input';
-import { MatMenuModule } from '@angular/material/menu';
-import { MatProgressBarModule } from '@angular/material/progress-bar';
-import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
-import { MatSelectModule } from '@angular/material/select';
-import { MatSortModule } from '@angular/material/sort';
-import { MatTableModule } from '@angular/material/table';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { RouterLink } from '@angular/router';
-import { MtxTooltip } from '@ng-matero/extensions/tooltip';
-import { PipesModule } from '../../pipes';
-import { ActionsModule } from '../actions';
-import { InputFieldModule } from '../input-field';
-import { SwitchButtonModule } from '../switch-button';
-import { TagModule } from '../tag';
-
-import { ScoreColumnComponent } from './components/score-column.component';
-import { SelectColumnComponent } from './components/select-column.component';
-import { ShowMoreButtonComponent } from './components/show-more-button/show-more-button.component';
import { TableActionsComponent } from './components/table-actions.component';
-import { TableCellComponent } from './components/table-cell/table-cell.component';
import { TableInputsComponent } from './components/table-inputs.component';
-import { Table2Component } from './components/table2';
import { TableComponent } from './table.component';
@NgModule({
- imports: [
- CommonModule,
- MatTableModule,
- MatCardModule,
- MatProgressSpinnerModule,
- MatInputModule,
- MatSelectModule,
- MatIconModule,
- MatMenuModule,
- MatButtonModule,
- ActionsModule,
- MatTooltipModule,
- MatChipsModule,
- PipesModule,
- TagModule,
- RouterLink,
- MatProgressBarModule,
- MatCheckboxModule,
- MatSortModule,
- InputFieldModule,
- ReactiveFormsModule,
- SwitchButtonModule,
- CdkDrag,
- CdkDropList,
- SelectColumnComponent,
- ScoreColumnComponent,
- MtxTooltip,
- Table2Component,
- ShowMoreButtonComponent,
- TableInputsComponent,
- ],
- declarations: [TableComponent, TableActionsComponent, TableCellComponent],
- exports: [TableComponent, TableActionsComponent, TableInputsComponent, Table2Component],
+ imports: [TableActionsComponent, TableComponent, TableInputsComponent],
+ exports: [TableActionsComponent, TableInputsComponent, TableComponent],
})
export class TableModule {}
diff --git a/projects/ng-core/src/lib/components/table/components/table2/tree-data/index.ts b/projects/ng-core/src/lib/components/table/tree-data/index.ts
similarity index 100%
rename from projects/ng-core/src/lib/components/table/components/table2/tree-data/index.ts
rename to projects/ng-core/src/lib/components/table/tree-data/index.ts
diff --git a/projects/ng-core/src/lib/components/table/components/table2/tree-data/tree-data-item-to-inline-data-item.ts b/projects/ng-core/src/lib/components/table/tree-data/tree-data-item-to-inline-data-item.ts
similarity index 100%
rename from projects/ng-core/src/lib/components/table/components/table2/tree-data/tree-data-item-to-inline-data-item.ts
rename to projects/ng-core/src/lib/components/table/tree-data/tree-data-item-to-inline-data-item.ts
diff --git a/projects/ng-core/src/lib/components/table/components/table2/tree-data/tree-data.ts b/projects/ng-core/src/lib/components/table/tree-data/tree-data.ts
similarity index 100%
rename from projects/ng-core/src/lib/components/table/components/table2/tree-data/tree-data.ts
rename to projects/ng-core/src/lib/components/table/tree-data/tree-data.ts
diff --git a/projects/ng-core/src/lib/components/table/types/base-column.ts b/projects/ng-core/src/lib/components/table/types/base-column.ts
deleted file mode 100644
index a541502..0000000
--- a/projects/ng-core/src/lib/components/table/types/base-column.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { TemplateRef } from '@angular/core';
-import { Observable } from 'rxjs';
-
-import { SelectFn } from '../../../utils';
-
-import { ColumnObject } from './column';
-
-export type ColumnPinValue = 'left' | 'right' | null;
-
-export type FormatterFn = SelectFn<
- TObject,
- TResult,
- [index: number, colDef: ColumnObject]
->;
-export type ColumnFn = (
- rowData: TObject,
- index: number,
-) => TResult;
-
-export interface BaseColumn {
- field: string;
- header?: string | Observable;
- hide?: boolean;
- pinned?: ColumnPinValue;
- width?: string;
- minWidth?: string;
- maxWidth?: string;
- sortable?: boolean | string;
- cellTemplate?: TemplateRef;
- formatter?: FormatterFn;
-
- description?: FormatterFn;
-
- tooltip?: FormatterFn;
-
- link?: ColumnFn;
- linkParameters?: { target?: '_blank' };
-
- click?: ColumnFn;
-
- lazy?: boolean;
-
- // TODO: Need to delete
- type?: void;
-}
diff --git a/projects/ng-core/src/lib/components/table/types/column.ts b/projects/ng-core/src/lib/components/table/types/column.ts
deleted file mode 100644
index a9fc801..0000000
--- a/projects/ng-core/src/lib/components/table/types/column.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { BaseColumn } from './base-column';
-import { TypedColumns } from './typed-column';
-
-export type ColumnObject = BaseColumn | TypedColumns;
-export type Column = ColumnObject | string;
diff --git a/projects/ng-core/src/lib/components/table/types/column2.ts b/projects/ng-core/src/lib/components/table/types/column2.ts
index f378607..438cf19 100644
--- a/projects/ng-core/src/lib/components/table/types/column2.ts
+++ b/projects/ng-core/src/lib/components/table/types/column2.ts
@@ -19,7 +19,7 @@ interface ColumnParams {
export type CellValue = 'string' | Value;
-export interface Column2 extends ColumnParams {
+export interface Column extends ColumnParams {
field: string;
header?: PossiblyAsync | string>;
@@ -38,7 +38,7 @@ export function normalizePossiblyFn>(fn: PossiblyFn<
export function normalizeCell(
field: string,
- cell: Column2['cell'],
+ cell: Column['cell'],
hasChild: boolean = false,
): Fn, CellFnArgs> {
const cellFn = normalizePossiblyFn(cell);
@@ -65,7 +65,7 @@ export class NormColumn {
params!: ColumnParams;
constructor(
- { field, header, cell, child, hidden, sort, lazyCell, ...params }: Column2,
+ { field, header, cell, child, hidden, sort, lazyCell, ...params }: Column,
commonParams: ColumnParams = {},
) {
this.field = field ?? (typeof header === 'string' ? header : Math.random());
diff --git a/projects/ng-core/src/lib/components/table/types/index.ts b/projects/ng-core/src/lib/components/table/types/index.ts
index 3b90966..bf6f3db 100644
--- a/projects/ng-core/src/lib/components/table/types/index.ts
+++ b/projects/ng-core/src/lib/components/table/types/index.ts
@@ -1,6 +1,3 @@
-export * from './base-column';
-export * from './typed-column';
-export * from './column';
export * from './column2';
export * from './update-options';
export * from './drag-drop';
diff --git a/projects/ng-core/src/lib/components/table/types/typed-column.ts b/projects/ng-core/src/lib/components/table/types/typed-column.ts
deleted file mode 100644
index c130d61..0000000
--- a/projects/ng-core/src/lib/components/table/types/typed-column.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { OmitByValueExact } from 'utility-types';
-
-import { Color } from '../../../styles';
-
-import { BaseColumn, ColumnFn, FormatterFn } from './base-column';
-
-export type TypedColumn<
- T extends object,
- TType extends string,
- TTypeParameters extends object = never,
-> = Omit, 'type'> &
- OmitByValueExact<{ type: TType; typeParameters: TTypeParameters }, never>;
-
-export type MenuColumn = TypedColumn<
- T,
- 'menu',
- {
- items: {
- label: string | ColumnFn;
- click: ColumnFn;
- disabled?: ColumnFn;
- }[];
- }
->;
-export type TagColumn = TypedColumn<
- T,
- 'tag',
- {
- label?: FormatterFn;
- tags: Record;
- }
->;
-export type CurrencyColumn = TypedColumn<
- T,
- 'currency',
- { currencyCode: FormatterFn; isMinor?: boolean; exponent?: FormatterFn }
->;
-
-export type TypedColumns =
- | TypedColumn
- | CurrencyColumn
- | TagColumn
- | MenuColumn
- | TypedColumn;
diff --git a/projects/ng-core/src/lib/components/table/utils/create-column.ts b/projects/ng-core/src/lib/components/table/utils/create-column.ts
index 125a5d8..05c1a9b 100644
--- a/projects/ng-core/src/lib/components/table/utils/create-column.ts
+++ b/projects/ng-core/src/lib/components/table/utils/create-column.ts
@@ -4,18 +4,18 @@ import { map } from 'rxjs/operators';
import { PossiblyAsync, getPossiblyAsyncObservable } from '../../../utils';
import { Value } from '../../value';
-import { Column2, CellFnArgs, normalizeCell } from '../types';
+import { Column, CellFnArgs, normalizeCell } from '../types';
import { createUniqueColumnDef } from './create-unique-column-def';
export function createColumn(
createCell: (cellParams: P, ...args: CellFnArgs) => PossiblyAsync,
- columnObject: Partial> = {},
+ columnObject: Partial> = {},
) {
return (
getCellParams: (...args: CellFnArgs) => PossiblyAsync,
- { isLazyCell, ...column }: Partial> & { isLazyCell?: boolean } = {},
- ): Column2 => {
+ { isLazyCell, ...column }: Partial> & { isLazyCell?: boolean } = {},
+ ): Column => {
const injector = inject(Injector);
const field = column?.field ?? createUniqueColumnDef(column?.header);
const cellKey = isLazyCell ? 'lazyCell' : 'cell';
diff --git a/projects/ng-core/src/lib/components/table/utils/create-columns-objects.ts b/projects/ng-core/src/lib/components/table/utils/create-columns-objects.ts
deleted file mode 100644
index 97c7ff4..0000000
--- a/projects/ng-core/src/lib/components/table/utils/create-columns-objects.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import isNil from 'lodash-es/isNil';
-import isObject from 'lodash-es/isObject';
-import startCase from 'lodash-es/startCase';
-
-import { Column, ColumnObject } from '../types';
-
-export function createColumnObject(col: Column): ColumnObject {
- const extCol: ColumnObject = isObject(col) ? col : { field: col };
- if (isNil(extCol.header)) {
- extCol.header = startCase(String(extCol.field.split('.').at(-1)));
- }
- return extCol;
-}
-
-export function createColumnsObjects(columns: Column[]): ColumnObject[] {
- return columns?.map((col) => createColumnObject(col)) || [];
-}
diff --git a/projects/ng-core/src/lib/components/table/components/table2/utils/filter-sort.ts b/projects/ng-core/src/lib/components/table/utils/filter-sort.ts
similarity index 90%
rename from projects/ng-core/src/lib/components/table/components/table2/utils/filter-sort.ts
rename to projects/ng-core/src/lib/components/table/utils/filter-sort.ts
index 23424d7..7d1df8c 100644
--- a/projects/ng-core/src/lib/components/table/components/table2/utils/filter-sort.ts
+++ b/projects/ng-core/src/lib/components/table/utils/filter-sort.ts
@@ -3,12 +3,12 @@ import { Sort } from '@angular/material/sort';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
-import { compareDifferentTypes } from '../../../../../utils';
-import { valueToString } from '../../../../value/utils/value-to-string';
-import { NormColumn } from '../../../types';
-import { normalizeString } from '../../../utils/normalize-string';
+import { compareDifferentTypes } from '../../../utils';
+import { valueToString } from '../../value/utils/value-to-string';
+import { NormColumn } from '../types';
+import { DisplayedData, DisplayedDataItem, ColumnData } from '../utils/to-columns-data';
-import { DisplayedData, DisplayedDataItem, ColumnData } from './to-columns-data';
+import { normalizeString } from './normalize-string';
export type FilterSearchData = Map<
DisplayedDataItem,
diff --git a/projects/ng-core/src/lib/components/table/utils/one-page-table-data-source-paginator.ts b/projects/ng-core/src/lib/components/table/utils/one-page-table-data-source-paginator.ts
deleted file mode 100644
index ce81ec8..0000000
--- a/projects/ng-core/src/lib/components/table/utils/one-page-table-data-source-paginator.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { EventEmitter } from '@angular/core';
-import { PageEvent, MatPaginator } from '@angular/material/paginator';
-import { BehaviorSubject } from 'rxjs';
-
-export class OnePageTableDataSourcePaginator implements Partial {
- pageIndex = 0;
- pageSize = 0;
- page = new EventEmitter();
- initialized = new BehaviorSubject(undefined);
-
- get length() {
- return this.pageSize;
- }
- set length(v: number) {}
-
- get displayedPages() {
- return this.pageSize / this.partSize;
- }
-
- private partSize!: number;
-
- constructor(partSize?: number) {
- this.setSize(partSize);
- }
-
- setSize(partSize = 25) {
- if (partSize !== this.partSize) {
- this.pageSize = this.partSize = partSize;
- this.reload();
- }
- }
-
- reload() {
- this.pageSize = this.partSize;
- this.update();
- }
-
- more() {
- this.pageSize += this.partSize;
- this.update();
- }
-
- private update() {
- this.page.next({
- pageIndex: this.pageIndex,
- pageSize: this.pageSize,
- length: this.length,
- });
- }
-}
diff --git a/projects/ng-core/src/lib/components/table/utils/table-data-source.ts b/projects/ng-core/src/lib/components/table/utils/table-data-source.ts
index 48a1d40..cb9e558 100644
--- a/projects/ng-core/src/lib/components/table/utils/table-data-source.ts
+++ b/projects/ng-core/src/lib/components/table/utils/table-data-source.ts
@@ -10,7 +10,7 @@ import {
TreeData,
TreeInlineDataItem,
treeDataItemToInlineDataItem,
-} from '../components/table2/tree-data';
+} from '../tree-data';
import { cachedHeadMap } from './cached-head-map';
diff --git a/projects/ng-core/src/lib/components/table/utils/table-to-csv-object.ts b/projects/ng-core/src/lib/components/table/utils/table-to-csv-object.ts
index 0285574..006e09b 100644
--- a/projects/ng-core/src/lib/components/table/utils/table-to-csv-object.ts
+++ b/projects/ng-core/src/lib/components/table/utils/table-to-csv-object.ts
@@ -1,7 +1,7 @@
import { Value } from '../../value';
import { unknownToString } from '../../value/utils/unknown-to-string';
import { valueToString } from '../../value/utils/value-to-string';
-import { DisplayedDataItem, ColumnData } from '../components/table2/utils/to-columns-data';
+import { DisplayedDataItem, ColumnData } from '../utils/to-columns-data';
export function tableToCsvObject(
cols: Value[],
diff --git a/projects/ng-core/src/lib/components/table/components/table2/utils/to-columns-data.ts b/projects/ng-core/src/lib/components/table/utils/to-columns-data.ts
similarity index 92%
rename from projects/ng-core/src/lib/components/table/components/table2/utils/to-columns-data.ts
rename to projects/ng-core/src/lib/components/table/utils/to-columns-data.ts
index 2fd8fa2..4673ec4 100644
--- a/projects/ng-core/src/lib/components/table/components/table2/utils/to-columns-data.ts
+++ b/projects/ng-core/src/lib/components/table/utils/to-columns-data.ts
@@ -1,10 +1,11 @@
+import { isEqual } from 'lodash-es';
import { Observable, scan, of, switchMap, combineLatest, timer } from 'rxjs';
-import { shareReplay, map } from 'rxjs/operators';
+import { shareReplay, map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { Overwrite } from 'utility-types';
-import { Value } from '../../../../value';
-import { CellFnArgs, Fn, NormColumn } from '../../../types';
+import { Value } from '../../value';
import { TreeInlineDataItem, TreeInlineData } from '../tree-data';
+import { CellFnArgs, Fn, NormColumn } from '../types';
export type DisplayedDataItem = TreeInlineDataItem | T;
export type DisplayedData = TreeInlineData | T[];
@@ -119,11 +120,15 @@ export function toColumnsData(
Array.from(columnsData.values()).map((v) =>
combineLatest(
Array.from(v.values()).map((cell) =>
- timer(0).pipe(switchMap(() => cell.value)),
+ timer(0).pipe(
+ switchMap(() => cell.value),
+ distinctUntilChanged(isEqual),
+ ),
),
- ),
+ ).pipe(debounceTime(0)),
),
).pipe(
+ debounceTime(0),
map(
(res) =>
new Map(