1

Here is the relevant JSFiddle

https://jsfiddle.net/9Ltyru6a/3/

In the fiddle, I have set up a controller and a directive that I want to use to call a callback whenever a value is change. I know that Angular has an ng-change directive, but I want something more akin to the standard onchange event (that gets triggered once when the field is blurred).

Controller:

var Controllers;
    (function (Controllers) {
    var MyCtrl = (function () {
        function MyCtrl($scope) {
            $scope.vm = this;
        }

        MyCtrl.prototype.callback = function (newValue) {
            alert(newValue);
        };

        return MyCtrl;
    })();
    Controllers.MyCtrl = MyCtrl;
})(Controllers || (Controllers = {}));

Directive:

var Directives;
(function (Directives) {
    function OnChange() {
        var directive = {};
        directive.restrict = "A";
        directive.scope = {
            onchange: '&'
        };
        directive.link = function (scope, elm) {
            scope.$watch('onChange', function (nVal) {
                elm.val(nVal);
            });
            elm.bind('blur', function () {
                var currentValue = elm.val();
                scope.$apply(function () {
                    scope.onchange({ newValue: currentValue });
                });
            });
        };
        return directive;
    }
    Directives.OnChange = OnChange;
})(Directives || (Directives = {}));

HTML:

<body ng-app="app" style="overflow: hidden;">
    <div ng-controller="MyCtrl">
        <button ng-click="vm.callback('Works')">Test</button>
        <input onchange="vm.callback(newValue)"></input>
    </div>
</body>

The button works, so I can safely say (I think) that the controller is fine. However, whenever I change the value of the input field and unfocus, I get a "vm is undefined" error.

Thanks for the help!

15
  • 2
    why don't you just use ng-blur? docs.angularjs.org/api/ng/directive/ngBlur#! Commented Jul 9, 2015 at 18:21
  • That's definitely an option, but I eventually want to encapsulate the logic of checking whether or not the value actually changed within this directive. Commented Jul 9, 2015 at 18:22
  • I'm not sure what you mean? Angular can handle dirty checking for you as well...... Commented Jul 9, 2015 at 18:24
  • 1
    it looks like you have an answer that might fix the directive you are creating, but I still feel like you're re-inventing the wheel here. Commented Jul 9, 2015 at 18:39
  • 1
    aha, now that I can answer :) Commented Jul 9, 2015 at 19:19

2 Answers 2

2

First of all, use proper controllerAs notation, not $scope.vm = this;:

ng-controller="MyCtrl as vm"

Then don't mix custom directive with native onchange event handler - this is the reason why you get undefined error. Name your directive something like onChange and use on-change attribute instead.

Correct code would look like:

var app = angular.module("app", []);

var Directives;
(function (Directives) {
    function OnChange() {
        var directive = {};
        directive.restrict = "A";
        directive.scope = {
            onChange: '&'
        };
        directive.link = function (scope, elm) {
            elm.bind('blur', function () {
                var currentValue = elm.val();
                scope.$apply(function () {
                    scope.onChange({
                        newValue: currentValue
                    });
                });
            });
        };
        return directive;
    }
    Directives.onChange = OnChange;
})(Directives || (Directives = {}));

app.directive("onChange", Directives.onChange);


var Controllers;
(function (Controllers) {
    var MyCtrl = (function () {
        function MyCtrl($scope) {

        }

        MyCtrl.prototype.callback = function (newValue) {
            alert(newValue);
        };

        return MyCtrl;
    })();
    Controllers.MyCtrl = MyCtrl;
})(Controllers || (Controllers = {}));

app.controller("MyCtrl", ["$scope", function ($scope) {
    return new Controllers.MyCtrl($scope);
}]);

Demo: https://jsfiddle.net/9Ltyru6a/5/

Sign up to request clarification or add additional context in comments.

Comments

1

If the intent of your code is to only update your controller value on blur, rather than update it on every keypress, angular has ngModelOptions for this use. For example:

<input type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" />

you could even provide a debounce, or a button to clear the value....

<form name="userForm">
  <input type="text" name="userName" 
         ng-model="user.name" ng-model-options="{ debounce: 1000 }" />

  <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button>
</form>

In these cases, if you were to supply an ng-change, it would only trigger on the blur event, or after the debounce.

You can also write directives that directly leverage the $validators or $asyncValidators from the ngModelController

here's an example from the Angular Developer Guide:

app.directive('username', function($q, $timeout) {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
    var usernames = ['Jim', 'John', 'Jill', 'Jackie'];

      ctrl.$asyncValidators.username = function(modelValue, viewValue) {

        if (ctrl.$isEmpty(modelValue)) {
          // consider empty model valid
          return $q.when();
        }

        var def = $q.defer();

        $timeout(function() {
          // Mock a delayed response
          if (usernames.indexOf(modelValue) === -1) {
            // The username is available
            def.resolve();
          } else {
            def.reject();
          }

        }, 2000);

        return def.promise;
      };
    }
  };
});

and the HTML:

<div>
    Username:
    <input type="text" ng-model="name" name="name" username />{{name}}<br />
    <span ng-show="form.name.$pending.username">Checking if this name is available...</span>
    <span ng-show="form.name.$error.username">This username is already taken!</span>
</div>

You could of course add the ng-model-options to ensure that this triggers only once.

3 Comments

This is great! I had no idea most of this even existed. Technically, the other answer actually answered the question, but I'm actually going to end up using this. Thanks so much, and sorry if I wasn't very clear in the comments.
@Kith no need to apologize; Angular is a massive framework with many hidden features, it's not always easy to express exactly what you need. I could tell well enough though that what you thought was the only way wasn't really ideal, and wanted to take the time to help you get to the other side of that.
This was so much easier hah. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.