javascript – Can't configure $ watch to react to data changes

Question:

There is a directive:

angular.module('analyticsMenu').directive('chartJs', ['$timeout', function($timeout) {
    return {
        restrict: 'E',
        scope: {
            type: '=',
            width: '=',
            height: '=',
            data: '=',
            options: '='
        },
        templateUrl: 'analytics-menu/chart-js/chart-js.template.html',
        link: function($scope, $element, $attr) {

            var newChart = function() {
                var ctx = $element.find('canvas')[0];
                var chart = new Chart(ctx, {
                    type: $scope.type,
                    data: $scope.data,
                    options: $scope.options
                });
            };

            var watchData = function() {
                return JSON.stringify($scope.data);
            };

            $scope.$watch('watchData', updateWhenDataChange, true);

            var updateWhenDataChange = function(newVal, oldVal) {
                console.log(123);
            }

            $timeout(newChart, 0);
        }
    }
}]);

When changing the data attribute (or rather, when changing any of the parameters of the objects that I pass to the directive as a parameter), I need to redraw the graph.

At first I just tried to write:

$scope.$watch('data', updateWhenDataChange, true);

But this resulted in an error:

angular.js: 13920 RangeError: Maximum call stack size exceeded

Answer:

After chatting with @Grundy, he suggested to me that it was necessary to deal with this particular problem (initially I tried to "get around" it). Today I was still able to deal with her.

The crux of the problem was that the Chart.js library wrote its "system" data to objects, the state of which I checked. Because of this, the data change handler function was constantly called.

How did I solve the problem? I am creating a copy of an array with data WITHOUT the "_meta" property, into which the Chart.js data is written. I only check for changes to this data. Here is the code:

'use strict';

angular.module('analyticsMenu').directive('chartJs', ['$timeout', function($timeout) {
    return {
        restrict: 'E',
        scope: {
            type: '=',
            width: '=',
            height: '=',
            data: '=',
            options: '='
        },
        templateUrl: 'analytics-menu/chart-js/chart-js.template.html',
        link: function($scope, $element, $attr) {

            var canvas = $element.find('canvas')[0];
            var chart = null;

            var newChart = function() {
                if (chart === null) {
                    chart = new Chart(canvas, {
                        type: $scope.type,
                        data: $scope.data,
                        options: $scope.options
                    });
                } else {
                    chart.update();
                }
            };

            var without = function without(obj, keys) {
                if (obj === null || typeof obj !== 'object') {
                    return obj;
                }

                var temp = obj.constructor();
                for (var key in obj) {
                    if (key != '_meta') {
                        temp[key] = without(obj[key], keys);
                    }
                }

                return temp;
            };

            var watchComplexNode = function() {
                return without($scope.data, ['_meta']);
            };

            $scope.$watch(watchComplexNode, function () {
                newChart();
            }, true);

        }
    }
}]);

I also recommend reading this article .

P.S. Thanks again for the help @Grundy!

Scroll to Top