7

I am having a hard time figuring out how to make sure I maintain 2-way data binding when I create directives. Here is what I am working with and the fiddle:

http://jsfiddle.net/dkrotts/ksb3j/6/

HTML:

<textarea my-maxlength="20" ng-model="bar"></textarea>
<h1>{{bar}}</h1>

Directive:

myApp.directive('myMaxlength', ['$compile', function($compile) {
return {
    restrict: 'A',
    scope: {},
    link: function (scope, element, attrs, controller) {
        element = $(element);

        var counterElement = $compile(angular.element('<span>Characters remaining: {{charsRemaining}}</span>'))(scope);

        element.after(counterElement);

        scope.charsRemaining = parseInt(attrs.myMaxlength);

        scope.onEdit = function() {
            var maxLength = parseInt(attrs.myMaxlength),
                currentLength = parseInt(element.val().length);

            if (currentLength >= maxLength) {
                element.val(element.val().substr(0, maxLength));
                scope.charsRemaining = 0;
            } else {
                scope.charsRemaining = maxLength - currentLength;
            }

            scope.$apply(scope.charsRemaining);
        }

        element.keyup(scope.onEdit)
            .keydown(scope.onEdit)
            .focus(scope.onEdit)
            .live('input paste', scope.onEdit);
        element.on('ngChange', scope.onEdit);
    }
}
}]);

As I type in the textarea, the model is not updating like I need it to. What am I doing wrong?

Dustin
  • 8,217
  • 11
  • 33
  • 44

3 Answers3

12

Well, there are two reasons why the two-way databinding doesn't work. First, you need to create a bi-directional binding between a local scope property and the parent scope property:

scope: { bar: "=ngModel" }

otherwise you're creating an isolated scope (see http://docs.angularjs.org/guide/directive).

The other reason is that you have to replace the after insert instruction with an append from the parent (because you are only bootstrapping angular on dom.ready):

element.parent().append(counterElement);

Update jsfiddle: http://jsfiddle.net/andregoncalves/ksb3j/9/

Andre Goncalves
  • 3,840
  • 2
  • 21
  • 15
  • How would I do this if I didn't know the name of the model ahead of time? Also, why did you need to use element.parent().append() instead of element.after()? – Dustin Dec 03 '12 at 21:04
  • 1
    If you don't know the model name ahead of time you better not use declarative ng-model syntax, and get the model from the controller scope (set scope to false in the directive). Regarding the element.parent().append(), it's because you're only bootstrapping angular on dom.ready and when the directive runs the bootstrap hasn't occured yet, remove it from the dom.ready handler and you can use after(). – Andre Goncalves Dec 04 '12 at 11:38
  • Ok I have an updated fiddle here: http://jsfiddle.net/dkrotts/WWCzQ/3/. The problem I am having now is my scope variable charsRemaining needs to be isolated to the directive so that I can use this directive multiple times on the same page. As it is now (with scope: false), charsRemaining is shared across the parent scope, and I need that specific variable to be isolated to the directive only. – Dustin Dec 04 '12 at 14:25
5

Do you really need a custom directive? AngularJS ships with a ngMaxlength directive that combined with ngChange might help you.

For example, if you have the following HTML

<body ng-controller="foo">
    <form name="myForm">
        <textarea name = "mytextarea"
                  ng-maxlength="20" 
                  ng-change="change()"
                  ng-model="bar"></textarea>
         <span class="error" ng-show="myForm.mytextarea.$error.maxlength">
             Too long!
         </span>
        <span> {{left}} </span>
        <h1>{{bar}}</h1>
    </form>                 
</body>

Then you just need this into your controller

function foo($scope) {  
    $scope.change = function(){
        if($scope.bar){
           $scope.left = 20 - $scope.bar.length;            
        }else{
           $scope.left = "";
        }      
    };
    $scope.bar = 'Hello';
    $scope.change();
}

Let angular handle the dom as much as you can.

Here's the updated jsfiddle: http://jsfiddle.net/jaimem/ksb3j/7/

jaime
  • 41,961
  • 10
  • 82
  • 52
  • I'd like to use the built-in angular feature. But why doesn't this stop additional characters from being typed in the textarea? That's the behavior I was expecting from maxlength. – broc.seib Feb 06 '13 at 04:22
0

i'm not entirely sure, but i think that what you whant is a filter, take a look to this url, maybe it is a great approach to rethink about your problem.

http://docs.angularjs.org/api/ng.filter:limitTo

Flavio CF Oliveira
  • 5,235
  • 13
  • 40
  • 63
  • I don't think that is what I'm having difficulties with here. My problem is pretty straight-forward. On the textarea, I have a model assigned. The 2-way binding on that model is breaking after I enable the myMaxlength directive. – Dustin Dec 03 '12 at 18:06