Search code examples
angularjsd3.jswebcola

Why D3 events that deal AngularJS models does not have effect in binding?


Consider the following controller of Angular JS (1.5.8). This project uses also WebCola 3.1.3 and d3 3.4.11.

When I try to change any property of my $scope from inside a d3 callback handler function, the binding takes no effect in rendered HTML.

How can I figure out how to prevent this behavior from d3 and let the 2-way binding flows normally?

<div ng-controller="MainController">
    <h2>{{balls[0].a}}</h2>
    <button ng-click="foo()">Do it!</button>
</div>

angular.module('nua').controller('MainController', ['$scope'], function ($scope) {

    $scope.balls = [];

    $scope.onCircleClickHandler = function (data, index) {

        $scope.balls[index].a = 2;

        // The problem is here!
        // Every function of d3 that change the value
        // of any scope property takes no effect in binding

        // No one of my tries to change the value of any 
        // property of $scope.balls to see the rendered result
        // in the <h2> takes effect.

        // The value of $scope.balls[index].a is really
        // updated to "2", but the values of <h2> remains "1".

        // The calling from D3 seems to prevent something that affects binding.

    };

    $scope.foo = function () {

        $scope.balls[1].d = 5;

        // This works properly.

        // If I call onCircleClickHandler and THEN call foo,
        // then the binding takes effect and <h2> has now the 
        // "2" as innerHTML

    };

    $scope.init = function () {

        // var mycola = cola.d3adaptor() ...

        // var svg = d3.select('id') ...

        // var nodes = svg.selectAll('circle') ...

        nodes.on('click', function (data, index) {

            this.onCircleClickHandler(data, index);

        }.bind($scope))

        $scope.balls = [
            {a:1, b:2, c:3},
            {d:4, e:5, f:6}
        ];

    };


});

Solution

  • The reason is when you updating values from d3 event, angular never comes to know that the scope data has changed and so it needs to call the digest cycle.

    So it should be this way:

     $scope.onCircleClickHandler = function (data, index) {
            //this will notify angular that the scope value has changed.
            $scope.$apply(function () {
                $scope.balls[index].a = 2;
            });
    
        };