Skip to content

Commit

Permalink
fix(autosize): not updating when window is resized (#8619)
Browse files Browse the repository at this point in the history
Fixes the autosize textarea not recalculating its height when the viewport size has changed.

Fixes #8610.
  • Loading branch information
crisbeto authored and mmalerba committed Dec 8, 2017
1 parent fb9ea53 commit b8664b8
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
27 changes: 13 additions & 14 deletions src/lib/input/autosize.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {Component, ViewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {ComponentFixture, TestBed, async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
import {ComponentFixture, TestBed, async, fakeAsync, flush, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {MatInputModule} from './index';
import {MatTextareaAutosize} from './autosize';
import {MatStepperModule} from '@angular/material/stepper';
import {MatTabsModule} from '@angular/material/tabs';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {dispatchFakeEvent} from '@angular/cdk/testing';


describe('MatTextareaAutosize', () => {
Expand Down Expand Up @@ -185,14 +186,14 @@ describe('MatTextareaAutosize', () => {
autosize = fixtureWithPlaceholder.debugElement.query(
By.directive(MatTextareaAutosize)).injector.get<MatTextareaAutosize>(MatTextareaAutosize);

triggerTextareaResize();
autosize.resizeToFitContent(true);

const heightWithLongPlaceholder = textarea.clientHeight;

fixtureWithPlaceholder.componentInstance.placeholder = 'Short';
fixtureWithPlaceholder.detectChanges();

triggerTextareaResize();
autosize.resizeToFitContent(true);

expect(textarea.clientHeight).toBe(heightWithLongPlaceholder,
'Expected the textarea height to be the same with a long placeholder.');
Expand All @@ -213,7 +214,7 @@ describe('MatTextareaAutosize', () => {
Some late visitor entreating entrance at my chamber door;—
This it is and nothing more.” `;
fixtureWithForms.detectChanges();
flushMicrotasks();
flush();
fixtureWithForms.detectChanges();

expect(textarea.clientHeight)
Expand All @@ -229,7 +230,7 @@ describe('MatTextareaAutosize', () => {
`;

fixture.detectChanges();
flushMicrotasks();
flush();
fixture.detectChanges();

expect(textarea.clientHeight)
Expand All @@ -250,16 +251,14 @@ describe('MatTextareaAutosize', () => {
expect(textarea.getBoundingClientRect().height).toBeGreaterThan(1);
});

/** Triggers a textarea resize to fit the content. */
function triggerTextareaResize() {
// To be able to trigger a new calculation of the height with a short placeholder, the
// textarea value needs to be changed.
textarea.value = '1';
autosize.resizeToFitContent();
it('should trigger a resize when the window is resized', fakeAsync(() => {
spyOn(autosize, 'resizeToFitContent');

textarea.value = '';
autosize.resizeToFitContent();
}
dispatchFakeEvent(window, 'resize');
tick(16);

expect(autosize.resizeToFitContent).toHaveBeenCalled();
}));
});

// Styles to reset padding and border to make measurement comparisons easier.
Expand Down
47 changes: 41 additions & 6 deletions src/lib/input/autosize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, ElementRef, Input, AfterViewInit, DoCheck} from '@angular/core';
import {
Directive,
ElementRef,
Input,
AfterViewInit,
DoCheck,
OnDestroy,
NgZone,
} from '@angular/core';
import {Platform} from '@angular/cdk/platform';
import {fromEvent} from 'rxjs/observable/fromEvent';
import {auditTime} from 'rxjs/operators/auditTime';
import {takeUntil} from 'rxjs/operators/takeUntil';
import {Subject} from 'rxjs/Subject';


/**
Expand All @@ -23,9 +35,10 @@ import {Platform} from '@angular/cdk/platform';
'rows': '1',
},
})
export class MatTextareaAutosize implements AfterViewInit, DoCheck {
export class MatTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
/** Keep track of the previous textarea value to avoid resizing when the value hasn't changed. */
private _previousValue: string;
private _destroyed = new Subject<void>();

private _minRows: number;
private _maxRows: number;
Expand All @@ -49,7 +62,12 @@ export class MatTextareaAutosize implements AfterViewInit, DoCheck {
/** Cached height of a textarea with a single row. */
private _cachedLineHeight: number;

constructor(private _elementRef: ElementRef, private _platform: Platform) {}
constructor(
private _elementRef: ElementRef,
private _platform: Platform,
private _ngZone?: NgZone) {}

// TODO(crisbeto): make the `_ngZone` a required param in the next major version.

/** Sets the minimum height of the textarea as determined by minRows. */
_setMinHeight(): void {
Expand All @@ -74,9 +92,22 @@ export class MatTextareaAutosize implements AfterViewInit, DoCheck {
ngAfterViewInit() {
if (this._platform.isBrowser) {
this.resizeToFitContent();

if (this._ngZone) {
this._ngZone.runOutsideAngular(() => {
fromEvent(window, 'resize')
.pipe(auditTime(16), takeUntil(this._destroyed))
.subscribe(() => this.resizeToFitContent(true));
});
}
}
}

ngOnDestroy() {
this._destroyed.next();
this._destroyed.complete();
}

/** Sets a style property on the textarea element. */
private _setTextareaStyle(property: string, value: string): void {
const textarea = this._elementRef.nativeElement as HTMLTextAreaElement;
Expand Down Expand Up @@ -134,8 +165,12 @@ export class MatTextareaAutosize implements AfterViewInit, DoCheck {
}
}

/** Resize the textarea to fit its content. */
resizeToFitContent() {
/**
* Resize the textarea to fit its content.
* @param force Whether to force a height recalculation. By default the height will be
* recalculated only if the value changed since the last call.
*/
resizeToFitContent(force = false) {
this._cacheTextareaLineHeight();

// If we haven't determined the line-height yet, we know we're still hidden and there's no point
Expand All @@ -148,7 +183,7 @@ export class MatTextareaAutosize implements AfterViewInit, DoCheck {
const value = textarea.value;

// Only resize of the value changed since these calculations can be expensive.
if (value === this._previousValue) {
if (value === this._previousValue && !force) {
return;
}

Expand Down

0 comments on commit b8664b8

Please # to comment.