import { AfterViewInit , Directive , DoCheck , ElementRef , Input , NgZone , OnDestroy , Renderer2 } from '@angular/core' ; import { fromEvent , Subject } from 'rxjs' ; import { auditTime , takeUntil } from 'rxjs/operators' ; @ Directive ( { selector : 'textarea[gdAutosize]' , } ) export class AutosizeDirective implements AfterViewInit , DoCheck , OnDestroy { private previousValue : string ; private readonly destroyed = new Subject < void > ( ) ; private cachedLineHeight : number ; constructor ( private elementRef , private renderer , private ngZone ) { } private _minRows : number ; get minRows ( ) : number { return this . _minRows ; } @ Input ( ) set minRows ( value ) { this . _minRows = value ; this . setMinHeight ( ) ; } private _maxRows : number ; @ Input ( ) get maxRows ( ) : number { return this . _maxRows ; } set maxRows ( value ) { this . _maxRows = value ; this . setMaxHeight ( ) ; } ngAfterViewInit ( ) { this . resizeToFitContent ( ) ; this . ngZone . runOutsideAngular ( ( ) => { fromEvent ( window , 'resize' ) . pipe ( auditTime ( 16 ) , takeUntil ( this . destroyed ) ) . subscribe ( ( ) => this . resizeToFitContent ( true ) ) ; } ) ; } ngDoCheck ( ) { this . resizeToFitContent ( ) ; } ngOnDestroy ( ) { this . destroyed . next ( ) ; this . destroyed . complete ( ) ; } private setMinHeight ( ) { const minHeight = this . minRows && this . cachedLineHeight ? `${ this . minRows * this . cachedLineHeight }px` : null ; if ( minHeight ) { this . renderer . setStyle ( this . elementRef . nativeElement , 'minHeight' , minHeight ) ; } } private setMaxHeight ( ) { const maxHeight = this . maxRows && this . cachedLineHeight ? `${ this . maxRows * this . cachedLineHeight }px` : null ; if ( maxHeight ) { this . renderer . setStyle ( this . elementRef . nativeElement , 'maxHeight' , maxHeight ) ; } } private cacheTextareaLintHeight ( ) { if ( this . cachedLineHeight ) { return ; } const textarea = this . elementRef . nativeElement as HTMLTextAreaElement ; const textareaClone = textarea . cloneNode ( false ) as HTMLTextAreaElement ; textareaClone . rows = 1 ; textareaClone . style . position = 'absolute' ; textareaClone . style . visibility = 'hidden' ; textareaClone . style . border = 'none' ; textareaClone . style . padding = '0' ; textareaClone . style . height = '' ; textareaClone . style . minHeight = '' ; textareaClone . style . maxHeight = '' ; textarea . parentNode . appendChild ( textareaClone ) ; this . cachedLineHeight = textareaClone . clientHeight ; textarea . parentNode . removeChild ( textareaClone ) ; this . setMinHeight ( ) ; this . setMaxHeight ( ) ; } private resizeToFitContent ( force = false ) { this . cacheTextareaLintHeight ( ) ; if ( ! this . cachedLineHeight ) { return ; } const textarea = this . elementRef . nativeElement as HTMLTextAreaElement ; const value = textarea . value ; if ( value === this . previousValue && ! force ) { return ; } const placeholderText = textarea . placeholder ; textarea . style . height = 'auto' ; textarea . style . overflow = 'hidden' ; textarea . placeholder = '' ; textarea . style . height = `${ textarea . scrollHeight + 2 }px` ; textarea . style . overflow = '' ; textarea . placeholder = placeholderText ; this . previousValue = value ; } }