Filterable table.ts

interface SearchResult<T> {
    displayedResults: T[];
    total: number;
}

interface State {
    page: number;
    pageSize: number;
    searchTerm: string;
    sortColumn: string;
    sortDirection: SortDirection;
}

function compare(v1: any, v2: any) {
    return v1 < v2 ? -1 : v1 > v2 ? 1 : 0;
}

export abstract class FilterableTable<T> {
    private _loading$ = new BehaviorSubject<boolean>(true);
    private _search$ = new Subject<void>();
    private _displayedResults$ = new BehaviorSubject<T[]>([]);
    private _total$ = new BehaviorSubject<number>(0);

    // public allValues: T[] = [];

    private _state: State = {
        page: 1,
        pageSize: 4,
        searchTerm: '',
        sortColumn: '',
        sortDirection: '',
    };

    constructor(public allValues: T[]) {
        // this.allValues = allValues;
        this._search$
        .pipe(
            tap(() => this._loading$.next(true)),
            debounceTime(200),
            switchMap(() => this._search()),
            delay(200),
            tap(() => this._loading$.next(false))
        )
        .subscribe((result) => {
            this._displayedResults$.next(result.displayedResults);
            this._total$.next(result.total);
        });

        this._search$.next();
    }

    get displayedResults$() {
        return this._displayedResults$.asObservable();
    }
    get total$() {
        return this._total$.asObservable();
    }
    get loading$() {
        return this._loading$.asObservable();
    }
    get page() {
        return this._state.page;
    }
    get pageSize() {
        return this._state.pageSize;
    }
    get searchTerm() {
        return this._state.searchTerm;
    }

    set page(page: number) {
        this._set({ page });
    }
    set pageSize(pageSize: number) {
        this._set({ pageSize });
    }
    set searchTerm(searchTerm: string) {
        this._set({ searchTerm });
    }
    set sortColumn(sortColumn: string) {
        this._set({ sortColumn });
    }
    set sortDirection(sortDirection: SortDirection) {
        this._set({ sortDirection });
    }

    private _set(patch: Partial<State>) {
        Object.assign(this._state, patch);
        this._search$.next();
    }

    sort(apiResp: T[], column: string, direction: string): T[] {
        console.log(`sourting on column ${column}`);
        if (direction === '') {
        return apiResp;
        } else {
        return [...apiResp].sort((a: T, b: T) => {
            const res = compare(
            this.getProperty(a, column),
            this.getProperty(b, column)
            );
            return direction === 'asc' ? res : -res;
        });
        }
    }

    /**
        * Ability to get nested properties, i.e. `name.common`
        */
    getProperty(object: any, propertyName: string) {
        var parts = propertyName.split('.'),
        length = parts.length,
        i,
        property = object || this;

        for (i = 0; i < length; i++) {
        property = property[parts[i]];
        }

        return property;
    }

    abstract matches(resp: T, term: string): boolean;

    private _search(): Observable<SearchResult<T>> {
        const { sortColumn, sortDirection, pageSize, page, searchTerm } =
        this._state;

        // console.log(`allValues size ${this.allValues.length}`)
        // console.log(`searchTerm: ${searchTerm}`)

        // 1. sort
        let displayResults: T[] = this.sort(
        this.allValues,
        sortColumn,
        sortDirection
        );

        // 2. filter
        displayResults = displayResults.filter((country) =>
        this.matches(country, searchTerm)
        );
        const total = displayResults.length;

        // 3. paginate
        displayResults = displayResults.slice(
        (page - 1) * pageSize,
        (page - 1) * pageSize + pageSize
        );

        // console.log(`displayResults size ${displayResults.length}`)
        return of({ displayedResults: displayResults, total });
    }
}