AngularJS directive with $ http creates memory leak - .resolve doesn't seem to work?
I seem to have a problem and I don't think I can see a solution, maybe someone can help ... I have an AngularJS directive that makes a request $http
that returns HTML and I inject it into my view when there is content - I know this is not very convenient and the HTML return should be replaced with JSON and using templates, but I have no control over the content that is returned from the $ http request. The directive takes some arguments / attributes to get the correct content and everything seems to work. Direct, usually in 7 places, in my opinion, one in my navigation which I add to my index using ng-include, three times in my footer which I add to my index.html using ng-include
and then the directive appears insideng-view
I have, the tag can display 0 to 3 times per view. This is a directive in my HTML
<div data-cms-inject data-page-name="homePageContent" data-slot-name="slot1"></div>
Here is the JS ...
.directive('cmsInject', ['$rootScope', '$q', '$http', function ($rootScope, $q, $http) {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attrs) {
var canceller = $q.defer(),
cmsPromise = $http({
url : 'url/to/feed',
timeout: canceller.promise,
method: 'POST',
data: [{
pageName: attrs.pageName,
slotName: attrs.slotName
}]
});
cmsPromise.success(function(data) {
var resp = data[0];
if(resp.replace(/\s{1,}/,'').replace(/\r?\n|\r/g,'') !== 'null') {
element.html(resp);
}
console.log('new request success');
$rootScope.$broadcast('cmsLoaded');
}).error(function(data) {
console.log('new request error', data);
}) ;
$rootScope.$on('$locationChangeStart', function () {
console.log('$locationChangeStart');
canceller.resolve('locationChange');
});
}
};
}])
Now this works fine, but when I quickly move between views in my application, I notice that the application seems to slow down and then shutdown the browser, thus we have a memory leak. At first I thought it had to do with pending $ http calls, but when I tried to solve them using timeout and $ locationChangeStart event, the problem persisted. Then I tried writing to the console to see what was going on ... this is the result I have ...
This is when I load the application, we have 7 instances of the directive, 3 are inside ng-view, 4 are outside in ng-include
7 new request success
Then I switch to a new view without directives
7 $locationChangeStart
Then go back to the original view
7 $locationChangeStart
3 new request success
Once again I switch to a new view without directives
10 $locationChangeStart
Back to the original view
10 $locationChangeStart
3 new request success
Once again I switch to a new view without directives
13 $locationChangeStart
and finally return to the original view
13 $locationChangeStart
3 new request success
Something weird here, since the number of times the $ locationChangeStart event is called increases, more than the pointer appears on the page, ng-include + ng-view can see what I am doing wrong here, some dumping remains in memory / DOM. I think?
source to share
Your directive creates circular links between DOM and js objects. Try to avoid binding events to the rootScope and using services such as $ http inside the link directive. The best solution is to put this logic in the startup function of the module.
Something like that:
.run(['$rootScope', 'myService', function($rootScope, myService){
$rootScope.$on('$locationChangeStart', function () {
myService.doSomethng().then(function(data){
var resp = data[0];
if(resp.replace(/\s{1,}/,'').replace(/\r?\n|\r/g,'') !== 'null') {
$rootScope.$broadcast('cmsLoaded', data);
}
});
});
}])
.directive('cmsInject', ['$rootScope', '$q', '$http', function ($rootScope, $q, $http) {
'use strict';
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$on('cmsLoaded', function(e, args) {
element.html(args);
})
}
}
}])
source to share