Skip to content

Commit bb42409

Browse files
MwanPygmayunai-reliefappAntoineRelief
authored
fix: charts would load multiple times whenever dashboard would init (#1933)
* skip first time loading, merged two subscriptions, and increased throttle time so that activating the filter does not trigger loading two times * AB#76508_fix_grids_loading_multiple_times refactor: merge filter enable and filter value properties into same stream as we are using it in all the widgets to trigger filtering values refactor: apply the new merge stream into the components using both filter enable and filter value stream for the same purpose fix: trigger filter value only when source changes to avoid multiple stream emitted values fix: added some timeout cleaners to avoid memory leaks * AB#76508_fix_grids_loading_multiple_times refactor: listen to filter value changes only for related widget as the filter injection method would take care of applying filters or not if enabled or not. fix: add again the isEnabled filter property for filter toggle and dashboard loading --------- Co-authored-by: Unai Zalba <[email protected]> Co-authored-by: Antoine Hurard <[email protected]>
1 parent 314d66e commit bb42409

File tree

8 files changed

+71
-65
lines changed

8 files changed

+71
-65
lines changed

apps/back-office/src/app/dashboard/pages/dashboard/dashboard.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ export class DashboardComponent
333333
this.showFilter = this.dashboard.showFilter ?? false;
334334
this.contextService.isFilterEnabled.next(this.showFilter);
335335
} else {
336+
this.contextService.isFilterEnabled.next(false);
336337
this.snackBar.openSnackBar(
337338
this.translate.instant('common.notifications.accessNotProvided', {
338339
type: this.translate
@@ -688,8 +689,8 @@ export class DashboardComponent
688689
this.handleDashboardMutationResponse(errors, data);
689690
},
690691
complete: () => {
691-
this.loading = false;
692692
this.contextService.isFilterEnabled.next(this.showFilter);
693+
this.loading = false;
693694
},
694695
});
695696
}

