36

I'm trying to do a simple textarea with "so many chars remaining" along with validation. when I use ng-maxlength to validate my form, it resets my charcount as soon as the length hits the max length. Here's the plunkr Any workarounds?

  <body ng-controller="MainCtrl">
    <div ng-form="noteForm">
      <textarea ng-maxlength="15" ng-model="result"></textarea>
      <p>{{15 - result.length}} chars remaining</p>
      <button ng-disabled="!noteForm.$valid">Submit</button>
    </div>
  </body>
Satpal
  • 132,252
  • 13
  • 159
  • 168
Riz
  • 6,486
  • 19
  • 66
  • 106
  • Funny thing, but for me (using Angular 1.4.7 + TypeScript 1.8.7) usage of HTML attribute "maxLength" had same effect as for mentioned here "ng-maxLength": after setting some big value (bigger than defined limit) directly into model, value in model becomes undefined. From my point of view, this is Angular bug (just like [user3232182](http://stackoverflow.com/users/3232182/user3232182) mentioned above). Probably even not linked to ng-maxLength directve, but to the whole binding mechanism. – bkg Apr 20 '16 at 07:13

7 Answers7

56

When your textarea exceeds 15 characters, result becomes undefined — that's just how the ng-min/maxlength directives work. I think you'll have to write your own directive. Here is a directive that will block input after 15 characters:

<textarea my-maxlength="15" ng-model="result"></textarea>
app.directive('myMaxlength', function() {
  return {
    require: 'ngModel',
    link: function (scope, element, attrs, ngModelCtrl) {
      var maxlength = Number(attrs.myMaxlength);
      function fromUser(text) {
          if (text.length > maxlength) {
            var transformedInput = text.substring(0, maxlength);
            ngModelCtrl.$setViewValue(transformedInput);
            ngModelCtrl.$render();
            return transformedInput;
          } 
          return text;
      }
      ngModelCtrl.$parsers.push(fromUser);
    }
  }; 
});

fiddle


Update: to allow more than 15 characters, but disable the submit button when the count exceeds 15:

link: function (scope, element, attrs, ngModelCtrl) {
  var maxlength = Number(attrs.myMaxlength);
  function fromUser(text) {
      ngModelCtrl.$setValidity('unique', text.length <= maxlength);
      return text;
  }
  ngModelCtrl.$parsers.push(fromUser);
}

fiddle

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • 1
    thanks very much. I'd've liked it to go beyond and show negative like twitter, but this is good enough :) – Riz Jun 13 '13 at 17:05
  • brilliant! this should be default ng-maxlength behaviour. Maybe you can push this to angular ;) – Riz Jun 13 '13 at 17:30
  • @MarkRajcok This solution was working correctly for me when I manually edited an input, but it did not catch errors with initial values. Adding `ngModelCtrl.$formatters.push(fromUser)`, as the default Angular validators do, fixed it. – Eric Simonton Apr 12 '14 at 00:19
  • 1
    Don't use myMaxlength as it can call itself again $setViewValue if you paste a text big enough. It's better to use validation. I run into "Maximum call stack size exceeded" error because of that. – Nicolae Namolovan Aug 14 '14 at 15:17
  • Mark Rajcok your directive doesn't work with input type="number" – Murilo Jan 07 '16 at 18:18
  • 1
    You can achieve this behaviour using `ng-maxlength` if you add `ng-model-options="{allowInvalid: true}"` . Source: http://stackoverflow.com/a/29132765/592062 – k0nG Dec 19 '16 at 12:46
  • I just found this code and tried it in a project using _AngularJS v1.4.8_ and when typing the text in the input this works great, but when the user do copy paste from a text from the web page into the textfield, what @NicolaeNamolovan describes happens :\ – lealceldeiro Mar 09 '17 at 14:39
52

Alternatively you can just add the standard html maxlength attribute alongside the ng-maxlength.

