import { ChangeDetectionStrategy , ChangeDetectorRef , Component , Input , OnDestroy , OnInit } from '@angular/core' ; import { BehaviorSubject , Subject } from 'rxjs' ; import { filter as rxFilter , takeUntil } from 'rxjs/operators' ; import { TypeaheadKeyService , TypeaheadOptionEvent } from '../../typeahead/index' ; import { FilterRemoveAllEvent } from '../events/filter-remove-all-event' ; import { FilterService } from '../filter.service' ; import { FilterDynamicListConfig } from '../interfaces/filter-dynamic-list-config.interface' ; import { Filter } from '../interfaces/filter.interface' ; let uniqueId = 0 ; @ Component ( { selector : 'ux-filter-dynamic' , templateUrl : './filter-dynamic.component.html' , changeDetection : ChangeDetectionStrategy . OnPush } ) export class FilterDynamicComponent implements OnInit , OnDestroy { /** The unique id is used multiple times - this is to ensure we only increment it once per instance */ private readonly _uniqueId : number = uniqueId ++ ; /** Define the input for the component */ @ Input ( ) id : string ; /** The list of possible filter options */ @ Input ( ) filters : Filter [ ] = [ ] ; /** Specify if there should be an initially selected filter */ @ Input ( ) initial : Filter ; /** Specify the typeahead options */ @ Input ( ) set options ( options ) { this . _options = options ; } /** Get the options with the defaults for any missing options */ get options ( ) : FilterDynamicListConfig { return { ... this . _defaultOptions , ... this . _options } ; } /** Generate a unique id for the typeahead */ typeaheadId : string = `ux-filter-dynamic-typeahead-${ this . _uniqueId }` ; /** Store the current search query */ query$ = new BehaviorSubject < string > ( '' ) ; /** Store the selected filter */ selected : Filter ; /** Indicate whether or not the typeahead should be shown */ showTypeahead : boolean = true ; /** Store the items that should be displayed in the typeahead */ typeaheadItems : string [ ] = [ ] ; /** Store the currently highlighted element */ highlightedElement : HTMLElement ; /** Store the open state of the typeahead */ typeaheadOpen : boolean = false ; /** Get the user provided id or fallback to a default ID */ get filterId ( ) : string { return this . id ?? `ux-filter-dynamic-${ this . _uniqueId }` ; } /** The default options */ private _defaultOptions : FilterDynamicListConfig = { placeholder : '' , minCharacters : 3 , maxResults : Infinity } ; /** Store the user specified typeahead options */ private _options : FilterDynamicListConfig = { ... this . _defaultOptions } ; /** Unsubscribe from all subscriptions */ private readonly _onDestroy = new Subject < void > ( ) ; constructor ( public readonly typeaheadKeyService , private readonly _filterService , private readonly _changeDetector ) { _filterService . events$ . pipe ( rxFilter ( event => event instanceof FilterRemoveAllEvent ) , takeUntil ( this . _onDestroy ) ) . subscribe ( ( ) => this . removeFilter ( ) ) ; _filterService . filters$ . pipe ( takeUntil ( this . _onDestroy ) ) . subscribe ( filters => { if ( this . selected && filters . indexOf ( this . selected ) === - 1 ) { this . removeFilter ( ) ; } } ) ; } /** Set up the initial conditions */ ngOnInit ( ) { this . selected = this . initial ; this . _filterService . filters$ . pipe ( takeUntil ( this . _onDestroy ) ) . subscribe ( filters => { filters . forEach ( filter => { if ( this . filters . indexOf ( filter ) !== - 1 ) { this . selected = filter ; } } ) ; this . _changeDetector . markForCheck ( ) ; } ) ; this . typeaheadItems = this . getItems ( ) ; const shouldHideTypeahead = this . options ?. maxIndividualItems + 1 >= this . filters . length ; if ( shouldHideTypeahead ) { this . showTypeahead = false ; } } /** Cleanup all subscriptions */ ngOnDestroy ( ) { this . _onDestroy . next ( ) ; this . _onDestroy . complete ( ) ; } /** Get the items to display in the typeahead based on the search query */ getItems ( ) { const query = this . query$ . value . toLowerCase ( ) ; return this . filters . filter ( item => item !== this . initial && item . name . toLowerCase ( ) . indexOf ( query ) !== - 1 ) . map ( item => item . name ) . slice ( 0 , this . _options . maxResults ) ; } /** When the dropdown is closed clear the query */ onClose ( ) { this . query$ . next ( '' ) ; } /** If a filter needs removed, and is not the initial filter then remove it */ removeFilter ( ) { if ( this . selected !== this . initial ) { this . _filterService . remove ( this . selected ) ; this . selected = this . initial ; } this . query$ . next ( '' ) ; this . _changeDetector . markForCheck ( ) ; } /** Select a specific filter */ selectFilter ( filter ) { this . removeFilter ( ) ; this . selected = filter ; this . _filterService . add ( this . selected ) ; } /** Update typeahead items and visibility */ updateTypeahead ( query ) { this . typeaheadOpen = query . length >= this . _options . minCharacters ; this . typeaheadItems = this . getItems ( ) ; } /** Select a filter from a typeahead item */ select ( event ) { const filter = this . filters . find ( _filter => _filter . name === event . option ) ; if ( filter ) { this . selectFilter ( filter ) ; } } }