Skip to content

Instantly share code, notes, and snippets.

@enapupe
Last active February 8, 2021 11:26
Show Gist options
  • Save enapupe/2a59589168f33ca405d0 to your computer and use it in GitHub Desktop.
Save enapupe/2a59589168f33ca405d0 to your computer and use it in GitHub Desktop.
Auto Grow/Shrink textarea directive for AngularJS (credits to this gist https://gist.github.com/thomseddon/4703968, specially @huyttq)
angular.module("myApp").directive("autoGrow", function(){
return function(scope, element, attr){
var update = function(){
element.css("height", "auto");
var height = element[0].scrollHeight;
if(height > 0){
element.css("height", height + "px");
}
};
scope.$watch(attr.ngModel, function(){
update();
});
attr.$set("ngTrim", "false");
};
});
@enapupe
Copy link
Author

enapupe commented Aug 28, 2014

ngTrim is set to false because otherwise angularjs trims newlines and spaces. That makes watch not being fired when you enter new lines/spaces..

@enapupe
Copy link
Author

enapupe commented Oct 17, 2014

Issues

  • If the textarea is invisible it won't come with the automagic height because the scrollheight was zero on modelchange, unless the same model that populates de textarea makes it visible.

@zhouzi
Copy link

zhouzi commented Dec 30, 2014

Wow, working even better than the shadow hack trick while being terribly simple. Do you actually use it on a large project? Seems to be well supported so I think I'll go with it! May I suggest the following:

return function (scope, element, attrs) {
    function update () {
        element.css("height", "auto");

        var height = element[0].scrollHeight;
        if (height > 0) element.css("height", height + "px");
    }

    scope.$watch(attrs.ngModel, update);
    attrs.$set("ngTrim", "false");
}

scope.$watch(attr.ngModel, function () { update(); }); is a bit redondant as update is already a function. On the other part, but this is probably a matter of gut, I'm not a big fan of defining function as var.

Also, it'd be cool to include a note about the recommended css for those not coming from thomseddon's gist.

@zhouzi
Copy link

zhouzi commented Dec 30, 2014

Note: textarea won't resize if the ng-maxlength is exceeded, which is probably a good thing. Actually the $watcher on ngModel doesn't fire anymore.

@kimardenmiller
Copy link

Love it. My only wish is that the textarea did not start out defaulting to two lines. Any idea how to get it to start with a single line, then expand by a line at a time as it does? I guess scrollHeight is doing that?

Here it is in CoffeeScript, injected into a larger project:

autoGrow = ->
  (scope, element, attrs) ->
    update = ->
      element.css 'height', 'auto'
      height = element[0].scrollHeight
      element.css 'height', height + 'px'  if height > 0
    scope.$watch attrs.ngModel, update
    attrs.$set 'ngTrim', 'false'

# Register
App.Directives.directive 'autoGrow', autoGrow

@kimardenmiller
Copy link

Fixed for my purposes, though still curious if there might be a more elegant way to somehow control the children element sizes instead:

autoGrow = ->
  (scope, element, attrs) ->
    update = ->
      if element[0].scrollWidth > element.outerWidth isBreaking = true else isBreaking = false
      element.css 'height', 'auto' if isBreaking
      height = element[0].scrollHeight
      element.css 'height', height + 'px'  if height > 0
    scope.$watch attrs.ngModel, update
    attrs.$set 'ngTrim', 'false'

# Register
App.Directives.directive 'autoGrow', autoGrow

@muratsplat
Copy link

@kimardenmiller can I use your code in my project ?

@muratsplat
Copy link

if I can convert your coffee code to native js code 😃

@muratsplat
Copy link

I have written my version with little changes

angular.module('mayApp')

  .directive('autogrow', function () {
    return {

      restrict: 'A',
      link: function postLink(scope, element, attrs) {
          // hidding the scroll of textarea
          element.css('overflow', 'hidden');

          var update = function(){

              element.css("height", "auto");

              var height = element[0].scrollHeight;

              if(height > 0){

                  element.css("height", height + "px");
                }

          };

          scope.$watch(attrs.ngModel, function(){

              update();
          });

          attrs.$set("ngTrim", "false");
      }
    };
  });

on view layer

<textarea autogrow class="form-control" rows="10" placeholder="Enter ..." ng-model="content.body" ng-maxlength="16777215" ng-minlength="10" required  name="content"></textarea>

@laurensnl
Copy link

Thanks guys! Works perfect! There does however seem to be an issue with Chrome (Version 46.0.2490.80 (64-bit) on Mac). Setting the element height twice every keystroke (first to auto, then to the desired height) causes a slight but annoying delay while typing. Any suggestions to solve that?

@pacificsharma
Copy link

Thanks a lot.

@dcolley
Copy link

dcolley commented May 5, 2017

thank you! It works for me on Chrome 57 and IOS 9.

@JavyMB
Copy link

JavyMB commented May 9, 2017

Thank you ! works for me too.

@suisun2015
Copy link

@muratsplat
Excellent Work! Thanks!

@bettysteger
Copy link

In case someone uses debounced model updates - this will just work with a timeout, which is not very nice, so i changed the code a bit to also listen to the input event:

angular.module('myApp').directive('autogrow', function () {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      // hide scroll of textarea
      element.css('overflow', 'hidden');

      var autogrow = function () {
        element.css('height', 'auto');

        var height = element[0].scrollHeight;

        if(height > 0){
          element.css('height', height + 'px');
        }
      };

      // using 'input' event on element, because of debounced model update
      element.on('input', autogrow);

      // need this too, for initialize
      scope.$watch(attrs.ngModel, autogrow);
    }
  };
});

@suisun2015
Copy link

suisun2015 commented Aug 22, 2018

@lpsBetty
Your trick of binding input event is great! Thanks.
PS:
I was implementing autogrowing textarea in ionic 1 modal template while I should consider a delay effect of modal opening. So I added a modal.shown watcher.

angular.module('myApp').directive('autogrow', function () {
  return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      // hide scroll of textarea
      element.css('overflow', 'hidden');

      var autogrow = function () {
        element.css('height', 'auto');

        var height = element[0].scrollHeight;

        if(height > 0){
          element.css('height', height + 'px');
        }
      };

      // using 'input' event on element, because of debounced model update
      element.on('input', autogrow);

      // need this too, for initialize
      scope.$watch(attrs.ngModel, autogrow);

      /////////////////////////////////////////////////
      // apply after modal is shown <- ADD HERE
      scope.$on('modal.shown', autogrow);
    }
  };
});

Hope this might help someone.

@bettysteger
Copy link

in case textarea is not yet visible:

if(!element.is(':visible')) {
  scope.$watch(function () { return element.is(':visible'); }, function (visible) {
    if(visible) {
      autogrow();
      scope.$applyAsync();
    }
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment