Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit a6181c9

Browse files
SQUASH ME
Added in the `onChangesTtl` setting and `infchng` error for preventing overflow
1 parent f60d0c4 commit a6181c9

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@ngdoc error
2+
@name $compile:infchng
3+
@fullName Unstable `$onChanges` hooks
4+
@description
5+
6+
This error occurs when the application's model becomes unstable because some `$onChanges` hooks are causing updates which then trigger
7+
further calls to `$onChanges` that can never complete.
8+
Angular detects this situation and prevents an infinite loop from causing the browser to become unresponsive.
9+
10+
For example, the situation can occur by setting up a `$onChanges()` hook which triggers an event on the component, which subsequently
11+
triggers the component's bound inputs to be updated:
12+
13+
```html
14+
<c1 prop="a" on-change="a = -a"></c1>
15+
```
16+
17+
```js
18+
Controller1.$onChanges = function() {
19+
this.onChange();
20+
};
21+
```
22+
23+
The maximum number of allowed iterations of the `$onChanges` hooks is controlled via TTL setting which can be configured via
24+
{@link ng.$compileProvider#onChangesTtl `$compileProvider.onChangesTtl`}.

src/ng/compile.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,6 +1221,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
12211221
return debugInfoEnabled;
12221222
};
12231223

1224+
1225+
var TTL = 10;
1226+
/**
1227+
* @ngdoc method
1228+
* @name $compileProvider#onChangesTtl
1229+
* @description
1230+
*
1231+
* Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
1232+
* assuming that the model is unstable.
1233+
*
1234+
* The current default is 10 iterations.
1235+
*
1236+
* In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
1237+
* in several iterations of calls to these hooks. However if an application needs more than the default 10
1238+
* iterations to stabilize then you should investigate what is causing the model to continuously change during
1239+
* the `$onChanges` hook execution.
1240+
*
1241+
* Increasing the TTL could have performance implications, so you should not change it without proper justification.
1242+
*
1243+
* @param {number} limit The number of `$onChanges` hook iterations.
1244+
*/
1245+
this.onChangesTtl = function(value) {
1246+
if (arguments.length) {
1247+
TTL = value;
1248+
}
1249+
return TTL;
1250+
};
1251+
12241252
this.$get = [
12251253
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
12261254
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
@@ -1230,12 +1258,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
12301258
var SIMPLE_ATTR_NAME = /^\w/;
12311259
var specialAttrHolder = document.createElement('div');
12321260

1261+
1262+
1263+
var onChangesTtl;
12331264
// The onChanges hooks should all be run together in a single digest
12341265
// When changes occur, the call to trigger their hooks will be added to this queue
12351266
var onChangesQueue;
12361267

12371268
// This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
12381269
function flushOnChangesQueue() {
1270+
if (!(onChangesTtl--)) {
1271+
throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL);
1272+
}
12391273
// We must run this hook in an apply since the $$postDigest runs outside apply
12401274
$rootScope.$apply(function() {
12411275
for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) {
@@ -3156,6 +3190,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
31563190
if (isFunction(destination.$onChanges) && currentValue !== previousValue) {
31573191
// If we have not already scheduled the top level onChangesQueue handler then do so now
31583192
if (!onChangesQueue) {
3193+
// Initialize the TTL tracker for tracking stack overflow
3194+
if (isUndefined(onChangesTtl)) { onChangesTtl = TTL; }
31593195
scope.$$postDigest(flushOnChangesQueue);
31603196
onChangesQueue = [];
31613197
}

test/ng/compileSpec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3854,13 +3854,13 @@ describe('$compile', function() {
38543854
inject(function($compile, $rootScope) {
38553855

38563856
// Setup the directive with bindings that will keep updating the bound value forever
3857-
element = $compile('<c1 prop="a" on-change="a = -a"></outer>')($rootScope);
3857+
element = $compile('<c1 prop="a" on-change="a = -a"></c1>')($rootScope);
38583858

38593859
// Update val to trigger the unstable onChanges, which will result in a
38603860
// Range error - maximum call stack size exceeded
38613861
expect(function() {
38623862
$rootScope.$apply('a = 42');
3863-
}).toThrowError();
3863+
}).toThrowMinErr('$compile', 'infchng');
38643864
});
38653865
});
38663866
});

0 commit comments

Comments
 (0)