@servicemind.tis/tis-smart-table-viewer 2.3.5 → 2.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,14 +89,12 @@ class ApiDataSource {
89
89
  this.loadingSubject.next(true);
90
90
  this.apiSubs = this.apiService.getList(url, (pageIndex + 1), pageSize, search, { filter }, { sortFilter }).pipe(catchError(() => of([])), finalize(() => this.loadingSubject.next(false))).subscribe(r => {
91
91
  console.log(`DataSource: Url: ${url}, Reply:`, r);
92
- if (r?.data?.length > 0) {
93
- this.totalDataLength.next(r?.total);
94
- }
95
- else {
96
- this.totalDataLength.next(0);
97
- }
98
- this.apiSubject.next(r?.data);
99
- this.extraDataSubject.next(r?.extraData);
92
+ // ✅ FIX: Ensure we always emit an array (even if empty) to prevent undefined issues
93
+ const data = r?.data || [];
94
+ const total = (Array.isArray(data) && data.length > 0) ? (r?.total || data.length) : 0;
95
+ this.totalDataLength.next(total);
96
+ this.apiSubject.next(data);
97
+ this.extraDataSubject.next(r?.extraData || null);
100
98
  });
101
99
  }
102
100
  loadWithCancellation(url, pageIndex, pageSize, search, filter, sortFilter, cancelSubject) {
@@ -111,14 +109,12 @@ class ApiDataSource {
111
109
  }
112
110
  this.apiSubs = apiCall$.subscribe(r => {
113
111
  console.log(`DataSource: Url: ${url}, Reply:`, r);
114
- if (r?.data?.length > 0) {
115
- this.totalDataLength.next(r?.total);
116
- }
117
- else {
118
- this.totalDataLength.next(0);
119
- }
120
- this.apiSubject.next(r?.data);
121
- this.extraDataSubject.next(r?.extraData);
112
+ // ✅ FIX: Ensure we always emit an array (even if empty) to prevent undefined issues
113
+ const data = r?.data || [];
114
+ const total = (Array.isArray(data) && data.length > 0) ? (r?.total || data.length) : 0;
115
+ this.totalDataLength.next(total);
116
+ this.apiSubject.next(data);
117
+ this.extraDataSubject.next(r?.extraData || null);
122
118
  });
123
119
  }
124
120
  }
@@ -2096,12 +2092,18 @@ class TisSmartTableViewerComponent {
2096
2092
  }
