How to auto resize the textarea to fit the content?

For anyone still looking for an answer to this in almost 2021, it's covered in the official Angular Material docs here. Directly manipulating the DOM via nativeElement is an anti-pattern.

<mat-form-field [style.fontSize]="fontSize.value">
  <mat-label>Autosize textarea</mat-label>
  <textarea matInput
            cdkTextareaAutosize
            #autosize="cdkTextareaAutosize"
            cdkAutosizeMinRows="1"
            cdkAutosizeMaxRows="5"></textarea>
</mat-form-field>

What you are trying to achieve is a very old trick. I have used it myself but trying a different approach.

It makes more sense why the text area is jumpy coz every keystroke you were making the height = 0 to calculate scroll height so that you can assign a new height.

I calculated the fontSize or lineHeight and calculated number of lines and the initial height to adjust based on that. So on every keystroke you are just assigning height w/o making the text area height=0

textareaProps = null;
getHeight(element) {
  const lines = element.value.split(/\r\n|\r|\n/).length;
  if(!this.textareaProps) {
    const autoStyle = getComputedStyle(element);
    const lineHeight = parseInt(autoStyle.lineHeight);
    const adjust = parseInt(autoStyle.height) - lineHeight;
    this.textareaProps = {adjust, lineHeight}
  }
  const { adjust, lineHeight } = this.textareaProps;
  const height = lines * lineHeight + adjust;
  return height + 'px';
}

You now need to call this method to get height and pass the textarea element as arg.

element.style.cssText = 'height:' + getHeight(element) ;

Edit 2

Sadly the above solution will only work if there are line breaks by user. When you enter a huge line text area wraps it but it doesn't increase the height. So intruducing a proxy html element which will have the text as same as text area value and will provide a height that we can assign to our text area.

textareaProps = null;
getHeight(element) {
  if(!this.textareaProps) {
    const proxy = document.createElement('div');
    const {padding, width, fontSize, height, lineHeight} = getComputedStyle(element);
    const css = [
      'position:absolute',
      'visibility: hidden',
      'pointer-events:none',
      `width: ${width}`,
      `padding:${padding}`,
      `min-height: ${height}`,
      `font-size:${fontSize}`,
      `line-height:${lineHeight}`,
    ].join(';');
    proxy.style.cssText=css;
    this.textareaProps = {
      proxy: document.body.appendChild(proxy), 
      adjust: (parseInt(fontSize))
    };
  }
  const { proxy, adjust} = this.textareaProps;
  proxy.innerText = element.value + '.';
  return (proxy.offsetHeight + adjust) + 'px';
}

Updated StackBlitz https://stackblitz.com/edit/next-line-view-child-ssnp4q


<textarea rows="15" id="code" disabled placeholder="Description"></textarea>

rows="15" to set min heigth to 15 rows

el = document.getElementById("code");
el.style.height = "auto";
// code to populate textarea
el.style.height = (5 + el.scrollHeight) + "px";

this helped me to set textarea to height as per content populated in it.


addEventListener here is redundant since valueChanges already notifies you when the field changes. Instead, update the height using the ViewChild reference myDiv.

this.myForm.valueChanges.subscribe(value => {
    this.myDiv.nativeElement.style.height = 'auto';
    this.myDiv.nativeElement.style.height = `${this.myDiv.nativeElement.scrollHeight}px`;
});

Then add overflow: hidden to your css so the scrollbar doesn't show.

textarea {
    resize: horizontal;
    overflow: hidden;
}

You can keep the resize: horizontal; but it is no longer required since the textarea will resize automatically anyway.

Here is a working example on StackBlitz.