Search code examples
javascriptangularjsangular-directive

Change parent controller model through directive $watch using controllerAs syntax


I'm new to controllerAs syntax of angular and just trying to understand how it works with directive. I've created one directive for password validation. I want to make some flag true based on conditions and those will be used in parent template for displaying error messages. I'm not getting how can I achieve this!

JSFiddle

VIEW

<div ng-app="myapp">
    <fieldset ng-controller="PersonCtrl as person">
        <input name="emailID" type="text" ng-model="person.first" >
        <input name="pass" type="password" ng-model="person.pass" password-validator>
        <p ng-show="person.showMsg">Password validation message here.</p>
    </fieldset>
</div>

Directive

myapp.directive('passwordValidator',function() {
        return {
        controller : PasswordCtrl,
      controllerAs : 'dvm',
      bindToController : true,
      require : ['ngModel','passwordValidator'],
      link : function(scope,ele,attrs,ctrls) {
        var person = ctrls[1];
        var ngModelCtrl = ctrls[0];

        scope.$watch(function() {
                    return ngModelCtrl.$modelValue;
        },function(newVal) {
          if(newVal!='') {
            person.showMsg = true;
          } else {
            person.showMsg = false;
          }
          console.log(person.showMsg);
        });
      }
    }

    function PasswordCtrl() {

    }
});

Specially I want to understand why and how below watch is working fine!

// Why this below is also working, can anyone explain what's going behind!! 
scope.$watch('person.pass',function(newVal) {
    console.log("Watch fires");
});

This is just for learning purpose so please explain how controllerAs and bindToController works!


Solution

  • I know this was not part of your question, I will get to it, but using directive 'ng-controller' is an anti-pattern. If if are interested why I can explain in a separate post but in short it makes code much harder to follow.

    Now, to get to the heart of your question.

    From reading the Angular documentation for bindToController it would appear that if you are not also creating an isolated scope, i.e. scope: true or scope: {} it does not do anything.

    Personally I have never used it before and does not seem particularly useful.

    Using ng-controller is in essence adding a property to the current scope with that controller object.

    So:

    <fieldset ng-controller="PersonCtrl as person">
    

    Is effectively saying, (in a contrived way):

    $scope.person = new PersonCtrl();
    

    Your directive passwordValidator which is using the controllerAs syntax within it is basically doing:

    $scope.dvm= new PasswordCtrl();
    

    In this case you effectively have a scope object that looks like:

    $scope = {
        person = new PersonCtrl(),
        dvm: new PasswordCtrl()
    }
    

    Your person controller and dvm controller are sibling objects. Within your passwordValidator directive you are requiring in its controller, which is the dvm object. Using that dvm object are you setting person.showMsg which is the same as doing:

    $scope.dvm.person.showMsg = <value>
    

    The dvm object does not have a way to access the person object, as they are siblings, on the $scope. So you need to use the $scope itself to access the person object. You would need to do:

    $scope.person.showMsg = <value>
    

    Although this assumes that person exists on the scope, which is a dangerous assumption.