Search code examples
angularjsangularjs-directiveangularjs-scope

Why reference to directive's attribute is broken when object under attribute is changed and ControllerAs is used?


I have a directive which takes an object as parameter. This object can be changed to another one by parent directive. When I use $scope, then object is updated in the directive. When I use ControllerAs syntax then reference is broken and change is not reflected. I assumed that in both cases $scope.obj and vm.obj are references so they should behave in the same way. What is breaking the reference here and why?

<body ng-app="app" ng-controller="ctrl">
    <div>Original object: {{object}}</div>
    <button ng-click="changeObject()">Change object!</button>
    <my-object obj="object"></my-object>
    <my-object-vm obj="object"></my-object-vm>

    <script type="text/javascript">
      var app = angular.module("app", []);

      app.controller("ctrl", function($scope) {
        $scope.object = {
          p1: "A"
        };

        $scope.changeObject = function() {
          $scope.object = {
            p2: "B"
          }
        }
      });

      app.directive("myObject", function() {
        return {
          template: "<div>Obj from scope: {{obj}}</div>",
          scope: {
            obj: "="
          }
        }
      });

      app.directive("myObjectVm", function() {
        return {
          template: "<div>Obj from vm: {{vm.obj}}</div>",
          scope: {
            obj: "="
          },
          controller: function($scope) {
            var vm = this;
            vm.obj = $scope.obj;
          },
          controllerAs: "vm"
        }
      });
    </script>
</body>

Plunker: working example


Solution

  • The link is broken because you're copying the reference from the scope to the controller only once, when link() is invoked. So later, when a new value is assigned to $scope.obj, the controller still refers to the old one.

    You need to use bindToColtroller in addition to controllerAs:

    When an isolate scope is used for a directive (see above), bindToController: true will allow a component to have its properties bound to the controller, rather than to scope.