apps/front-office/src/app/application/pages/dashboard/dashboard.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export class DashboardComponent
214214
this.showFilter = this.dashboard.showFilter ?? false;
215215
this.contextService.isFilterEnabled.next(this.showFilter);
216216
} else {
217+
this.contextService.isFilterEnabled.next(false);
217218
this.snackBar.openSnackBar(
218219
this.translate.instant('common.notifications.accessNotProvided', {
219220
type: this.translate

libs/shared/src/lib/components/aggregation/aggregation-grid/aggregation-grid.component.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,12 @@ export class AggregationGridComponent
100100
}
101101

102102
ngOnInit(): void {
103-
this.getAggregationData();
104-
this.getAggregationFields();
105-
106103
this.contextService.filter$
107104
.pipe(debounceTime(500), takeUntil(this.destroy$))
108105
.subscribe(() => {
109106
this.getAggregationData();
110107
});
111-
112-
this.contextService.isFilterEnabled$
113-
.pipe(debounceTime(500), takeUntil(this.destroy$))
114-
.subscribe(() => {
115-
this.getAggregationData();
116-
});
108+
this.getAggregationFields();
117109
}
118110

119111
ngOnChanges(): void {

libs/shared/src/lib/components/dashboard-filter/dashboard-filter.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ export class DashboardFilterComponent
169169

170170
override ngOnDestroy(): void {
171171
super.ngOnDestroy();
172+
// Trigger filtering with no values on deactivating context filter
173+
this.contextService.filter.next({});
172174
this.resizeObserver.disconnect();
173175
}
174176

@@ -446,7 +448,6 @@ export class DashboardFilterComponent
446448
};
447449
return acc;
448450
}, {});
449-
450451
this.contextService.filter.next(surveyData);
451452
this.ngZone.run(() => {
452453
this.quickFilters = displayValues

libs/shared/src/lib/components/ui/map/map.component.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ import {
5252
isEqual,
5353
flatMapDeep,
5454
} from 'lodash';
55-
import { BehaviorSubject, concatMap, debounceTime, takeUntil } from 'rxjs';
55+
import {
56+
BehaviorSubject,
57+
concatMap,
58+
debounceTime,
59+
filter,
60+
takeUntil,
61+
} from 'rxjs';
5662
import { MapPopupService } from './map-popup/map-popup.service';
5763
import { Platform } from '@angular/cdk/platform';
5864
import { ContextService } from '../../../services/context/context.service';
@@ -155,6 +161,9 @@ export class MapComponent
155161
/** Refreshing layers. When true, should prevent layers to be duplicated */
156162
private refreshingLayers = new BehaviorSubject<boolean>(true);
157163

164+
/** Timeout listeners */
165+
firstLoadEmitTimeoutListener!: NodeJS.Timeout;
166+
158167
/**
159168
* Map widget component
160169
*
@@ -244,7 +253,10 @@ export class MapComponent
244253

245254
this.setUpMapListeners();
246255

247-
setTimeout(() => {
256+
if (this.firstLoadEmitTimeoutListener) {
257+
clearTimeout(this.firstLoadEmitTimeoutListener);
258+
}
259+
this.firstLoadEmitTimeoutListener = setTimeout(() => {
248260
this.map.invalidateSize();
249261
this.mapEvent.emit({
250262
type: MapEventType.FIRST_LOAD,
@@ -256,15 +268,26 @@ export class MapComponent
256268
});
257269
//}
258270
}, 1000);
271+
259272
/**
260273
* Keep checking until filters are applied in order to apply next one
261274
*/
275+
let filterCheckTimeoutListener: NodeJS.Timeout;
262276
const loadNextFilters = (): Promise<void> => {
263277
const checkAgain = (resolve: () => void) => {
264278
if (this.refreshingLayers.getValue()) {
279+
if (filterCheckTimeoutListener) {
280+
clearTimeout(filterCheckTimeoutListener);
281+
}
265282
resolve();
266283
} else {
267-
setTimeout(() => checkAgain(resolve), 100);
284+
if (filterCheckTimeoutListener) {
285+
clearTimeout(filterCheckTimeoutListener);
286+
}
287+
filterCheckTimeoutListener = setTimeout(
288+
() => checkAgain(resolve),
289+
100
290+
);
268291
}
269292
};
270293
return new Promise(checkAgain);
@@ -274,22 +297,23 @@ export class MapComponent
274297
this.contextService.filter$
275298
.pipe(
276299
debounceTime(500),
300+
filter(() => {
301+
const filters = this.contextService.filter.getValue();
302+
return !isEqual(filters, this.appliedDashboardFilters);
303+
}),
277304
concatMap(() => loadNextFilters()),
278305
takeUntil(this.destroy$)
279306
)
280307
.subscribe(() => {
281308
this.filterLayers();
282309
});
283-
284-
this.contextService.isFilterEnabled$
285-
.pipe(debounceTime(500), takeUntil(this.destroy$))
286-
.subscribe(() => {
287-
this.filterLayers();
288-
});
289310
}
290311

291312
override ngOnDestroy(): void {
292313
super.ngOnDestroy();
314+
if (this.firstLoadEmitTimeoutListener) {
315+
clearTimeout(this.firstLoadEmitTimeoutListener);
316+
}
293317
this.resizeObserver?.disconnect();
294318
}
295319

@@ -967,9 +991,6 @@ export class MapComponent
967991
private async filterLayers() {
968992
this.document.getElementById('layer-control-button-close')?.click();
969993
const filters = this.contextService.filter.getValue();
970-
if (isEqual(filters, this.appliedDashboardFilters)) {
971-
return;
972-
}
973994
this.refreshingLayers.next(false);
974995
this.appliedDashboardFilters = filters;
975996
const { layers: layersToGet, controls } = this.extractSettings();

libs/shared/src/lib/components/widgets/chart/chart.component.ts

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import {
2-
ChangeDetectorRef,
32
Component,
43
Input,
54
OnChanges,
65
SimpleChanges,
76
ViewChild,
87
Inject,
8+
OnInit,
99
} from '@angular/core';
1010
import { LineChartComponent } from '../../ui/charts/line-chart/line-chart.component';
1111
import { PieDonutChartComponent } from '../../ui/charts/pie-donut-chart/pie-donut-chart.component';
1212
import { BarChartComponent } from '../../ui/charts/bar-chart/bar-chart.component';
1313
import { uniq, get, groupBy, isEqual } from 'lodash';
1414
import { AggregationService } from '../../../services/aggregation/aggregation.service';
1515
import { UnsubscribeComponent } from '../../utils/unsubscribe/unsubscribe.component';
16-
import { debounceTime, takeUntil } from 'rxjs/operators';
16+
import { takeUntil } from 'rxjs/operators';
1717
import { BehaviorSubject } from 'rxjs';
1818
import { TranslateService } from '@ngx-translate/core';
1919
import { ContextService } from '../../../services/context/context.service';
@@ -32,7 +32,10 @@ const DEFAULT_FILE_NAME = 'chart';
3232
templateUrl: './chart.component.html',
3333
styleUrls: ['./chart.component.scss'],
3434
})
35-
export class ChartComponent extends UnsubscribeComponent implements OnChanges {
35+
export class ChartComponent
36+
extends UnsubscribeComponent
37+
implements OnInit, OnChanges
38+
{
3639
// === DATA ===
3740
public loading = true;
3841
public options: any = null;
@@ -78,38 +81,22 @@ export class ChartComponent extends UnsubscribeComponent implements OnChanges {
7881
* @param aggregationService Shared aggregation service
7982
* @param translate Angular translate service
8083
* @param contextService Shared context service
81-
* @param cdr Angular change detector
8284
* @param document document
8385
*/
8486
constructor(
8587
private aggregationService: AggregationService,
8688
private translate: TranslateService,
8789
private contextService: ContextService,
88-
private cdr: ChangeDetectorRef,
8990
@Inject(DOCUMENT) private document: Document
9091
) {
9192
super();
93+
}
9294

93-
this.contextService.filter$
94-
.pipe(debounceTime(500), takeUntil(this.destroy$))
95-
.subscribe(() => {
96-
this.loadChart();
97-
this.getOptions();
98-
});
99-
100-
this.contextService.isFilterEnabled$
101-
.pipe(debounceTime(500), takeUntil(this.destroy$))
102-
.subscribe(() => {
103-
this.loadChart();
104-
this.getOptions();
105-
});
106-
107-
// Not entirely sure why the change detection is not happening automatically
108-
// when the series are updated, but this forces it to happen
109-
this.series$.pipe(takeUntil(this.destroy$)).subscribe(() => {
110-
setTimeout(() => {
111-
this.cdr.detectChanges();
112-
}, 100);
95+
ngOnInit(): void {
96+
this.contextService.filter$.pipe(takeUntil(this.destroy$)).subscribe(() => {
97+
this.series.next([]);
98+
this.loadChart();
99+
this.getOptions();
113100
});
114101
}
115102

@@ -133,7 +120,9 @@ export class ChartComponent extends UnsubscribeComponent implements OnChanges {
133120
},
134121
};
135122

136-
if (!isEqual(previousDatasource, currentDatasource)) this.loadChart();
123+
if (!isEqual(previousDatasource, currentDatasource)) {
124+
this.loadChart();
125+
}
137126
this.getOptions();
138127
}
139128

libs/shared/src/lib/components/widgets/summary-card/summary-card.component.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ export class SummaryCardComponent
196196
this.setupDynamicCards();
197197
this.setupGridSettings();
198198
this.searchControl.valueChanges
199-
.pipe(debounceTime(2000), distinctUntilChanged())
199+
.pipe(
200+
debounceTime(2000),
201+
distinctUntilChanged(),
202+
takeUntil(this.destroy$)
203+
)
200204
.subscribe((value) => {
201205
this.handleSearch(value || '');
202206
});
@@ -212,18 +216,6 @@ export class SummaryCardComponent
212216
totalItems: 0,
213217
});
214218
});
215-
216-
this.contextService.isFilterEnabled$
217-
.pipe(debounceTime(500), takeUntil(this.destroy$))
218-
.subscribe(() => {
219-
this.onPage({
220-
pageSize: DEFAULT_PAGE_SIZE,
221-
skip: 0,
222-
previousPageIndex: 0,
223-
pageIndex: 0,
224-
totalItems: 0,
225-
});
226-
});
227219
}
228220