<form name="myForm">
    <textarea name="myTextarea" ng-maxlength="15" maxlength="15" ng-model="result"></textarea>
    <span class="error" ng-show="myForm.myTextarea.$error.maxlength">
         Reached limit!
     </span>
</form>

This will cut-off at "15" characters, as maxlength has always done, and additionally the ng-maxlength lets you show custom message upon reaching the limit.

Click for Plunker Example

Anil Singh
  • 4,173
  • 4
  • 41
  • 47
Thalis K.
  • 7,363
  • 6
  • 39
  • 54
17

If you add a name attribute to the textarea then a new property with the value is created in the scope of the form, which you can use to get the length for your character counter.

<body ng-controller="MainCtrl">
  <div ng-form="noteForm">
    <textarea ng-maxlength="15" name="noteItem" ng-model="result"></textarea>
    <p>{{15 - noteForm.noteItem.$viewValue.length}} chars remaining</p>
    <button ng-disabled="!noteForm.$valid">Submit</button>
  </div>
</body>

Updated your plnkr

mwardm
  • 1,953
  • 15
  • 19
jlooooo
  • 379
  • 3
  • 5
16

As the doc says, the $validate function set model to undefined when validity changes to invalid.

But, we can still prevent this behavior simply by adding allowInvalid: true to the ng-model-options.

So, just modify your code like:

<body ng-controller="MainCtrl">
    <div ng-form="noteForm">
        <textarea ng-maxlength="15" ng-model="result" 
            ng-model-options="{ allowInvalid: true }"></textarea>
        <p>{{15 - result.length}} chars remaining</p>
        <button ng-disabled="!noteForm.$valid">Submit</button>
    </div>
</body>
Buh Buh
  • 7,443
  • 1
  • 34
  • 61
Lutz
  • 161
  • 1
  • 2
3

I think this should be logged as a bug on angular. It is not supposed to clear your model.I have a directive that links a dropdown select to a textbox and as soon as you insert a word that makes it go over the max-length then it clears my model and textbox.It is supposed to be a validator not clear your model when it thinks the model is invalid .

3

An alternative that I'm using is to retain the same behaviour as the ng-maxlength validator but to feed the length back via an extra attribute 'actual-length'.

app.directive('newMaxlength', function () {
        return {
            require: 'ngModel',
            scope: {
                maxlength: '=newMaxlength',
                actualLength: '='
            },
            link: function (scope, elem, attr, ngModelCtrl) {

                function validate(ctrl, validatorName, validity, value) {
                    ctrl.$setValidity(validatorName, validity);
                    return validity ? value : undefined;
                }

                var maxlength = parseInt(scope.maxlength, 10);
                var maxLengthValidator = function (value) {
                    scope.actualLength = value ? value.length : 0;
                    return validate(ngModelCtrl, 'maxlength', ngModelCtrl.$isEmpty(value) || value.length <= maxlength, value);
                };

                ngModelCtrl.$parsers.push(maxLengthValidator);
                ngModelCtrl.$formatters.push(maxLengthValidator);
            }
        };
    });

Here is the element with the extra attribute:

<textarea name="myTextarea" new-maxlength="100" actual-length="actualLength" ng-model="text"></textarea>
PenFold
  • 706
  • 3
  • 20
0

I have a better solution I think: to set the maxlength if the ng-maxlength is present.

This way you get both features at the same time: angular validations + text cut off

.directive("textarea", function () {
    return {
        restrict: "E",
        link: function (scope, elem, attrs) {
            if (angular.isDefined(attrs["ngMaxlength"])) {
                attrs.$set("maxlength", attrs["ngMaxlength"]);
            }
        }
    };
})
Kat Lim Ruiz
  • 2,425
  • 2
  • 26
  • 32
  • indeed. but you could use polyfills for that case I guess. Example found: http://stackoverflow.com/questions/1125482/how-to-impose-maxlength-on-textarea-in-html-using-javascript/1125521#1125521, although I have not tried to make it work within the directive, but I guess it is not that hard. THANKS FOR THE TIP! – Kat Lim Ruiz Jul 17 '14 at 20:11