Skip to content

Instantly share code, notes, and snippets.

@JonWallsten
Last active February 18, 2021 10:37
Show Gist options
  • Save JonWallsten/21e0bede9ee5a965223063c1b3ccce68 to your computer and use it in GitHub Desktop.
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
// 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