import { Input, OnInit, Component, Output, EventEmitter, ViewChild, ElementRef, QueryList, AfterViewInit } from '@angular/core'; import { FormControl, FormGroupDirective, NgForm, FormArray, AbstractControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; import { ErrorStateMatcher, MatChipInputEvent, MatAutocompleteSelectedEvent, MatChipList } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; import { Subject , Observable } from 'rxjs'; import { map, startWith, merge, mapTo, mergeMap, timeout } from 'rxjs/operators'; import { SingleAutoCompleteConfiguration } from './single-auto-complete-configuration'; @Component({ selector: 'app-single-auto-complete', templateUrl: './single-auto-complete.component.html', styleUrls: ['./single-auto-complete.component.scss'] }) export class SingleAutoCompleteComponent implements OnInit { @Input() reactiveFormControl: FormControl; @Input() placeholder: string; @Input() validationErrorString: string; @Input() configuration: SingleAutoCompleteConfiguration; // Selected Option Event @Output() optionSelected: EventEmitter = new EventEmitter(); private requestDelay = 200; //ms private minFilteringChars = 3; private loadDataOnStart = true; // @Input() items: Observable; // @Input() filterFn?: (searchQuery: string) => Observable; // @Input() displayFn?: (item: any) => string; // @Input() titleFn?: (item: any) => string; // @Input() subtitleFn?: (item: any) => string; loading = false; _items: Observable; constructor() { } ngOnInit() { } filter(query: string): Observable { // If loadDataOnStart is enabled and query is empty we return the initial items. if (this.isNullOrEmpty(query) && this.loadDataOnStart) { return this.configuration.items || Observable.of([]); } else if (query && query.length >= this.minFilteringChars) { if (this.configuration.filterFn) { return this.configuration.filterFn(query); } else { return this.configuration.items || Observable.of([]); } } else { return Observable.of([]); } } isNullOrEmpty(query: string): boolean { return typeof query !== 'string' || query === null || query.length === 0; } _displayFn(item: any): string { if (this.configuration.displayFn && item) { return this.configuration.displayFn(item); } return item; } _titleFn(item: any): string { if (this.configuration.titleFn && item) { return this.configuration.titleFn(item); } return item; } _subtitleFn(item: any): string { if (this.configuration.subtitleFn && item) { return this.configuration.subtitleFn(item); } return null; } _requestDelay(): number { return this.configuration.requestDelay || this.requestDelay; } _minFilteringChars(): number { return this.configuration.minFilteringChars || this.minFilteringChars; } _loadDataOnStart(): boolean { return this.configuration.loadDataOnStart || this.loadDataOnStart; } _optionSelected(event: MatAutocompleteSelectedEvent) { this.optionSelected.emit(event.option.value); } _onInputFocus() { // We set the items observable on focus to avoid the request being executed on component load. if (!this._items) { this._items = this.reactiveFormControl.valueChanges .startWith(null) .debounceTime(this.requestDelay) .distinctUntilChanged() .do(() => { this.loading = true; }) .flatMap(query => { // If its a valid object, a selection just made and the object is set as the value of the form control. That means we should fire an extra request to the server. if (this._isValidObject(query)) { return Observable.of([]); } return this.filter(query); }) .do(() => { this.loading = false; }); } } _isValidObject(value: any): boolean { try { if (!value) { return false; } if (typeof value !== 'object') { JSON.parse(value); } } catch (e) { return false; } return true; } }