41

I've got an AngularJS attribute directive, and I would like to take an action any time its parent input's value changes. Right now I'm doing it with jQuery:

angular.module("myDirective", [])
.directive("myDirective", function()
{
    return {
        restrict: "A",
        scope:
        {
            myDirective: "=myDirective"
        },
        link: function(scope, element, attrs)
        {
            element.keypress(function()
            {
                // do stuff
            });
        }
    };
});

Is there a way to do this without jQuery? I'm finding the keyPress event isn't doing exactly what I want it to, and while I'm sure I'll come up with a solution, I get a little nervous when I resort to using jQuery in an Angular project.

So what's the Angular way to do this?

Mike Pateras
  • 14,127
  • 29
  • 93
  • 131

3 Answers3

66

There's a great example in the AngularJS docs.

It's very well commented and should get you pointed in the right direction.

A simple example, maybe more so what you're looking for is below:

jsfiddle


HTML

<div ng-app="myDirective" ng-controller="x">
    <input type="text" ng-model="test" my-directive>
</div>

JavaScript

angular.module('myDirective', [])
    .directive('myDirective', function () {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            scope.$watch(attrs.ngModel, function (v) {
                console.log('value changed, new value is: ' + v);
            });
        }
    };
});

function x($scope) {
    $scope.test = 'value here';
}


Edit: Same thing, doesn't require ngModel jsfiddle:

JavaScript

angular.module('myDirective', [])
    .directive('myDirective', function () {
    return {
        restrict: 'A',
        scope: {
            myDirective: '='
        },
        link: function (scope, element, attrs) {
            // set the initial value of the textbox
            element.val(scope.myDirective);
            element.data('old-value', scope.myDirective);

            // detect outside changes and update our input
            scope.$watch('myDirective', function (val) {
                element.val(scope.myDirective);
            });

            // on blur, update the value in scope
            element.bind('propertychange keyup paste', function (blurEvent) {
                if (element.data('old-value') != element.val()) {
                    console.log('value changed, new value is: ' + element.val());
                    scope.$apply(function () {
                        scope.myDirective = element.val();
                        element.data('old-value', element.val());
                    });
                }
            });
        }
    };
});

function x($scope) {
    $scope.test = 'value here';
}
Prashant Pokhriyal
  • 3,125
  • 4
  • 25
  • 30
Langdon
  • 19,288
  • 17
  • 84
  • 106
  • That could work. Any idea how to do it without requiring a model? I'd like to only add the directive. – Mike Pateras Apr 30 '13 at 20:37
  • 3
    If you want the directive to serve as `ngModel`, you can use isolate scope w/ a two-way binding, shown here: http://jsfiddle.net/langdonx/djtQR/ – Langdon Apr 30 '13 at 20:40
  • I'm already using the directive, I'm afraid, but great suggestion. – Mike Pateras Apr 30 '13 at 20:44
  • What you have there is great. If I could do that without requiring the model, it would be perfect. It's not a deal-breaker, but I'd love to not require the model, since I'm not actually using it for anything. Marking this as the answer, since it's pretty much what I want, but if you have any ideas on how I can drop the model, I'd be interested. Thank you very much for your help. – Mike Pateras Apr 30 '13 at 20:51
  • What do mean already using the directive? I was suggesting that you modify your `myDirective` to act like the `ngModelBlur` directive in my example, which that doesn't require ngModel. – Langdon Apr 30 '13 at 20:52
  • @MikePateras Updated the answer, but I gather you're on the right track at this point. – Langdon Apr 30 '13 at 20:58
  • I'm using the value of the directive for something else. – Mike Pateras Apr 30 '13 at 20:59
  • Ahh, I see. You'll have to include another attribute in your HTML then or use ngModel. Either that, or hard code what property on your scope your directive uses (not recommended). – Langdon Apr 30 '13 at 21:02
  • Yeah, the model is fine. Come to think of it, it's pretty unlikely that anyone would be using this directive and not have a model anyway. Thank you again for all of your help. I'm very pleased with where this has ended up. Also, for anyone doing something similar in the future, if you call ngModel.$setViewValue(), don't forget to call ngModel.$render() afterward. – Mike Pateras Apr 30 '13 at 21:24
  • Thank you! I did not know you have to define the directive scope using HTML attributes. Very helpful. – nathancahill Jan 20 '14 at 07:32
  • Great Solution :D – NFRiaCowboy Mar 28 '17 at 00:11
12

Since this must have an input element as a parent, you could just use

<input type="text" ng-model="foo" ng-change="myOnChangeFunction()">

Alternatively, you could use the ngModelController and add a function to $formatters, which executes functions on input change. See http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController

.directive("myDirective", function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attr, ngModel) {
      ngModel.$formatters.push(function(value) {
        // Do stuff here, and return the formatted value.
      });
  };
};
jwg
  • 4,761
  • 2
  • 38
  • 56
Jmr
  • 11,788
  • 4
  • 37
  • 33
0

To watch out the runtime changes in value of a custom directive, use $observe method of attrs object, instead of putting $watch inside a custom directive. Here is the documentation for the same ... $observe docs

Shivam
  • 1,996
  • 1
  • 16
  • 17