Avoid infinite digest loop caused by $watch and $timeout (#10122)

Backports PR #10036

**Commit 1:**
Avoid infinite digest loop in debounce

The custom debounce implementation uses Angular's `$timeout`, which
interacts unfavourably with the unconditional `$watch` handler used in
the `fixed-scroll` directive. It results in an infinite digest being
triggered about every 100ms. To avoid that, this commit uses the
`invokeApply` option of `$timeout` and instead calls `$scope.$apply`
conditionally.

* Original sha: 13c677d613
* Authored by Felix Stürmer <stuermer@weltenwort.de> on 2017-01-24T11:30:41Z
This commit is contained in:
jasper 2017-01-31 13:30:37 -05:00 committed by Felix Stürmer
parent d2af8a157e
commit e0827ef614
3 changed files with 16 additions and 12 deletions

View file

@ -13,7 +13,8 @@ module.service('debounce', ['$timeout', function ($timeout) {
let result;
options = _.defaults(options || {}, {
leading: false,
trailing: true
trailing: true,
invokeApply: true,
});
function debounce() {
@ -32,7 +33,7 @@ module.service('debounce', ['$timeout', function ($timeout) {
if (timeout) {
$timeout.cancel(timeout);
}
timeout = $timeout(later, wait);
timeout = $timeout(later, wait, options.invokeApply);
if (callNow) {
result = func.apply(self, args);

View file

@ -9,15 +9,16 @@ import Promise from 'bluebird';
describe('FixedScroll directive', function () {
let compile;
let flushPendingTasks;
let trash = [];
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.module(function ($provide) {
$provide.service('debounce', () => {
return targetFunction => targetFunction;
});
}));
beforeEach(ngMock.inject(function ($compile, $rootScope) {
beforeEach(ngMock.inject(function ($compile, $rootScope, $timeout) {
flushPendingTasks = function flushPendingTasks() {
$rootScope.$digest();
$timeout.flush();
};
compile = function (ratioY, ratioX) {
if (ratioX == null) ratioX = ratioY;
@ -46,7 +47,7 @@ describe('FixedScroll directive', function () {
}).appendTo($el);
$compile($parent)($rootScope);
$rootScope.$digest();
flushPendingTasks();
return {
$container: $el,
@ -97,7 +98,7 @@ describe('FixedScroll directive', function () {
expect(off.callCount).to.be(0);
els.$container.width(els.$container.prop('scrollWidth'));
els.$container.scope().$digest();
flushPendingTasks();
expect(off.callCount).to.be(2);
checkThisVals('$.fn.off', off);

View file

@ -110,14 +110,16 @@ uiModules
const newWidth = $el.width();
if (scrollWidth !== newScrollWidth || width !== newWidth) {
setup();
$scope.$apply(setup);
scrollWidth = newScrollWidth;
width = newWidth;
}
}
const debouncedCheckWidth = debounce(checkWidth, 100);
const debouncedCheckWidth = debounce(checkWidth, 100, {
invokeApply: false,
});
$scope.$watch(debouncedCheckWidth);
// cleanup when the scope is destroyed