use refCount for shareReplay & add shareDistinctLast operator
This commit is contained in:
parent
e1ce89e38d
commit
712178ea34
@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
import { map, startWith, switchMapTo } from 'rxjs/operators';
|
||||
import { processFilters, toFlatFilters } from './filter-utils';
|
||||
import { IFilterGroup } from './models/filter-group.model';
|
||||
import { INestedFilter } from './models/nested-filter.model';
|
||||
import { get } from '../utils';
|
||||
import { get, shareDistinctLast, shareLast } from '../utils';
|
||||
import { NestedFilter } from './models/nested-filter';
|
||||
import { Filter } from './models/filter';
|
||||
|
||||
@ -16,11 +16,7 @@ export class FilterService {
|
||||
private readonly _refresh$ = new Subject();
|
||||
|
||||
constructor() {
|
||||
this.filterGroups$ = this._refresh$.pipe(
|
||||
startWith(''),
|
||||
switchMap(() => this._filterGroups$.asObservable()),
|
||||
shareReplay(1),
|
||||
);
|
||||
this.filterGroups$ = this._refresh$.pipe(startWith(''), switchMapTo(this._filterGroups$.asObservable()), shareLast());
|
||||
|
||||
this.showResetFilters$ = this._showResetFilters$;
|
||||
}
|
||||
@ -33,8 +29,7 @@ export class FilterService {
|
||||
return this.filterGroups$.pipe(
|
||||
map(toFlatFilters),
|
||||
map(f => !!f.find(el => el.checked)),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -100,7 +95,7 @@ export class FilterService {
|
||||
getGroup$(slug: string): Observable<IFilterGroup | undefined> {
|
||||
return this.filterGroups$.pipe(
|
||||
get(group => group.slug === slug),
|
||||
shareReplay(1),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core';
|
||||
import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
|
||||
import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs';
|
||||
import { delay, distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
|
||||
import { any } from '../../utils';
|
||||
import { delay, map } from 'rxjs/operators';
|
||||
import { any, shareDistinctLast, shareLast } from '../../utils';
|
||||
import { handleCheckedValue } from '../filter-utils';
|
||||
import { FilterService } from '../filter.service';
|
||||
import { IFilterGroup } from '../models/filter-group.model';
|
||||
@ -13,8 +13,7 @@ import { Filter, IFilter } from '..';
|
||||
const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
|
||||
const atLeastOneIsExpandable = pipe(
|
||||
map<IFilterGroup | undefined, boolean>(group => !!group?.filters.some(areExpandable)),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
|
||||
@Component({
|
||||
@ -55,15 +54,14 @@ export class PopupFilterComponent implements OnInit {
|
||||
return combineLatest([this.primaryFilterGroup$, this.secondaryFilterGroup$]).pipe(
|
||||
map(([primary, secondary]) => [...(primary?.filters || []), ...(secondary?.filters || [])]),
|
||||
any(f => f.checked || !!f.indeterminate),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
private get _primaryFilters$(): Observable<IFilter[]> {
|
||||
return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
|
||||
map(([group]) => this.searchService.searchIn(group?.filters ?? [])),
|
||||
shareReplay(1),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -71,15 +69,14 @@ export class PopupFilterComponent implements OnInit {
|
||||
return combineLatest([this.primaryFilterGroup$, this.primaryFilters$, this.searchService.valueChanges$]).pipe(
|
||||
map(([group, filters, value]) => [!!group?.filterceptionPlaceholder, filters?.length === 0, value === '']),
|
||||
map(([hasFilterSearch, noFilters, searchIsEmpty]) => noFilters && (!hasFilterSearch || (hasFilterSearch && searchIsEmpty))),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug).pipe(shareReplay(1));
|
||||
this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug).pipe(shareLast());
|
||||
this.primaryFilters$ = this._primaryFilters$;
|
||||
this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug).pipe(shareReplay(1));
|
||||
this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug).pipe(shareLast());
|
||||
|
||||
this.hasActiveFilters$ = this._hasActiveFilters$;
|
||||
this.primaryFiltersDisabled$ = this._primaryFiltersDisabled$;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Directive, Injector, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { map, switchMapTo } from 'rxjs/operators';
|
||||
import { FilterService } from '../filtering';
|
||||
import { SortingService } from '../sorting';
|
||||
import { AutoUnsubscribe } from '../utils';
|
||||
import { AutoUnsubscribe, shareDistinctLast } from '../utils';
|
||||
import { SearchService } from '../search';
|
||||
import { EntitiesService, ListingService } from './services';
|
||||
import { IListable, ListingMode, ListingModes, TableColumnConfig } from './models';
|
||||
@ -51,23 +51,21 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
||||
|
||||
private get _sortedDisplayedEntities$(): Observable<readonly T[]> {
|
||||
const sort = (entities: T[]) => this.sortingService.defaultSort(entities);
|
||||
const sortedEntities = () => this.listingService.displayed$.pipe(map(sort));
|
||||
return this.sortingService.sortingOption$.pipe(switchMap(sortedEntities), distinctUntilChanged(), shareReplay(1));
|
||||
const sortedEntities$ = this.listingService.displayed$.pipe(map(sort));
|
||||
return this.sortingService.sortingOption$.pipe(switchMapTo(sortedEntities$), shareDistinctLast());
|
||||
}
|
||||
|
||||
private get _noMatch$(): Observable<boolean> {
|
||||
return combineLatest([this.entitiesService.allLength$, this.listingService.displayedLength$]).pipe(
|
||||
map(([hasEntities, hasDisplayedEntities]) => !!hasEntities && !hasDisplayedEntities),
|
||||
shareReplay(1),
|
||||
distinctUntilChanged(),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
private get _noContent$(): Observable<boolean> {
|
||||
return combineLatest([this._noMatch$, this.entitiesService.noData$]).pipe(
|
||||
map(([noMatch, noData]) => noMatch || noData),
|
||||
shareReplay(1),
|
||||
distinctUntilChanged(),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import { IListable } from '../models';
|
||||
import { IconButtonTypes } from '../../buttons';
|
||||
import { SearchService } from '../../search';
|
||||
import { FilterService } from '../../filtering';
|
||||
import { filterEach } from '../../utils';
|
||||
|
||||
@Component({
|
||||
selector: 'iqser-page-header',
|
||||
@ -27,7 +28,7 @@ export class PageHeaderComponent<T extends IListable> {
|
||||
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;
|
||||
@Output() readonly closeAction = new EventEmitter();
|
||||
|
||||
readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
|
||||
readonly filters$ = this.filterService?.filterGroups$.pipe(filterEach(f => !!f.icon));
|
||||
readonly showResetFilters$ = this._showResetFilters$;
|
||||
|
||||
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener, Input, OnInit } from '@angular/core';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { delay, distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
||||
import { delay, map, startWith } from 'rxjs/operators';
|
||||
import { combineLatest, fromEvent, Observable } from 'rxjs';
|
||||
import { Required } from '../../utils';
|
||||
import { Required, shareDistinctLast } from '../../utils';
|
||||
|
||||
const ButtonTypes = {
|
||||
top: 'top',
|
||||
@ -40,10 +40,10 @@ export class ScrollButtonComponent implements OnInit {
|
||||
const rangeChange$ = this.scrollViewport.renderedRangeStream.pipe(startWith(null));
|
||||
|
||||
/** Delay so that we can wait for items to be rendered in viewport and get correct values */
|
||||
const scroll$ = combineLatest([scrolled$, resized$, rangeChange$]).pipe(delay(0));
|
||||
const scroll$ = combineLatest([scrolled$, resized$, rangeChange$]).pipe(delay(0), shareDistinctLast());
|
||||
|
||||
this.showScrollUp$ = scroll$.pipe(map(showScrollUp), distinctUntilChanged());
|
||||
this.showScrollDown$ = scroll$.pipe(map(showScrollDown), distinctUntilChanged());
|
||||
this.showScrollUp$ = scroll$.pipe(map(showScrollUp));
|
||||
this.showScrollDown$ = scroll$.pipe(map(showScrollDown));
|
||||
}
|
||||
|
||||
scroll(type: ButtonType): void {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable, InjectionToken, Injector, Optional } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, tap } from 'rxjs/operators';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { IListable } from '../models';
|
||||
import { GenericService, QueryParam } from '../../services';
|
||||
import { getLength, List } from '../../utils';
|
||||
import { getLength, List, mapEach, shareDistinctLast } from '../../utils';
|
||||
|
||||
/**
|
||||
* This should be removed when refactoring is done
|
||||
@ -30,8 +30,8 @@ export class EntitiesService<E extends IListable, I = E> extends GenericService<
|
||||
@Optional() @Inject(ENTITY_PATH) protected readonly _defaultModelPath = '',
|
||||
) {
|
||||
super(_injector, _defaultModelPath);
|
||||
this.all$ = this._all$.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
|
||||
this.allLength$ = this._all$.pipe(getLength, distinctUntilChanged(), shareReplay(1));
|
||||
this.all$ = this._all$.asObservable().pipe(shareDistinctLast());
|
||||
this.allLength$ = this._all$.pipe(getLength, shareDistinctLast());
|
||||
this.noData$ = this._noData$;
|
||||
}
|
||||
|
||||
@ -42,14 +42,13 @@ export class EntitiesService<E extends IListable, I = E> extends GenericService<
|
||||
private get _noData$(): Observable<boolean> {
|
||||
return this.allLength$.pipe(
|
||||
map(length => length === 0),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
loadAll(modelPath = this._defaultModelPath, queryParams?: List<QueryParam>): Observable<E[]> {
|
||||
return this.getAll(modelPath, queryParams).pipe(
|
||||
map((entities: I[]) => entities.map(entity => new this._entityClass(entity))),
|
||||
mapEach(entity => new this._entityClass(entity)),
|
||||
tap((entities: E[]) => this.setEntities(entities)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, tap } from 'rxjs/operators';
|
||||
import { map, mapTo, tap } from 'rxjs/operators';
|
||||
import { FilterService, getFilteredEntities } from '../../filtering';
|
||||
import { SearchService } from '../../search';
|
||||
import { IListable } from '../models';
|
||||
import { EntitiesService } from './entities.service';
|
||||
import { AutoUnsubscribe, getLength } from '../../utils';
|
||||
import { getLength, shareDistinctLast, shareLast } from '../../utils';
|
||||
|
||||
@Injectable()
|
||||
export class ListingService<E extends IListable> extends AutoUnsubscribe {
|
||||
export class ListingService<E extends IListable> {
|
||||
readonly displayed$: Observable<E[]>;
|
||||
readonly displayedLength$: Observable<number>;
|
||||
readonly areAllSelected$: Observable<boolean>;
|
||||
@ -25,25 +25,16 @@ export class ListingService<E extends IListable> extends AutoUnsubscribe {
|
||||
private readonly _searchService: SearchService<E>,
|
||||
private readonly _entitiesService: EntitiesService<E>,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.displayed$ = this._getDisplayed$;
|
||||
this.displayedLength$ = this.displayed$.pipe(getLength, distinctUntilChanged(), shareReplay(1));
|
||||
this.displayedLength$ = this.displayed$.pipe(getLength, shareDistinctLast());
|
||||
|
||||
this.selected$ = this._selected$.asObservable().pipe(shareReplay(1));
|
||||
this.selectedEntities$ = this._selected$.asObservable().pipe(
|
||||
map(() => this.selected),
|
||||
shareReplay(1),
|
||||
);
|
||||
this.selectedLength$ = this._selected$.pipe(getLength, distinctUntilChanged(), shareReplay(1));
|
||||
this.selected$ = this._selected$.asObservable().pipe(shareDistinctLast());
|
||||
this.selectedEntities$ = this._selected$.asObservable().pipe(mapTo(this.selected), shareLast());
|
||||
this.selectedLength$ = this._selected$.pipe(getLength, shareDistinctLast());
|
||||
|
||||
this.areAllSelected$ = this._areAllSelected$;
|
||||
this.areSomeSelected$ = this._areSomeSelected$;
|
||||
this.notAllSelected$ = this._notAllSelected$;
|
||||
|
||||
this.addSubscription = this.displayed$.subscribe(() => {
|
||||
this._updateSelection();
|
||||
});
|
||||
}
|
||||
|
||||
get selected(): E[] {
|
||||
@ -64,33 +55,30 @@ export class ListingService<E extends IListable> extends AutoUnsubscribe {
|
||||
map(entities => this._searchService.searchIn(entities)),
|
||||
tap(displayed => {
|
||||
this._displayed = displayed;
|
||||
this._updateSelection();
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareLast(),
|
||||
);
|
||||
}
|
||||
|
||||
private get _areAllSelected$(): Observable<boolean> {
|
||||
return combineLatest([this.displayedLength$, this.selectedLength$]).pipe(
|
||||
map(([displayedLength, selectedLength]) => !!displayedLength && displayedLength === selectedLength),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
private get _areSomeSelected$(): Observable<boolean> {
|
||||
return this.selectedLength$.pipe(
|
||||
map(length => !!length),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
private get _notAllSelected$(): Observable<boolean> {
|
||||
return combineLatest([this.areAllSelected$, this.areSomeSelected$]).pipe(
|
||||
map(([allAreSelected, someAreSelected]) => !allAreSelected && someAreSelected),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
shareDistinctLast(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { IListable } from '../listing';
|
||||
import { distinctUntilChanged, shareReplay } from 'rxjs/operators';
|
||||
import { shareDistinctLast } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService<T extends IListable> {
|
||||
skip = false;
|
||||
private readonly _query$ = new BehaviorSubject('');
|
||||
readonly valueChanges$ = this._query$.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
|
||||
readonly valueChanges$ = this._query$.asObservable().pipe(shareDistinctLast());
|
||||
|
||||
get searchValue(): string {
|
||||
return this._query$.getValue();
|
||||
|
||||
@ -3,9 +3,8 @@ import { orderBy } from 'lodash';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SortingOption } from './models/sorting-option.model';
|
||||
import { SortingOrder, SortingOrders } from './models/sorting-order.type';
|
||||
import { KeysOf } from '../utils';
|
||||
import { KeysOf, shareDistinctLast } from '../utils';
|
||||
import { IListable } from '../listing';
|
||||
import { distinctUntilChanged, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class SortingService<T extends IListable> {
|
||||
@ -13,7 +12,7 @@ export class SortingService<T extends IListable> {
|
||||
column: 'searchKey',
|
||||
order: SortingOrders.asc,
|
||||
});
|
||||
readonly sortingOption$ = this._sortingOption$.asObservable().pipe(distinctUntilChanged(), shareReplay(1));
|
||||
readonly sortingOption$ = this._sortingOption$.asObservable().pipe(shareDistinctLast());
|
||||
|
||||
get sortingOption(): SortingOption<T> | undefined {
|
||||
return this._sortingOption$.getValue();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
|
||||
import { MonoTypeOperatorFunction, OperatorFunction, pipe } from 'rxjs';
|
||||
import { MonoTypeOperatorFunction, Observable, OperatorFunction, pipe, UnaryFunction } from 'rxjs';
|
||||
|
||||
export function get<T>(predicate: (value: T, index: number) => boolean): OperatorFunction<readonly T[], T | undefined> {
|
||||
return map(entities => entities.find(predicate));
|
||||
@ -21,5 +21,9 @@ export function shareLast<T>(values = 1): MonoTypeOperatorFunction<T> {
|
||||
return shareReplay<T>({ bufferSize: values, refCount: true });
|
||||
}
|
||||
|
||||
export function shareDistinctLast<T>(values = 1): UnaryFunction<Observable<T>, Observable<T>> {
|
||||
return pipe(distinctUntilChanged(), shareLast(values));
|
||||
}
|
||||
|
||||
export const toLengthValue = (entities: unknown[]): number => entities?.length ?? 0;
|
||||
export const getLength = pipe(map(toLengthValue), distinctUntilChanged());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user