AngularJS directive $destroy

74,005

The i18n example you provided would work if you only ever used it once.

I don't think you should be doing the event binding inside the compile function. You can do it inside the link function instead:

angular.directive('i18n', ['$rootScope', 'LocaleService', function($rootScope, LocaleService) {
  return {
    restrict: 'EAC',
    link: function(scope, element, attrs) {
      var cleanup;
      var originalText = element.text();
      element.text(LocaleService.getTranslation(originalText, attrs.locale));
      cleanup = $rootScope.$on('locale-changed', function(locale) {
        element.text(LocaleService.getTranslation(originalText, attrs.locale || locale));
      });
      scope.$on('$destroy', function() {
        console.log("destroy");
        cleanup();
      });
    }
  };
}]);

Alternatively, you could bind the event on the child scope itself, and use $broadcast on the $rootScope to trigger it. That way the event will automatically be garbage collected when the scope is destroyed:

angular.directive('i18n', ['$rootScope', 'LocaleService', function($rootScope, LocaleService) {
  return {
    restrict: 'EAC',
    link: function(scope, element, attrs) {
      var originalText = element.text();
      setElText();
      function setElText(locale){
        element.text(LocaleService.getTranslation(originalText, attrs.locale || locale));
      }
      scope.$on('locale-changed', setElText);
    }
  };
}]);

$rootScope.$broadcast('locale-change', 'en-AU');
Share:
74,005
Pwnna
Author by

Pwnna

Updated on September 02, 2020

Comments

  • Pwnna
    Pwnna almost 4 years

    I have an angular app setup with ng-view. In one view, in addition to the view itself, there is also a component inside that view that is dynamically loaded. This component is a directive that essentially compiles the contents so the contents can be further hooked with other directives (which it is). The content inside that component is compiled using $compile(element.contents())(scope);.

    As an example:

    <ng-view>
      <viewer doc="getDocument()">
      </viewer>
    </ng-view>
    
    angular.directive('viewer', ['$compile', '$anchorScroll', function($compile, $anchorScroll) {
      return function(scope, element, attrs) {
        scope.$watch(
          function(scope) {
            var doc = scope.$eval(attrs.doc);
            if (!doc)
              return ""
            return doc.html;
          },
          function(value) {
            element.html(value);
            $compile(element.contents())(scope);
          }
        );
      };
    }]);
    

    My problem is when I switch routes, I essentially switch ng-view or viewer's content. The problem I'm having is a memory leak, where in other directives inside the viewer hooks to events and do not clean up when the route is changed.

    One such example is as follows:

    angular.directive('i18n', ['$rootScope', 'LocaleService', function($rootScope, LocaleService) {
      var cleanup;
      return {
        restrict: 'EAC',
        compile: function(element, attrs) {
          var originalText = element.text();
          element.text(LocaleService.getTranslation(originalText, attrs.locale));
          cleanup = $rootScope.$on('locale-changed', function(locale) {
            element.text(LocaleService.getTranslation(originalText, attrs.locale || locale));
          });
        },
        link: function(scope) {
          scope.$on('$destroy', function() {
            console.log("destroy");
            cleanup();
          });
        }
      };
    }]);
    

    How do I have it so that these events are properly cleaned up?

  • Pwnna
    Pwnna about 11 years
    My issue is that new i18n directive shows up as I navigate around. So it is not just used once. Consider a case where I have a <i18n>Last updated</i18n> in each <ng-view>. That would be loaded each time I change ng-view
  • Clark Pan
    Clark Pan about 11 years
    Sorry what i meant by the first sentence is that your current implementation would only clean up the last event listener bound. since the cleanup var is declared in the 'factory' portion of the directive. The two samples i've provided should do what you want.
  • Pwnna
    Pwnna about 11 years
    Agreed. That makes sense. An additional step is needed to make this work completely. Refer to: github.com/shuhaowu/osumo/blob/master/static/js/develop/…
  • Ivan
    Ivan almost 9 years
    @Clark Pan it seems like you use $scope instead of scope in the second example.