Skip to content
This repository has been archived by the owner on Mar 3, 2023. It is now read-only.

Ionic Elastic Textarea

Miroslav Smukov edited this page Sep 10, 2016 · 8 revisions

Elastic Textarea

At the moment that I'm writing this, ionic2 framework's component called ion-textarea doesn't have a pretty basic functionality to grow in height automatically as new lines are added to it. In this section we are going to write our own wrapper component around the ion-textarea that will give it this functionality.

Why elastic?

There's on old post in ionic version 1 forum that's called Ionic Elastichat - Chat Demo w/ Auto Resizing Textarea that served as an inspiration to my custom elastic-textarea component.

I'd also like to mention Bastian, who helped to push me in the right direction with his ionic 1 directive in typescript-version that he posted as an answer to my forum question.

elastic-textarea implementation

Below you can see the full implementation of my elastic-textarea component. I added it to app/pages/components/elasticTextarea.js in my project. Checkout the code and then we'll go through interesting bits.

Source Code

import {Component, ViewChild} from '@angular/core';

@Component({
  selector: 'elastic-textarea',
  inputs: ['placeholder', 'lineHeight'],
  template:
  `
  <ion-textarea #ionTxtArea
    placeholder='{{placeholder}}'
    [(ngModel)]="content"
    (ngModelChange)='onChange($event)'></ion-textarea>
  `,
  queries: {
    ionTxtArea: new ViewChild('ionTxtArea')
  }
})
export class ElasticTextarea {
  constructor() {
    this.content = "";
    this.lineHeight = "22px";
  }

  ngAfterViewInit(){
    this.txtArea = this.ionTxtArea._elementRef.nativeElement.children[0];
    this.txtArea.style.height = this.lineHeight + "px";
  }

  onChange(newValue){
    this.txtArea.style.height = this.lineHeight + "px";
    this.txtArea.style.height =  this.txtArea.scrollHeight + "px";
  }

  clearInput(){
    this.content = "";
    this.txtArea.style.height = this.lineHeight + "px";
  }

  setFocus(){
    this.ionTxtArea.setFocus()
  }
}

I wanted to reuse the ion-textarea functionality as much as possible, and only extend it by making it resize automatically. Because of this I decided to wrap the ion-textarea inside my directive.

I'm using a two way binding for my ngModel, and I'm binding it to content property. This basically means that if I change the text of my ion-textarea, the content property will get updated, and vice versa.

I've also attached an ngModelChanged event listener, that gets triggered every time the model of ion-textarea changes, which basically means any time its text is updated. The $event that I'm passing to the onChange(..) method will actually be the new text value inside the ion-textarea, however, I won't be using this information at the moment.

The nested textarea

Before I continue, I want to point out that with this.ionTxtArea._elementRef.nativeElement.children[0] I'm obtaining the native HTML textarea input element that will get generated inside the ion-textarea once the angular resolves our code. ion-textarea is basically only extending the textarea with additional styling and event handling, similarly to what I'm doing by wrapping the ion-textarea in my elastic-textarea. This is a dirty way to obtain this textarea element, but I don't know any alternative approach.

Moving on, the onChange(..) method, which gets triggered on ngModelChanged event, will handle the automatic resizing of the nested textarea, which will also resize the ion-textarea. The logic behind the automatic resize is quite simple. Firstly, I'm setting the height of textarea to the height of the single line of text. At this point, the framework will automatically calculate the required scrollHeight that is required to scroll through all the lines of text inside the textarea element. Then, I'm simply using this calculated scrollHeight and I'm just setting the height of the element to this height.

That's it, those are the key parts of my elastic-textarea component. It can be used in code like this:

<elastic-textarea placeholder="Type to compose" lineHeight="22"></elastic-textarea>

Inputs and Outputs

You've probably noticed the inputs property that I'm setting in my component's decorator. These are Angular2 properties and there are two types of them: @Input and @Output. In ionic2 framework, they are marked simply as inputs, and outputs inside the decorator, and then the names of these bindable properties are added to the array.

  • @Input - Declares a data-bound input property. Angular automatically updates data-bound properties during change detection.
  • @Output - Declares an event-bound output property. When an output property emits an event, an event handler attached to that event is invoked.

I'm using the inputs to mark the placeholder and lineHeight as the input properties, and then I'm using them inside my component. You can see how I'm using these properties with my component: <elastic-textarea placeholder="Type to compose" lineHeight="22"></elastic-textarea>

The aliasing of the properties is also available. For example, if I declared the inputs as: inputs: ['msg: message']. This would mean that inside my component I would use the variable called msg, but outside I would bind to it using the full name message. For example: <my-directive message='hey!'></my-directive>.

Conclusion

It was fun creating this custom component, but it was also a disappointment that ionic2 didn't support this feature out of the box, like Android Native did.

References

Commits

Clone this wiki locally