2097
2093
  if (changes['loadDataApiBaseUrl']) {
2098
2094
  if (changes['loadDataApiBaseUrl'].currentValue) {
2095
+ // Clean up existing subscriptions
2099
2096
  if (this.loadingSubscription) {
2100
2097
  this.loadingSubscription.unsubscribe();
2101
2098
  }
2102
2099
  if (this.dataLengthSubscription) {
2103
2100
  this.dataLengthSubscription.unsubscribe();
2104
2101
  }
2102
+ // ✅ FIX: Disconnect previous dataSource to prevent memory leaks
2103
+ if (this.dataSource) {
2104
+ this.dataSource.disconnect({});
2105
+ }
2106
+ // Create new ApiDataSource
2105
2107
  this.dataSource = new ApiDataSource(this.apiService);
2106
2108
  this.loadingSubscription = this.dataSource.loading$.subscribe(loading => {
2107
2109
  if (!loading) {
@@ -2109,11 +2111,14 @@ class TisSmartTableViewerComponent {
2109
2111
  this._paginator.pageIndex = this.pageIndex;
2110
2112
  this._paginator.pageSize = this.pageSize;
2111
2113
  }
2114
+ // ✅ FIX: Always clear caches when new data arrives (even if empty)
2112
2115
  // Clear background cache when new data arrives
2113
2116
  this.clearRowBackgroundCache();
2114
2117
  // Pre-compute all row backgrounds for optimal performance
2115
2118
  this.computeAllRowBackgrounds();
2119
+ // ✅ FIX: Ensure selection state is updated even when data changes from empty to populated
2116
2120
  this.checkAllRowsSelected();
2121
+ // ✅ FIX: Force change detection by emitting events
2117
2122
  this.onDataLoaded.emit(true);
2118
2123
  this.onSetExtraData.emit(this.dataSource.extraDataSubject.value);
2119
2124
  // if (this.selectedRowIds && this.selectedRowIds.length) {
@@ -2136,8 +2141,12 @@ class TisSmartTableViewerComponent {
2136
2141
  if (this.filterFormGroupSubscription) {
2137
2142
  this.filterFormGroupSubscription.unsubscribe();
2138
2143
  }
2144
+ // ✅ FIX: Use custom comparator for distinctUntilChanged to properly detect form value changes
2139
2145
  this.filterFormGroupSubscription = this.filterFormGroup.valueChanges
2140
- .pipe(takeUntil(this._onDestroy), distinctUntilChanged()).subscribe(val => {
2146
+ .pipe(takeUntil(this._onDestroy), distinctUntilChanged((prev, curr) => {
2147
+ // Custom comparator: deep compare form values
2148
+ return JSON.stringify(prev) === JSON.stringify(curr);
2149
+ })).subscribe(val => {
2141
2150
  this.filterHasNonEmptyValue = ValidationHelper.hasNonEmptyValue(val);
2142
2151
  });
2143
2152
  }
@@ -2283,11 +2292,21 @@ class TisSmartTableViewerComponent {
2283
2292
  }
2284
2293
  // Compute all row backgrounds when data changes (runs once per data load)
2285
2294
  computeAllRowBackgrounds() {
2286
- if (!this.dataSource?.apiSubject.value || !this.rowsConfig.backgroundApplyFunction) {
2295
+ // FIX: Always clear the cache first to ensure fresh computation
2296
+ this.computedRowBackgrounds.clear();
2297
+ // ✅ FIX: Safely check if we have data and a background function
2298
+ if (!this.dataSource?.apiSubject?.value ||
2299
+ !Array.isArray(this.dataSource.apiSubject.value) ||
2300
+ !this.rowsConfig.backgroundApplyFunction) {
2301
+ return;
2302
+ }
2303
+ // ✅ FIX: Only compute backgrounds if we have actual data
2304
+ if (this.dataSource.apiSubject.value.length === 0) {
2287
2305
  return;
2288
2306
  }
2289
- this.computedRowBackgrounds.clear();
2290
2307
  this.dataSource.apiSubject.value.forEach((row) => {
2308
+ if (!row)
2309
+ return; // Skip null/undefined rows
2291
2310
  const rowId = row?.id || row?.[this.selectedRowKey] || JSON.stringify(row);
2292
2311
  try {
2293
2312
  const background = this.rowsConfig.backgroundApplyFunction(row);
@@ -2376,6 +2395,8 @@ class TisSmartTableViewerComponent {
2376
2395
  this.isAllExpanded = false;
2377
2396
  // Clear expansion state when loading new data to avoid stale expansion state
2378
2397
  CollectionHelper.clearSet(this.expandedRowIds);
2398
+ // ✅ FIX: Clear row background cache before loading new data to prevent stale computed backgrounds
2399
+ this.clearRowBackgroundCache();
2379
2400
  const filterFormData = this.filterFormGroup?.value;
2380
2401
  this.filterHasNonEmptyValue = ValidationHelper.hasFormData(filterFormData);
2381
2402
  // Build query string using helper
@@ -2436,6 +2457,10 @@ class TisSmartTableViewerComponent {
2436
2457
  UrlHelper.safeNavigate(this.router, url);
2437
2458
  }
2438
2459
  toggleSelection(status, row) {
2460
+ // ✅ Add null guards to prevent crashes
2461
+ if (!row || !ValidationHelper.hasRowKey(row, this.selectedRowKey)) {
2462
+ return;
2463
+ }
2439
2464
  if (this.onlySingleSelection && status.checked) {
2440
2465
  this.selection.clear();
2441
2466
  }
@@ -2445,16 +2470,29 @@ class TisSmartTableViewerComponent {
2445
2470
  this.checkAllRowsSelected();
2446
2471
  }
2447
2472
  checkAllRowsSelected() {
2473
+ // ✅ Add comprehensive null guards to prevent crashes
2474
+ if (!this.dataSource?.apiSubject?.value || !Array.isArray(this.dataSource.apiSubject.value)) {
2475
+ this.isAllRowsSelected = false;
2476
+ this.allRowsSelectedChange.emit(this.isAllRowsSelected);
2477
+ return;
2478
+ }
2448
2479
  this.isAllRowsSelected = this.selection.selected.length === this.dataSource.apiSubject.value.length;
2449
2480
  this.allRowsSelectedChange.emit(this.isAllRowsSelected);
2450
2481
  }
2451
2482
  toggleAllRows() {
2483
+ // ✅ Add comprehensive null guards
2484
+ if (!this.dataSource?.apiSubject?.value || !Array.isArray(this.dataSource.apiSubject.value)) {
2485
+ return;
2486
+ }
2452
2487
  if (this.isAllRowsSelected) {
2453
2488
  this.selection.clear();
2454
2489
  }
2455
2490
  else {
2456
2491
  this.dataSource.apiSubject.value.forEach(row => {
2457
- this.selection.select(row);
2492
+ // ✅ Validate each row before selecting
2493
+ if (row && ValidationHelper.hasRowKey(row, this.selectedRowKey)) {
2494
+ this.selection.select(row);
2495
+ }
2458
2496
  });
2459
2497
  }
2460
2498
  this.selectedRows = this.selection.selected;
@@ -2465,8 +2503,13 @@ class TisSmartTableViewerComponent {
2465
2503
  $event.stopPropagation();
2466
2504
  }
2467
2505
  isChecked(row) {
2468
- return ValidationHelper.hasRowKey(row, this.selectedRowKey) &&
2469
- this.selectedIds.has(row[this.selectedRowKey]);
2506
+ // Add comprehensive null guards
2507
+ if (!row ||
2508
+ !ValidationHelper.hasRowKey(row, this.selectedRowKey) ||
2509
+ !this.selectedIds) {
2510
+ return false;
2511
+ }
2512
+ return this.selectedIds.has(row[this.selectedRowKey]);
2470
2513
  }
2471
2514
  getQueryParams(url) {
2472
2515
  return QueryParamsHelper.parseQueryParams(url);
@@ -2525,6 +2568,10 @@ class TisSmartTableViewerComponent {
2525
2568
  return this.computedRowBackgrounds.get(rowId) || null;
2526
2569
  }
2527
2570
  drop(event) {
2571
+ // ✅ Add null guards to prevent crashes
2572
+ if (!this.dataSource?.apiSubject?.value || !Array.isArray(this.dataSource.apiSubject.value)) {
2573
+ return;
2574
+ }
2528
2575
  // Ignore if the item was dropped at the same index
2529
2576
  if (event.previousIndex === event.currentIndex) {
2530
2577
  return;
@@ -2548,14 +2595,28 @@ class TisSmartTableViewerComponent {
2548
2595
  UrlHelper.handleSecondaryButtonClick(this.router, config);
2549
2596
  }
2550
2597
  setSelectedRows() {
2598
+ // ✅ Add null guards to prevent crashes
2599
+ if (!this.dataSource?.apiSubject?.value ||
2600
+ !Array.isArray(this.dataSource.apiSubject.value) ||
2601
+ !this.selectedRowIds) {
2602
+ this.selection.clear();
2603
+ this.selectedRows = [];
2604
+ this.selectedRowsChange.emit(this.selectedRows);
2605
+ return;
2606
+ }
2551
2607
  this.selection.clear();
2608
+ // ✅ FIX: Convert array to Set for O(1) lookup instead of O(n) indexOf
2609
+ const selectedIdsSet = new Set(this.selectedRowIds);
2552
2610
  this.dataSource.apiSubject.value.forEach(row => {
2553
- if (this.selectedRowIds.indexOf(row[this.selectedRowKey]) != -1) {
2611
+ if (row &&
2612
+ ValidationHelper.hasRowKey(row, this.selectedRowKey) &&
2613
+ selectedIdsSet.has(row[this.selectedRowKey])) {
2554
2614
  this.selection.select(row);
2555
2615
  }
2556
2616
  });
2557
2617
  this.selectedRows = this.selection.selected;
2558
2618
  this.selectedRowsChange.emit(this.selectedRows);
2619
+ this.checkAllRowsSelected();
2559
2620
  }
2560
2621
  resetSelectedRows() {
2561
2622
  this.isAllRowsSelected = false;