2

I have the following markup:

<div ng-controller="DataController as vm">
  <div ng-repeat="name in vm.users track by $index">
    {{name}}
  </div>
  <form name="form" validation="vm.errors">
    <input validator ng-model="vm.name" name="vm.name" placeholder="name" type="text" />
    <a href="#" ng-click="vm.add(vm.name)">Add</a>
  </form>
</div>

I have the following controller:

function DataController($scope) {
  var vm = this;
  vm.name = "Mary";
  vm.users = ["Alice", "Peter"];
  vm.errors = 1;
  vm.add = function(name) {  
    vm.errors++;
    vm.users.push(name);    
  }  
}

Every time I add a user I increase the value of errors.

I need to watch this variable inside a directive so I have:

app.directive("validation", validation);

function validation() {

  var validation = {
    controller: ["$scope", controller],
    restrict: "A",
    scope: {
     validation: "="
    }
  };

  return validation;

  function controller($scope) {
    this.errors = $scope.validation;
  } 
}  

app.directive("validator", validator);

function validator() {

  var validator = {
    link: link,
    replace: false,
    require: "^validation",
    restrict: "A"
  };

  return validator;

  function link(scope, element, attributes, controller) {
    scope.$watch(function() {
     return controller.errors;
   }, function () {
     console.log(controller.errors);
  });
}

The console.log shows the initial value but not new values:
https://jsfiddle.net/qb8o006h/2/

If I change vm.errors to an array, add the values, and watch its length then it works fine: https://jsfiddle.net/nprx63qa/2/

Why is my first example does not work?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481

2 Answers2

1

In both of your examples inside validation directive controller you assign errors property a reference to $scope.validation value.

In the first example the value is numeric and thus immutable - 1 - the reference value cannot be modified. The vm.add modifies property value of the controller instance. The change is then propagated to validation directive $scope.validation but not to the validation directive controller instance $errors property.

In the second example the value is an array and thus mutable - [] - the reference value can be modified. The vm.add does not modify property value of the controller instance. Thus the validation directive controller instance errors property value is the very same Array instance - hence it's length changes.

One way to use a immutable value (as in your first example) is to $watch a controller function as in this example:

function link(scope, element, attributes, controller) {
  scope.$watch(controller.errors, function (newValue) {
    console.log(newValue);
  });
}

Where controller.errors is defined as follows:

function controller($scope) {
  this.errors = function(){ return $scope.validation; };
}

You can find the following answer(s) useful:

Community
  • 1
  • 1
miensol
  • 39,733
  • 7
  • 116
  • 112
  • Is there any way to solve this? I gave the example with a number but if I use an array which does not change length then it does not work either. And that is what I would like to make it work – Miguel Moura Apr 24 '16 at 21:22
1

I update your code, you can access to the property scope.vm.errors which is updated, if you debug the code, you will see that the property controller.errors is not updated (after each digest all the watches are called to re-evaluate them). If you access the property errors from the scope you can add the $scope.$watch and make it work. However I would not recommend to have a $scope.$watch inside a directive. But that's up to you :

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

app.controller("DataController", DataController);

function DataController($scope) {

    var vm = this;
  vm.name = "Mary";
  vm.users = ["Alice", "Peter"];
  vm.errors = 1;
  vm.add = function(name) {  
      vm.errors++;
      vm.users.push(name);    
  }

}

app.directive("validation", validation);

function validation() {

  var validation = {
    controller: ["$scope", controller],
    restrict: "A",
    scope: {
      validation: "="
    }
  };

  return validation;

  function controller($scope) {
    this.errors = $scope.validation;
  } 

}  

app.directive("validator", validator);

function validator() {

  var validator = {
    link: link,
    replace: false,
    require: "^validation",
    restrict: "A"
  };

  return validator;

  function link(scope, element, attributes, controller) {
    scope.$watch(function() {
                return scope.vm.errors
    }, function () {
      console.log(scope.vm.errors);
    });
  }

}

https://jsfiddle.net/kcvqn5kL/

Rodrigo Juarez
  • 873
  • 1
  • 7
  • 17
  • What you would suggest as alternative? Basically, each validator will be responsible to show a error message. I am using validation directive to define which variable holds the errors without repeating it in all validators since it is always the same ... – Miguel Moura Apr 24 '16 at 23:19
  • And I am using a directive as o want to reuse it in my entire aplication. – Miguel Moura Apr 24 '16 at 23:20
  • In fact that was what i had and worked only with length. – Miguel Moura Apr 24 '16 at 23:24
  • Let me modify the code and work out something better – Rodrigo Juarez Apr 24 '16 at 23:25
  • Could you check this code ? http://jsbin.com/jeciraqisi/edit?html,js,output I was thinking in creating an object in the model called errors, which will have all the errors for each one of your elements. I also added the attribute element to your directive validator, so you can access to the property error. You can do the validations in your directive or in the controller. I would recommend you to do all the validations of your elements in the $scope.$watch and create a special controller for each one of your directives, but that's up to you – Rodrigo Juarez Apr 25 '16 at 00:08
  • I like your apuro acho of watching ng model but there is a problem. My validation does not occur on an input value change. This validation occurs after calling an api which responds with the errors which i use to update vm.errors – Miguel Moura Apr 25 '16 at 00:18
  • Okay, then you can just add a watch to the errors attribute of the element passed by parameter, in this case 'name'. When it changes you just check if you have an error and display it – Rodrigo Juarez Apr 25 '16 at 00:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110085/discussion-between-rodrigo-juarez-and-miguel-moura). – Rodrigo Juarez Apr 25 '16 at 00:23