import { ElementRef , HostListener , Directive , AfterViewInit , Optional , OnInit , OnDestroy , NgZone , Output , EventEmitter } from '@angular/core' ; import { NgModel } from '@angular/forms' ; import 'rxjs/add/observable/fromEvent' ; import 'rxjs/add/operator/debounceTime' ; import { Observable } from 'rxjs/Observable' ; import { Subscription } from 'rxjs/Subscription' ; @ Directive ( { selector : '[fz-elastic]' } ) export class ElasticDirective implements OnInit , OnDestroy , AfterViewInit { private modelSub : Subscription ; private textareaEl : HTMLTextAreaElement ; @ Output ( 'on-resize' ) onResize = new EventEmitter ( ) ; constructor ( private element , private ngZone , @ Optional ( ) private model ) { } ngOnInit ( ) { if ( ! this . model ) { return ; } this . modelSub = this . model . valueChanges . debounceTime ( 100 ) . subscribe ( ( ) => this . adjust ( ) ) ; } ngOnDestroy ( ) { if ( this . modelSub ) { this . modelSub . unsubscribe ( ) ; } } ngAfterViewInit ( ) { if ( this . isTextarea ( this . element . nativeElement ) ) { this . setupTextarea ( this . element . nativeElement ) ; return ; } const children = Array . from ( this . element . nativeElement . children ) as HTMLElement [ ] ; const textareaEl = children . find ( el => this . isTextarea ( el ) ) ; if ( textareaEl ) { this . setupTextarea ( textareaEl as HTMLTextAreaElement ) ; return ; } throw new Error ( 'The `fz-elastic` attribute directive must be used on a `textarea` or an element that contains a `textarea`.' ) ; } @ HostListener ( 'input' ) onInput ( ) { this . adjust ( ) ; } private isTextarea ( el ) { return el . tagName === 'TEXTAREA' ; } private setupTextarea ( textareaEl ) { this . textareaEl = textareaEl ; const style = this . textareaEl . style ; style . overflow = 'hidden' ; style . resize = 'none' ; this . ngZone . runOutsideAngular ( ( ) => { Observable . fromEvent ( window , 'resize' ) . debounceTime ( 100 ) . subscribe ( ( ) => this . adjust ( ) ) ; } ) ; this . adjust ( ) ; } private adjust ( ) { if ( ! this . textareaEl ) { return ; } const previousHeight = parseInt ( this . textareaEl . style . height , 10 ) ; const newHeight = this . textareaEl . scrollHeight ; this . textareaEl . style . height = 'auto' ; this . textareaEl . style . height = `${ newHeight }px` ; if ( previousHeight !== newHeight ) { this . onResize . emit ( newHeight ) ; } } }