简体   繁体   中英

AngularJS Directive Scope variables undefined

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!

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/

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM