ManifestWebDesign / angular-gridster

An implementation of gridster-like widgets for Angular JS
http://manifestwebdesign.github.io/angular-gridster/
MIT License
964 stars 394 forks source link

Infinite digest loop when re-calculating width of widgets container #169

Open nielsboogaard opened 10 years ago

nielsboogaard commented 10 years ago

When using angular-gridster with Angular 1.3.1, i'm frequently getting this error when the grid is displayed the second time:

Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: function () {\n\t\t\t\t\t\t\tvar _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');\n\t\t\t\t\t\t\treturn _width;\n\t\t\t\t\t\t}; newVal: 135; oldVal: 163"],["fn: function () {\n\t\t\t\t\t\t\tvar _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');\n\t\t\t\t\t\t\treturn _width;\n\t\t\t\t\t\t}; newVal: 114; oldVal: 135"],["fn: function () {\n\t\t\t\t\t\t\tvar _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');\n\t\t\t\t\t\t\treturn _width;\n\t\t\t\t\t\t}; newVal: 100; oldVal: 114"],["fn: function () {\n\t\t\t\t\t\t\tvar _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');\n\t\t\t\t\t\t\treturn _width;\n\t\t\t\t\t\t}; newVal: 93; oldVal: 100"],["fn: function () {\n\t\t\t\t\t\t\tvar _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');\n\t\t\t\t\t\t\treturn _width;\n\t\t\t\t\t\t}; newVal: 86; oldVal: 93"]]

As you can see the width is toggled very often between certain values, which causes the digest loop. The cause is on line 622 of gridster-angular which is watching the width and firing resize on change too often.

scope.$watch(function() {
    var _width = parseInt($elem.css('width')) || $elem.prop('offsetWidth');
    return _width;
}, resize);
TheBosZ commented 10 years ago

Is this a separate issue from #139?

GoodCoding commented 9 years ago

+1 here ... the bigger the grid gets, the faster this error occurs.

nielsboogaard commented 9 years ago

@TheBosZ sorry for the late answer, but i do think this is a separate issue (although there could be some overlap) as this error occurs also when the grid isn't hidden at all.

cbeek commented 9 years ago
stefan-1st commented 9 years ago

Same problem here. In my case the error did not appear if i specify a position (col and row) for every element in the grid. Also it disappeared if i commented out the $getters.row and $getters.col parts.

sandermak commented 9 years ago

:thumbsup:, very annoying problem

danomatic commented 9 years ago

Can someone make a fiddle for this? I haven't seen it in practice or in the demo.

stefan-1st commented 9 years ago

Please find a plunker attached.

http://embed.plnkr.co/BlSNA7M7bzJuTQXGQJk9/preview

When i thought about it i remembered that the issue appeared when i was using an incomplete item map.

danomatic commented 9 years ago

The version of gridster linked in that plunker appears to be very old: http://embed.plnkr.co/BlSNA7M7bzJuTQXGQJk9/gridster.js

That file is under 800 lines and the current version is over 1800 lines (mostly due to integrating the draggable implementation)

What version of gridster are you using?

stefan-1st commented 9 years ago

Sorry for the late reply. I updated the plnkr.

http://embed.plnkr.co/BlSNA7M7bzJuTQXGQJk9/preview

danomatic commented 9 years ago

I'm not seeing the infinite loop. I'm only seeing a blank page and this error in the console:

Resource interpreted as Script but transferred with MIME type text/plain: "https://raw.githubusercontent.com/ManifestWebDesign/angular-gridster/master/src/angular-gridster.js". run.plnkr.co/:1 Refused to execute script from 'https://raw.githubusercontent.com/ManifestWebDesign/angular-gridster/master/src/angular-gridster.js' because its MIME type ('text/plain') is not executable, and strict MIME type checking is enabled. angular.js:36 Uncaught Error: [$injector:modulerr] http://errors.angularjs.org/1.2.15/$injector/modulerr?p0=app&p1=Error%3A%20…20e%20(https%3A%2F%2Fcode.angularjs.org%2F1.2.15%2Fangular.min.js%3A32%3A9)

GoodCoding commented 9 years ago

That's the message in Chrome. In Firefox, the message is infinite loop.

stefan-1st commented 9 years ago

I updated the plunker once again. On my system firefox and chrome do show the infinite loop message.

barretodavid commented 9 years ago

Same problem here, any solution so far?

mtraynham commented 9 years ago

+1

Nothing so far? Seems like a pretty bad issue. Using 11.7, the error message dumps:

Watchers fired in the last 5 iterations: [[{"msg":"fn: function () {\n\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t}","newVal":1886,"oldVal":1871}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t}","newVal":1871,"oldVal":1886}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t}","newVal":1886,"oldVal":1871}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t}","newVal":1871,"oldVal":1886}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t}","newVal":1886,"oldVal":1871}]]

