Last active
February 18, 2021 10:37
-
-
Save JonWallsten/21e0bede9ee5a965223063c1b3ccce68 to your computer and use it in GitHub Desktop.
Clean AngularJs (1.5) child scopes to fix memory leak when using large data sets in template with transclude
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// We've been working for a full day hunting down why AngularJS 1.5 retains large data sets after a component is removed. | |
// We don't know the exact cause, since there are many parts involved. | |
// But jQuery cache, $scope.$$watchers, $transclude all seems to be different players in this issue. | |
// We provided a super large data (used about 1GB+ of memory) to a sub component in our template. | |
// But when resetting the component to apply new data instead of using the same amount, | |
// the memory doubled until the new component was rendered and Angular eventually cleared the scoped. | |
// Since this ended up using almost 3GB it crashed Chrome for our users. | |
// After testing everything trying to clean our objects it was still an issue since the data was references in so many places by Angular. | |
// Our solution was to thoroughly clean all child scopes for our destroyed component by deleting all watchers, all non-angular props, | |
// and if those props where objects, also delete those sub props in case other references to that object exists (it did). | |
/** | |
* Because of a potential Angular bug child scopes are not cleaned/cleared/destroyed thoroughly so in some cases we need to do this manually. | |
* | |
* @param {ng.IScope} scope | |
* @returns | |
*/ | |
export const cleanChildScopes = (scope: ng.IScope) => { | |
let child = scope.$$childHead; | |
if (!child) { | |
return; | |
} | |
while (child) { | |
// Look through each property on the scope | |
Object.keys(child).forEach(key => { | |
// Delete all non-Angular properties | |
if (key.indexOf('$') === -1) { | |
// If the prop is an object we also delete it's props in case something is referring to the object elsewhere (it is) | |
if (typeof child[key] === 'object') { | |
// Delete each sub key | |
Object.keys(child).forEach(subKey => { | |
delete child[key][subKey]; | |
}); | |
} | |
// Delete key to get rid of it's value | |
delete child[key]; | |
} | |
}); | |
// Clear any listeners since they reference any data on the scope | |
if (child.$$watchers) { | |
child.$$watchers.length = 0; | |
} | |
// Recursively find any child scope and clean those as well | |
if (child.$$childHead) { | |
cleanChildScopes(child); | |
} | |
// Continue with the next sibling | |
let nextSibling = child.$$nextSibling; | |
// Destroy scope in case Angular hasn't | |
child.$destroy(); | |
// Set child to next scope so the while loop continues or break it it's undefined | |
child = nextSibling; | |
// Clear nextSibling in case it retains something | |
nextSibling = undefined; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment