projects/rebirth-ng/src/lib/auto-complete/auto-complete.directive.ts
        
                        OnInit
                        OnDestroy
                        ControlValueAccessor
            
| providers | 
                            
                                {
    : , : (() => ), : true
}
                            
                         | 
                    
| selector | [reAutoComplete] | 
                    
                        Properties | 
                
                        
  | 
                
                        Methods | 
                
                        
  | 
                
                        Inputs | 
                
                        Outputs | 
                
                        HostListeners | 
                
constructor(elementRef: ElementRef, viewContainerRef: ViewContainerRef, componentFactoryResolver: ComponentFactoryResolver, renderer: Renderer2, injector: Injector, positionService: PositionService, rebirthNGConfig: RebirthNGConfig, changeDetectorRef: ChangeDetectorRef)
                     | 
                |||||||||||||||||||||||||||
| 
                             
                                    Parameters :
                                     
                    
  | 
                
                        
                        appendBody
                     | 
                    
                         
                            Default value:   | 
                
                        
                        cssClass
                     | 
                    
                             
                            Type:      | 
                
                        
                        dataSource
                     | 
                    
                             
                            Type:      | 
                
                        
                        delay
                     | 
                    
                             
                            Type:      | 
                
                        
                        disabled
                     | 
                    
                             
                            Type:      | 
                
                        
                        formatter
                     | 
                    
                             
                            Type:      | 
                
                        
                        itemTemplate
                     | 
                    
                             
                            Type:      | 
                
                        
                        minLength
                     | 
                    
                             
                            Type:      | 
                
                        
                        noResultItemTemplate
                     | 
                    
                             
                            Type:      | 
                
                        
                        onSearch
                     | 
                    
                             
                            Type:      | 
                
                        
                        placementElement
                     | 
                    
                             
                            Type:      | 
                
                        
                        selectValueChange
                     | 
                    
                        $event type:    EventEmitter
                     | 
                
| blur | 
blur()
                     | 
                
| document:click | 
                            Arguments : '$event' 
                         | 
                    
document:click($event: Event)
                     | 
                
| keydown.ArrowDown | 
                            Arguments : '$event' 
                         | 
                    
keydown.ArrowDown($event: )
                     | 
                
| keydown.ArrowUp | 
                            Arguments : '$event' 
                         | 
                    
keydown.ArrowUp($event: )
                     | 
                
| keydown.Enter | 
                            Arguments : '$event' 
                         | 
                    
keydown.Enter($event: )
                     | 
                
| keydown.esc | 
                            Arguments : '$event' 
                         | 
                    
keydown.esc($event: )
                     | 
                
| Private fillPopup | 
                            
                        fillPopup(source?: any, term?: string)
                     | 
                
| 
                            
                             
                                Returns :      
                                void
                             | 
                
| Private hidePopup | 
                            
                        hidePopup()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| ngOnDestroy | 
ngOnDestroy()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| ngOnInit | 
ngOnInit()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| onSourceChange | |||||||||
onSourceChange(source: , term?: string)
                     | 
                |||||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| Private onTermChange | ||||
                            
                        onTermChange(term: )
                     | 
                ||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| positionPopup | 
positionPopup()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| Private registerInputEvent | ||||||
                            
                        registerInputEvent(elementRef: ElementRef)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                any
                             | 
                
| registerOnChange | ||||||
registerOnChange(fn: any)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| registerOnTouched | ||||||
registerOnTouched(fn: any)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| Private removePopView | 
                            
                        removePopView()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| Private setupArraySource | 
                            
                        setupArraySource()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| toggle | ||||||
toggle($event?: Event)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| Private unSubscription | 
                            
                        unSubscription()
                     | 
                
| 
                             
                                Returns :      
                    void
                             | 
                
| Private writeInputValue | ||||||
                            
                        writeInputValue(value: any)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| writeValue | ||||||
writeValue(obj: any)
                     | 
                ||||||
| 
                             
                                    Parameters :
                                     
                            
 
                                Returns :      
                                void
                             | 
                
| Private arraySource | 
                        arraySource:     
                     | 
                
                            Type :     any[]
                         | 
                    
| Private onChange | 
                        onChange:     
                     | 
                
                            Default value : (_: any) => null
                         | 
                    
| Private onTouched | 
                        onTouched:     
                     | 
                
                            Default value : () => null
                         | 
                    
| Private placement | 
                        placement:     
                     | 
                
                            Type :     string
                         | 
                    
                            Default value : 'bottom-left'
                         | 
                    
