Asymmetrik/ngx-starter

View on GitHub
src/app/common/search-input/search-input.component.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
import { animate, style, transition, trigger } from '@angular/animations';
import { NgClass } from '@angular/common';
import {
    ChangeDetectionStrategy,
    Component,
    booleanAttribute,
    input,
    model,
    numberAttribute,
    output,
    signal
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';

import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

@Component({
    selector: 'asy-search-input',
    templateUrl: './search-input.component.html',
    styleUrls: ['./search-input.component.scss'],
    animations: [
        trigger('minCount', [
            transition(':enter', [
                style({ opacity: 0, height: 0 }),
                animate('200ms ease', style({ opacity: 1, height: '*' }))
            ]),
            transition(':leave', [
                style({ opacity: 1, height: '*' }),
                animate('200ms ease', style({ opacity: 0, height: 0 }))
            ])
        ])
    ],
    standalone: true,
    imports: [FormsModule, NgClass],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchInputComponent {
    search = model('');

    readonly placeholder = input('Search...');

    /**
     * If true, searches will be made on `input` events, otherwise searches will be made on `keyup` events
     */
    readonly preferInputEvent = input(true, { transform: booleanAttribute });

    /**
     * Specifies a minimum character count required to search.
     * In the event the number of characters is between 0 and the minimum, a warning message is shown beneath the search bar
     */
    readonly minSearchCharacterCount = input(0, { transform: numberAttribute });

    /**
     * When set to true, the minimum search character
     * message count will not be displayed, even if the search
     * value is less than the minimum number of characters.
     */
    readonly disableMinCountMessage = input(false, { transform: booleanAttribute });

    readonly showMinCountMessage = signal(false);

    readonly applySearch = output<string>();

    searchInput$ = new Subject<void>();

    constructor() {
        this.searchInput$.pipe(debounceTime(350), takeUntilDestroyed()).subscribe(() => {
            if (
                this.search().length === 0 ||
                this.search().length >= this.minSearchCharacterCount()
            ) {
                this.showMinCountMessage.set(false);
                this.applySearch.emit(this.search());
            } else {
                this.showMinCountMessage.set(true);
            }
        });
    }

    onKeyup() {
        if (!this.preferInputEvent()) {
            this.searchInput$.next();
        }
    }

    onInput() {
        if (this.preferInputEvent()) {
            this.searchInput$.next();
        }
    }

    clearSearch(event?: MouseEvent) {
        if (this.search().length > 0) {
            this.search.set('');
            this.applySearch.emit(this.search());
            if (event) {
                event.stopPropagation();
            }
        }
    }
}