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?

+3


source to share


3 answers


The increase in the number of event handlers can be explained:

$rootScope.$on('$locationChangeStart'

      



You attach an event handler to $rootScope

and it certainly doesn't disappear when the directive's lifecycle ends. The event handler must be bound to scope

.

+4


source


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);                    
               })
            }
        }
     }])

      

+1


source


Move your HTTP calls to some service and use that service in your directive. Listen for the directive's $ destory event and clean up if you can.

+1


source







All Articles