1

I have the following code:

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

testApp.directive('myDirective', function () {
    return {
        scope: {
            child: '=child'
        },
        
        controller: function ($scope, $element, $attrs) {
            $scope.$on('childChanged', function (event, newChild) {
                if ($scope.child.id != newChild.id) {
                    alert("The child in the isolated scope is not updated!");
                }
                else {
                    alert("success");                    
                }
            });
        }
    };
});

testApp.controller('myController', function ($scope) {
    $scope.parentObject = {childObject: {id:1}};
    $scope.changeChild = function changeChild()
    {
        var newChild = { id: $scope.parentObject.childObject.id + 1};
        $scope.parentObject.childObject = newChild;
        //$scope.$apply(); //This solves the problem
        $scope.$broadcast('childChanged', newChild);
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="testApp">
    <div data-ng-controller="myController">        
        <div my-directive child="parentObject.childObject"></div>
        <button data-ng-click="changeChild()">Change Child</button>
    </div>
</div>

Explanation:

  • I am creating an isolated scope for the directive, with a two-way binding to an object (parentObject.childObject) of the parent scope.
  • When clicking on the button, the function changeChild is invoked in myController. This function will replace $scope.parentObject.childObject with another child, and will broadcast an event.
  • The listener to this event is in the directive's controller. In the listener, we check whether the child in the isolated scope ($scope.child) is the same one as the one sent in the event (i.e. parent's scope.parentObject.childObject). We see that they are not the same, meaning the child we received in the isolated scope is not yet updated.

Please note that if I use $scope.$apply() before the broadcast, then $scope.child in the directive's controller will be updated, however, it will throw the following exception: "$apply already in progress".

Can someone please help me understand what is happening here? Why did the $scope.$apply help even though it only threw an excpetion? How this can be resolved without calling to $scope.$apply? I would like to know if there is a way to ensure that the bound property in the isolated scope gets updated once it is changed in the parent's scope, without the need to pass it in the event.

Thank you

1 Answers1

0

You can use $timeout to invoke next digest cycle after updating your child.id

Please see demo below

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

testApp.directive('myDirective', function() {
  return {
    scope: {
      child: '=child'
    },

    controller: function($scope, $element, $attrs) {
      $scope.$on('childChanged', function(event, newChild) {

        if ($scope.child.id != newChild.id) {
          alert("The child in the isolated scope is not updated?");
        } else {
          alert("success");
        }
      });

    }
  };
});

testApp.controller('myController', function($scope, $timeout) {
  $scope.parentObject = {
    childObject: {
      id: 1
    }
  };
  $scope.changeChild = function changeChild() {
    var newChild = {
      id: $scope.parentObject.childObject.id + 1
    };
    $scope.parentObject.childObject = newChild;

    $timeout(function() {
      $scope.$broadcast('childChanged', newChild);
    }, 0);
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="testApp">
  <div data-ng-controller="myController">
    <div my-directive child="parentObject.childObject"></div>
    <button data-ng-click="changeChild()">Change Child {{parentObject.childObject}}</button>
  </div>
</div>
sylwester
  • 16,498
  • 1
  • 25
  • 33
  • Thank you, this does solve the problem. I am new to js and AngularJs, can you please explain why this solution works? Is this the best practice for such cases? (Sorry I can't vote up, I am also a stackoverflow newbie...) – user2051749 Dec 02 '14 at 08:18
  • @user2051749 In your sample when you broadcasted newChild angularjs was during digest cycle and newChild wasn't updated bo you need invoke another digest cycle to brodcast new value (http://stackoverflow.com/questions/16066239/defer-angularjs-watch-execution-after-digest-raising-dom-event). I will rather use $watch in you directive instead of $broadcast please see here http://jsbin.com/nibopa/1/edit – sylwester Dec 02 '14 at 09:30
  • Thanks for the explanation. I tried using $watch before submitting the question, but I received "undefined" in both newValue and oldValue. Not sure why, since your example (jsbin.com/nibopa/1/edit) works perfectly and the code seems to be the same as mine... :( After I saw the following post I tried to use $broadcast instead: http://www.benlesh.com/2013/10/title.html – user2051749 Dec 02 '14 at 09:48