Вопрос

I've noticed what seems to be a bug for me, but is probably more me misusing the $compile service in AngularJS : I have a directive called "dynamic" that compiles angularjs code and shows it into a div. The code that I compile in that case contains ng-controllers and those controllers are listening on events. The problem is that apparently controllers aren't "dead" after being replaced, because controllers that should be vanished still react to events (like $routeChangeSuccess or any other event). Here is a working plunkr that shows the problem. Let's see an example code of my problem :

The directive I'm using :

app.directive('dynamic', function ($compile) {
    return {
        restrict: 'A',
        replace: true,
        link: function (scope, element, attrs) {
            scope.$watch(attrs.dynamic, function(html) {
                element.html(html);
                $compile(element.contents())(scope);
            });
        }
    };
});

The main controller, followed by the controllers that I include:

app.controller('TestCtrl', function($scope) {
  $scope.dynamicContent = "Default content";

  $scope.firstButton = function() {
    $scope.dynamicContent = "<div ng-controller='FirstCtrl'>The div from first button</div>";
  }

  $scope.secondButton = function() {
    $scope.dynamicContent = "<div ng-controller='SecondCtrl'>The div from second button</div>";
  }

  $scope.checkButton = function() {
    $scope.$broadcast('checkEvent');
  }
});

app.controller('FirstCtrl', function($scope) {
  $scope.$on('checkEvent', function() {
    alert(1);
  });

});
app.controller('SecondCtrl', function($scope) {
  $scope.$on('checkEvent', function() {
    alert(2);
  });
});

Now if I call firstButton() then secondButton() then checkButton(), instead of receiving only the alert(2), I receive two alerts. If I hit buttons 1/2/1/2/1/2/1/2 it's going to show me as many alerts as buttons I've clicked.

What am I doing wrong here ?

Thanks, hilnius

Это было полезно?

Решение

Your really close. First I'll give you what your probably looking to do since I do not know your intentions for the $compile service. Then I'll explain why you don't need the $compile service for this particular instance since your effectively duplicating ng-include.

What your probably looking to do:

The key to using directives (especially when attempting to "$compile" dynamic content is ensuring your know what scope is passed where. For most of the directives built into angularjs, angular automatically handles creation (via scope.$new() ) and destruction (via scope.$destroy() ). Since you are not explicitly '$destroy'-ing the scopes they will not be removed. Another problem is that your directly attaching the "dynamic" directive to the current scope without creating a child scope or an isolate scope in the directive (via $new):

Plunkr example

app.directive('dynamic', function ($compile) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
          var curScope = null,
              curEle = null;

          function removeOld(){
            if( curScope ){
              curScope.$destroy();
              curScope = null;
              curEle.remove();
              curEle = null;
            }
          }

            scope.$watch(attrs.dynamic, function(html) {
                removeOld();
                curScope = scope.$new(); //creates child scope (not isolate)
                //probably should do some proper escaping here see $sce service
                curEle = angular.element( html );
                if( !curEle.length ){
                  curEle = angular.element('<span>'+html+'</span>');
                }
                $compile( curEle )(curScope);
                element.append( curEle );
            });
        }
    };
});

What you probably should do:

For some small templates like this you probably should consider putting them into the $templateCache (via put as shown in the plunkr below) so that any request for the template can automatically load it. You also have to consider some other things like 'is the html properly sanitized?' or 'do I want my content animated properly?'. These things are automatically handled in ng-include which it almost seems like your trying to copy.

Plunkr example

app.run(function( $templateCache ){
  $templateCache.put("btn_default.html", "Default content");
  $templateCache.put("btn_one.html", "<div ng-controller='FirstCtrl'>The div from first button</div>");
  $templateCache.put("btn_two.html", "<div ng-controller='SecondCtrl'>The div from second button</div>");
})

Now all you have to do is use the pre-built ng-include directive like so:

<div ng-controller="TestCtrl">
      <div class="btn btn-default" ng-click="firstButton()">First button</div>
      <div class="btn btn-default" ng-click="secondButton()">Second button</div>
      <div class="btn btn-default" ng-click="checkButton()">Check events</div>
      <div ng-include="dynamicContent"></div>
</div>

ng-include source to help you out

Hope this helps with a better understanding.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top