| Private popupRef | 
                        popupRef:     
                     | 
                
                            Type :     ComponentRef<AutoCompletePopupComponent>
                         | 
                    
| Private subscription | 
                        subscription:     
                     | 
                
                            Type :     Subscription
                         | 
                    
| term | 
                        term:     
                     | 
                
                            Type :     string
                         | 
                    
| Private value | 
                        value:     
                     | 
                
                            Type :     any
                         | 
                    
| Private valueChanges | 
                        valueChanges:     
                     | 
                
                            Type :     Observable<any[]>
                         | 
                    
| dataSource | ||||||
                        setdataSource(dataSource: [])
                     | 
                ||||||
| 
                                 
                                        Parameters :
                                         
                                
 
                                    Returns :      
                        void
                                 | 
                    
import {
  ChangeDetectorRef,
  ComponentFactoryResolver,
  ComponentRef,
  Directive,
  ElementRef,
  EventEmitter,
  forwardRef,
  HostListener,
  Injector,
  Input,
  OnDestroy,
  OnInit,
  Output,
  Renderer2,
  TemplateRef,
  ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent } from 'rxjs';
import { of } from 'rxjs';
import { Observable } from 'rxjs';
import { Subscription } from 'rxjs';
import { map, filter, debounceTime, switchMap, tap } from 'rxjs/operators';
import { RebirthNGConfig } from '../rebirth-ng.config';
import { PositionService } from '../position/positioning.service';
import { AutoCompletePopupComponent } from './auto-complete-popup.component';
import { stopPropagationIfExist } from '../utils/dom-utils';
@Directive({
  selector: '[reAutoComplete]',
  exportAs: 'autoComplete',
  host: {
    'autocomplete': 'off',
    'autocapitalize': 'off',
    'autocorrect': 'off'
  },
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => AutoCompleteDirective),
    multi: true
  }]
})
export class AutoCompleteDirective implements OnInit, OnDestroy, ControlValueAccessor {
  @Input() disabled: boolean;
  @Input() cssClass: string;
  @Input() delay: number;
  @Input() minLength: number;
  @Input() appendBody = false;
  @Input() itemTemplate: TemplateRef<any>;
  @Input() noResultItemTemplate: TemplateRef<any>;
  @Input() formatter: (item: any) => string;
  @Input() onSearch: (term: string, target?: AutoCompleteDirective) => Observable<any[]>;
  @Output() selectValueChange = new EventEmitter<any>();
  @Input() placementElement: any;
  term: string;
  private valueChanges: Observable<any[]>;
  private value: any;
  private placement = 'bottom-left';
  private subscription: Subscription;
  private arraySource: any[];
  private popupRef: ComponentRef<AutoCompletePopupComponent>;
  private onChange = (_: any) => null;
  private onTouched = () => null;
  constructor(private elementRef: ElementRef, private viewContainerRef: ViewContainerRef,
              private componentFactoryResolver: ComponentFactoryResolver, private renderer: Renderer2,
              private injector: Injector, private positionService: PositionService,
              private rebirthNGConfig: RebirthNGConfig, private changeDetectorRef: ChangeDetectorRef) {
    this.delay = rebirthNGConfig.autoComplete.delay;
    this.minLength = rebirthNGConfig.autoComplete.minLength;
    this.itemTemplate = rebirthNGConfig.autoComplete.itemTemplate;
    this.noResultItemTemplate = rebirthNGConfig.autoComplete.noResultItemTemplate;
    this.formatter = rebirthNGConfig.autoComplete.formatter;
  }
  ngOnInit() {
    this.valueChanges = this.registerInputEvent(this.elementRef);
    this.subscription = this.valueChanges
      .subscribe(source => this.onSourceChange(source));
    const factory = this.componentFactoryResolver.resolveComponentFactory(AutoCompletePopupComponent);
    const viewContainerRef = this.appendBody ? this.rebirthNGConfig.rootContainer : this.viewContainerRef;
    // EXCEPTION: Expression has changed after it was checked when append to body;
    setTimeout(() => {
      this.popupRef = viewContainerRef.createComponent(factory, this.viewContainerRef.length, this.injector);
      this.fillPopup();
      this.positionPopup();
      this.popupRef.instance.registerOnChange(item => {
        this.value = item;
        this.writeInputValue(item);
        this.onChange(item);
        this.hidePopup();
        this.selectValueChange.emit(item);
      });
    }, 0);
  }
  @Input()
  set dataSource(dataSource: any[]) {
    if (dataSource) {
      this.arraySource = dataSource;
      this.setupArraySource();
    }
  }
  private setupArraySource() {
    if (this.popupRef) {
      this.popupRef.instance.source = this.arraySource;
    }
    this.onSearch = (term) => {
      return of(
        this.arraySource
          .filter(item => this.formatter(item).toLowerCase().indexOf((term || '').toLowerCase()) !== -1)
      );
    };
  }
  writeValue(obj: any): void {
    this.value = obj || '';
    this.writeInputValue(this.value);
  }
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
    this.renderer.setProperty(this.elementRef.nativeElement, 'disabled', isDisabled);
    if (this.popupRef) {
      this.popupRef.instance.setDisabledState(isDisabled);
    }
  }
  ngOnDestroy() {
    this.unSubscription();
    this.removePopView();
  }
  private removePopView() {
    if (this.popupRef) {
      this.popupRef.destroy();
    }
  }
  @HostListener('blur', [])
  onBlur() {
    this.onTouched();
  }
  @HostListener('keydown.esc', ['$event'])
  onEscKeyup($event) {
    this.hidePopup();
  }
  @HostListener('keydown.Enter', ['$event'])
  onEnterKeyDown($event) {
    if (this.popupRef && this.popupRef.instance.isOpen) {
      $event.preventDefault();
      $event.stopPropagation();
      this.popupRef.instance.selectCurrentItem();
      this.hidePopup();
    }
  }
  @HostListener('keydown.ArrowUp', ['$event'])
  onArrowUpKeyDown($event) {
    if (this.popupRef) {
      $event.preventDefault();
      $event.stopPropagation();
      this.popupRef.instance.prev();
    }
  }
  @HostListener('keydown.ArrowDown', ['$event'])
  onArrowDownKeyDown($event) {
    if (this.popupRef) {
      $event.preventDefault();
      $event.stopPropagation();
      this.popupRef.instance.next();
    }
  }
  @HostListener('document:click', ['$event'])
  onDocumentClick($event: Event) {
    const hostElement = this.elementRef.nativeElement;
    if ($event.target !== hostElement) {
      this.hidePopup();
    }
  }
  onSourceChange(source, term?: string) {
    if (term !== undefined) {
      this.value = term;
    }
    const pop = this.popupRef.instance;
    pop.reset();
    this.fillPopup(source, this.value);
    if ((source && source.length) || this.noResultItemTemplate) {
      pop.show();
      this.positionPopup();
      this.changeDetectorRef.markForCheck();
    }
  }
  private hidePopup() {
    if (this.popupRef) {
      this.popupRef.instance.activeIndex = 0;
      this.popupRef.instance.hide();
    }
  }
  toggle($event?: Event) {
    stopPropagationIfExist($event);
    if (this.popupRef) {
      const pop = this.popupRef.instance;
      if (pop.isOpen) {
        this.hidePopup();
        return;
      }
      this.onSourceChange(this.arraySource);
    }
  }
  positionPopup() {
    const targetElement = this.popupRef.location.nativeElement;
    const hostElement = this.placementElement || this.elementRef.nativeElement;
    const clientRect = this.positionService.positionElements(hostElement, targetElement, this.placement, this.appendBody);
    this.renderer.setStyle(targetElement, 'left', `${clientRect.left}px`);
    this.renderer.setStyle(targetElement, 'top', `${clientRect.top}px`);
  }
  private fillPopup(source?: any, term?: string) {
    const pop = this.popupRef.instance;
    pop.source = source;
    pop.term = term;
    ['formatter', 'itemTemplate', 'noResultItemTemplate', 'cssClass']
      .forEach(key => {
        if (this[key] !== undefined) {
          pop[key] = this[key];
        }
      });
  }
  private writeInputValue(value: any) {
    const formatValue = value || '';
    this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.formatter(formatValue) || '');
  }
  private unSubscription() {
    if (this.subscription) {
      this.subscription.unsubscribe();
      this.subscription = null;
    }
  }
  private onTermChange(term) {
    this.value = term;
    if (this.popupRef) {
      this.popupRef.instance.term = term;
    }
    this.onChange(term);
  }
  private registerInputEvent(elementRef: ElementRef) {
    return fromEvent(elementRef.nativeElement, 'input')
      .pipe(
        map((e: any) => e.target.value),
        tap(term => this.onTouched()),
        tap(term => this.term = term),
        filter(term => !this.disabled && this.onSearch && term.length >= this.minLength),
        debounceTime(this.delay),
        // distinctUntilChanged(),
        tap(term => this.onTermChange(term)),
        switchMap(term => this.onSearch(term, this))
      );
  }
}