229221
ngAfterViewInit(): void {

libs/shared/src/lib/services/context/context.service.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@angular/core';
2-
import { BehaviorSubject } from 'rxjs';
2+
import { BehaviorSubject, filter, map, pairwise } from 'rxjs';
33
import { ApplicationService } from '../application/application.service';
44
import { Application } from '../../models/application.model';
55
import localForage from 'localforage';
@@ -8,7 +8,7 @@ import {
88
FilterDescriptor,
99
} from '@progress/kendo-data-query';
1010
import { cloneDeep } from '@apollo/client/utilities';
11-
import { isNil, isEmpty, get } from 'lodash';
11+
import { isNil, isEmpty, get, isEqual } from 'lodash';
1212

1313
/**
1414
* Application context service
@@ -35,7 +35,16 @@ export class ContextService {
3535

3636
/** @returns filter value as observable */
3737
get filter$() {
38-
return this.filter.asObservable();
38+
return this.filter.asObservable().pipe(
39+
pairwise(),
40+
// We only emit a filter value if filter value changes and we send back the actual(curr) value
41+
filter(
42+
([prev, curr]: [Record<string, any>, Record<string, any>]) =>
43+
!isEqual(prev, curr)
44+
),
45+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
46+
map(([prev, curr]: [Record<string, any>, Record<string, any>]) => curr)
47+
);
3948
}
4049

4150
/** @returns filterStructure value as observable */

0 commit comments

Comments
 (0)