Search code examples
javascriptangularjsangularjs-scopeprototypal-inheritance

Angular controller inheritance scoping issues


The goal here is to have two different directives that are technically siblings share functionality. I will either use one or the other, never one inside the other.

However, the second directive will have all the capability of the first with some small additions. Because of this, I would like the functionality to inherit from the "Parent" directive to the "Child".

I'm achieving this by re-using the same directive definition object from the Parent on the Child, with the exception of the controller/template fields being changed.

This was all working well up until I hit the watchers from my ParentDirCtrl. For some reason the watcher seems to be set up correctly watching mydir.obj1 and yet somehow inside the watcher callback function mydir.obj1 becomes undefined.

I'm assuming something about _.extend/$controller is changing how the $scopes work so mydir.obj1 isn't defined in the ParentDirCtrl, but I'm not sure why that would be the case.

Plunk

angular.module('plunker', [])

// lodash
.constant('_', _)

.controller('MainCtrl', function($scope, $timeout) {
  $scope.obj = {
    name: 'John',
    age: 30,
  };
})


.controller('ParentDirCtrl', function($scope) {
  var mydir = this;

  mydir.doStuffInParent = function() {
    alert('executed from the parent directive');
  }

  $scope.$watch('mydir.obj1', function() {
    // ====================================
    //              ERROR
    // Why is 'mydir.obj1' undefined when
    // occupation is set?  
    // ====================================
    mydir.obj1.occupation = 'Meteorologist';
  });
})


.directive('parentDirective', parentDirective)


.directive('childDirective', function() {
  // borrow the directive definition object from the parent directive
  var parentDDO = parentDirective();

  // uodate the template and controller for our new directive
  parentDDO.template = [
    '<div>', 
      '<p ng-click="mydir.doStuffInParent()">{{mydir.obj1.name}}</p>',
      '<p ng-click="mydir.doStuffInChild()">{{mydir.obj1.age}}</p>',
    '</div>'
    ].join('');

  parentDDO.controller = function($scope, $controller, _) {
      // extend 'this' with the Parent's controller
      var mydir = _.extend(this, $controller('ParentDirCtrl', { $scope: $scope }));

      mydir.doStuffInChild = function() {
        alert("executed from the child directive");
      };
  }; 

  return parentDDO;
});


// this will be moved to the top during declaration hoisting
function parentDirective() {
  return {
    restrict:'E',
    scope: {},
    bindToController: {
      obj1: '=',
    },
    template: '<div>{{mydir.obj1}}</div>',
    controller: 'ParentDirCtrl',
    controllerAs: 'mydir',
  };
}

Solution

  • obj1 is populated on the child controller instance - that's why mydir.obj1 is undefined in the parent watcher. You can access obj1 directly via scope or by using the reference passed into the watcher:

    $scope.$watch('mydir.obj1', function(val) {
        $scope.mydir.obj1.occupation = 'Meteorologist';
        // or
        val.occupation = 'Meteorologis';
    });
    

    There is no scope inheritance here - both controllers operate on the same scope. Controller-AS syntax is what confuses you - I'd get rid of it to make things clearer.