Maybe this part of the code: https://github.com/ManifestWebDesign/angular-gridster/blob/master/src/angular-gridster.js#L910

Most of us, I would assume, are using an ng-repeat to populate our grids.

This bug only only happens if you pre-load the ng-repeat source before appending the grid to the page.

If you first append the grid and then add the items either by a full set of the backing array or adding one-by-one, it works without fault.

The grid is still recalculating it's size and causing large amounts of resize calls on it's children.

marianafranco commented 9 years ago

I'm having a similar error when adding new widgets continuously to the grid, but in this case the problem seems to be in the watch for the widgets position:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations: [["fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 60; oldVal: 58","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"60,0\"; oldVal: \"58,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 60; oldVal: 58","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"60,0\"; oldVal: \"58,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 60; oldVal: 58"],["fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"62,0\"; oldVal: \"60,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 62; oldVal: 60","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"62,0\"; oldVal: \"60,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 62; oldVal: 60","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"62,0\"; oldVal: \"60,0\""],["fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 62; oldVal: 60","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"64,0\"; oldVal: \"62,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 64; oldVal: 62","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"64,0\"; oldVal: \"62,0\""],["fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 64; oldVal: 62","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"64,0\"; oldVal: \"62,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 64; oldVal: 62","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"66,0\"; oldVal: \"64,0\""],["fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 66; oldVal: 64","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"66,0\"; oldVal: \"64,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 66; oldVal: 64","fn: function () {\n\t\t\t\t\t\treturn item.row + ',' + item.col;\n\t\t\t\t\t}; newVal: \"66,0\"; oldVal: \"64,0\"","fn: function (self, locals) {\n return (getter(self, locals));\n }; newVal: 66; oldVal: 64"]]

scope.$watch(function() {
    return item.row + ',' + item.col;
}, positionChanged);

Any idea how to fix this?

marianafranco commented 9 years ago

I fixed the above problem by using angular.copy instead of push a new widget to the widgets list directly:

Before:

        $scope.addWidget = function (newWidget) {
            $scope.widgets.push(newWidget);
        };

After:

        $scope.addWidget = function (newWidget) {
            var widgetsCopy = angular.copy($scope.widgets);
            widgetsCopy.push(newWidget);
            $scope.widgets =  widgetsCopy;
        };
krash84 commented 9 years ago

Most of us, I would assume, are using an ng-repeat to populate our grids.

+1

DHoffm commented 9 years ago

Hi I ran into the same here:

// see https://github.com/sdecima/javascript-detect-element-resize
if (typeof window.addResizeListener === 'function') {
  window.addResizeListener($elem[0], onResize);
} else {
  scope.$watch(function() {
    return $elem[0].offsetWidth || parseInt($elem.css('width'), 10);
  }, resize);
}

this code portion produces:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: function () {\n\t\t\t\t\t\t\t\tconsole.log($elem[0].offsetWidth)\n\t\t\t\t\t\t\t\tconsole.log(parseInt($elem.css('width'), 10))\n\t\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t\t}","newVal":1869,"oldVal":1890}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\t\tconsole.log($elem[0].offsetWidth)\n\t\t\t\t\t\t\t\tconsole.log(parseInt($elem.css('width'), 10))\n\t\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t\t}","newVal":1890,"oldVal":1869}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\t\tconsole.log($elem[0].offsetWidth)\n\t\t\t\t\t\t\t\tconsole.log(parseInt($elem.css('width'), 10))\n\t\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t\t}","newVal":1869,"oldVal":1890}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\t\tconsole.log($elem[0].offsetWidth)\n\t\t\t\t\t\t\t\tconsole.log(parseInt($elem.css('width'), 10))\n\t\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t\t}","newVal":1890,"oldVal":1869}],[{"msg":"fn: function () {\n\t\t\t\t\t\t\t\tconsole.log($elem[0].offsetWidth)\n\t\t\t\t\t\t\t\tconsole.log(parseInt($elem.css('width'), 10))\n\t\t\t\t\t\t\t\treturn $elem[0].offsetWidth || parseInt($elem.css('width'), 10);\n\t\t\t\t\t\t\t}","newVal":1869,"oldVal":1890}]]

This is triggered because offsetwidth and width are different.

I suppose this portion should only fire if the width or height changed.

Update:

This occurs when the grid is large enough to produce a browser scrollbar (not to large or this will not occur and not to small or there is no scrollbar). After a reload the grid decides to be smaller and it somehow fits the window height (no browser scrollbar visible).

jcubic commented 7 years ago

Maybe this will be the fix/workaround:

body {
  overflow-y: scroll;
}