Skip to content

Instantly share code, notes, and snippets.

@enapupe
Last active February 8, 2021 11:26
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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 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