Creating a dynamically resizing textarea and wrapping it in an Angular 1 directive

Angular textarea resize directive

Hello! For some reason I have very little content about JavaScript and Angular JS on my blog, even though most of the code I currently write is exactly in this language and framework. Today, I’ll be trying to fix this and show you how to built and Angular directive that makes an HTML textarea dynamically change its height (increase or decrease) based on the size of content that’s being typed in.

Before we get started, please take a look at the demo of the final result. As you can see, when the content starts to overflow, the input size increases so that everything is still visible. This works in both directions and applies when you type, paste long text from clipboard or even resize the window, making the textarea narrower. Let’s see how it works, this is the code of the textarea-resize directive:

angular.module('myAwesomeTextarea', [])
.directive('textareaResizer', function () {
    return {
        restrict: 'A',
        link: function (scope, element) {
            // Check if the directive is used on a 
            // textarea and stop otherwise
            if (element[0].tagName != 'TEXTAREA') {
                return;
            }
            // Subscribe to window resize event 
            // and element events.
            window.addEventListener('resize', changeHeight, false);
            element.on('keyup', changeHeight);
            element.on('change', changeHeight);
            element.on('$destroy', function () {
                // Remove event listener from global 
                // window object when the element is destroyed
                window.removeEventListener('resize', changeHeight, false);
            });
            // Initial height of an empty element
            const baseHeight = element[0].clientHeight;
            // Maximum height to be enlarged to. 
            // I set it x5 but it's up to you.
            const maxHeight = baseHeight * 5;
            // Indicates the textarea content's length 
            // after the last change. Used to determine if 
            // the content was added or removed.
            var lastValueLength = element[0].value.length;
 
            function changeHeight() {
                var elHeight = element[0].clientHeight,
                    scrollHeight = element[0].scrollHeight,
                    valueLength = element[0].value.length;
                if (scrollHeight > elHeight) {
                    // Do not make the textarea higher than 
                    // the chosen max height, we don't want 
                    // it to be too high.
                    if (scrollHeight >= maxHeight) {
                        element[0].style.height = maxHeight + 'px';
                    }
                    // Set the exactly the height that we need.
                    else {
                        element[0].style.height = scrollHeight + 'px';
                    }
                }
                // The content was not increased - either 
                // removed or remains the same.
                // This is where current and last content 
                // length comparison comes in handy.
                else if (valueLength < lastValueLength) {
                    // The content was removed. We reset t
                    // he element set to basic and run this 
                    // function recursively, so that it starts 
                    // over and sets right new height.
                    element[0].style.height = baseHeight + 'px';
                    lastValueLength = element[0].value.length;
                    changeHeight();
                }
                else {
                    // The length did not change or became 
                    // a little bit bigger (and not affected 
                    // the row count). Simply update the last value length.
                    lastValueLength = element[0].value.length;
                }
            }
        }
    };
});

As you know, any Angular directive building function should return an object literal with defined certian properties. There are plenty of them, but you directive is very simple, so we’ll use only two:

restrict: 'A', - restricts the directive usage to element’s attribute (A) only. There are also another options like element (“E”), class name (“C”) and even comment (“M”), but they are a way less common.

link: function() - accepts a function that runs when the directive is being initiated. This function has a reference to the element as one of its arguments (a JQLite wrapper), and that’s why it’s typically used to modify the DOM. Since the number of JQLite operations is limited (in comparison with jQuery), we’ll be also using the raw HTMLElement reference, which is available under element[0].

So what are we going to do in the link function? Let’s look from top to bottom.

1. Check if the element is actually a textarea and interrupt otherwise. This will prevent possible JavaScript errors if someone applies our directive on a wrong HTML element.

2. Subscribe to DOM events when we possibly need to update the textarea size. These will be: keyup (runs instantly after you typed something and released a key), change (runs with a delay after you type something, but will be useful in case of pasting or using autofill), and window’s resize event to get us covered if a user would like to change the window’s width. When the element is destroyed, we need to remove the resize listener to avoid memory leaks. The listeners attached to the element itself will be removed without manual instructions.

3. Define the following properties:

  • Base height – the initial (and minimal) height of an empty textarea at the moment of initialization.
  • Max height – the maximum height of the textarea, after reaching which scrolling will be available. I made it 5 times bigger than initial value, and you can change it as you see appropriate.
  • Last value length – a helper-property to determine whether text was added or removed after change and act correspondingly. At the beginning, it would be equal to 0.

4. Define the changeHeight function that will be triggered on any of the listed events (keyup, change or window.onresize). The function performs several checks:

  • If the scrolling height is bigger than element height – when this condition is true, there’s a text overflow, and we have to increase input height unless reached the maximum height.
  • If the previous condition is not satisfied, we’ll check if the number of text is reduced (this make cause blank lines at the bottom), and if it is – we’ll reset the height of the element (set it as its base height) and run the function recursively, so it could start over and determine a right new height.
  • Otherwise, the content length remains the same or insignificantly increased, which would not cause text overflow. Just updating the value of the content length.

That’s it. Basically, the directive in example is ready to use, so if you don’t feel like dig into details, feel free to grasp it as is. Hope, it’s been helpful, and thank you for reading!