Asymmetrik/ngx-starter

View on GitHub
src/app/common/table/columns/selection/asy-selection-column.component.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { CdkTableModule } from '@angular/cdk/table';
import { AsyncPipe } from '@angular/common';
import {
    AfterViewInit,
    ChangeDetectionStrategy,
    Component,
    DestroyRef,
    OnInit,
    booleanAttribute,
    inject,
    input
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { AsyTableDataSource } from '../../asy-table-data-source';
import { AsyAbstractColumnComponent } from '../asy-abstract-column.component';

type IsSelectableFn<T> = (index: number, rowData: T) => boolean;

type TrackByFn<T, TB> = (index: number, rowData: T) => TB;

@Component({
    selector: 'asy-selection-column',
    templateUrl: './asy-selection-column.component.html',
    styleUrls: ['./asy-selection-column.component.scss'],
    changeDetection: ChangeDetectionStrategy.Default,
    standalone: true,
    imports: [CdkTableModule, AsyncPipe]
})
export class AsySelectionColumnComponent<T, TB = T>
    extends AsyAbstractColumnComponent<T>
    implements AfterViewInit, OnInit
{
    readonly #destroyRef = inject(DestroyRef);
    #dataSource: AsyTableDataSource<T>;
    #selectionModel = new SelectionModel<TB>();

    readonly enableSelectAll = input(true, { transform: booleanAttribute });
    readonly clearOnLoad = input(true, { transform: booleanAttribute });
    readonly multi = input(true, { transform: booleanAttribute });
    readonly isSelectable = input<IsSelectableFn<T>>(() => true);
    readonly trackBy = input<TrackByFn<T, TB>>((index, rowData) => rowData as unknown as TB);

    _isAllSelected$: Observable<boolean>;

    _isMultiTemplateDataRows = false;

    constructor() {
        super();
        this.name = 'selection';
    }

    override ngOnInit(): void {
        super.ngOnInit();

        if (this.multi()) {
            const selection = this.#selectionModel.selected;
            this.#selectionModel = new SelectionModel<TB>(this.multi());
            this.#selectionModel.select(...selection);
        }

        if (this._isAsyTableDataSource(this._table.dataSource)) {
            this.#dataSource = this._table.dataSource;
            this._isMultiTemplateDataRows = this._table.multiTemplateDataRows;
        } else {
            throw Error(
                'Selection column could not find a parent table with dataSource of type AsyTableDataSource'
            );
        }
    }

    ngAfterViewInit(): void {
        if (this.clearOnLoad()) {
            this.#dataSource.pagingResults$
                .pipe(takeUntilDestroyed(this.#destroyRef))
                .subscribe(() => {
                    this.#selectionModel.clear();
                });
        }

        this._isAllSelected$ = this.#selectionModel.changed.pipe(
            map(() => this._isAllSelected()),
            takeUntilDestroyed(this.#destroyRef)
        );
    }

    select(...trackByValues: TB[]) {
        this.#selectionModel.select(...trackByValues);
    }

    toggle(index: number, result: T) {
        this.#selectionModel.toggle(this.trackBy()(index, result));
    }

    isSelected(index: number, result: T) {
        return this.#selectionModel.isSelected(this.trackBy()(index, result));
    }

    get selected() {
        return this.#selectionModel.selected;
    }

    // eslint-disable-next-line rxjs/finnish
    get changed() {
        return this.#selectionModel.changed;
    }

    hasSelection() {
        return this.#selectionModel.hasValue();
    }

    _isAllSelected() {
        return (
            this.#selectionModel.selected.length > 0 &&
            this.#selectionModel.selected.length ===
                this.#dataSource.pagingResults$.value.elements.filter((result, index) =>
                    this.isSelectable()(index, result)
                ).length
        );
    }

    _toggleAll() {
        if (this._isAllSelected()) {
            this.#selectionModel.clear();
        } else {
            this.#dataSource.pagingResults$.value.elements.forEach((result, index) => {
                if (this.isSelectable()(index, result)) {
                    this.select(this.trackBy()(index, result));
                }
            });
        }
    }

    private _isAsyTableDataSource(
        // eslint-disable-next-line rxjs/finnish
        dataSource: DataSource<T> | Observable<ReadonlyArray<T> | T[]> | ReadonlyArray<T> | T[]
    ): dataSource is AsyTableDataSource<T> {
        return 'pagingResults$' in dataSource;
    }
}