why does the mouse click in parent fires twice when a directive is injected during compile

I have a directive which needs to plug in an additional directive depending on some model value. I am doing that in the [edit] pre-link phase of the directive. The parent directive sets up a host of methods and data which the child uses. Therefore I have child directive with scope:true.

On the outer (or, parent) directive there is a click handler method. When I click this, it gets fired twice. I want to know why and how. Presently the only way I know how to stop that is by calling event::stopImmediatePropagation() but I have a suspicion I am doing something wrong here.

template usage

<dashboard-box data-box-type="column with no heading">
    <div class="card">
        <div 
                ng-click="show($event)" 

                class="box-title item item-divider ng-binding is-shown" 
                ng-class="{'is-shown':showMe}">
            <span class="box-title-string ng-binding">A/R V1</span>
        </div>
        <div class="box-content item item-text-wrap" ng-show="showMe">
            <!-- plug appropriate dashbox here - in dashboard-box compile -->
            <dashbox-column-with-no-heading>
                <div>
                    SOME DATA...
                </div>
            </dashbox-column-with-no-heading>
        </div>
    </div>
</dashboard-box>

In the directive for dashboard-box:

scope: {
    boxType: "@"
},
pre: function preLink(scope, iElement, iAttrs, controller) {
    // some crazy hardcoding - because find by tagname...replaceWith throws in ionic
    var parent_div = angular.element(angular.element(iElement.children()[0]).children()[1]);
    if (!parent_div) {
        return; // programmer error in template??
    }
    var html_tag_name = d_table_type_to_directive_name.xl[iAttrs.boxType];
    parent_div.append(angular.element( "<" + html_tag_name + ">" ));
    $compile(iElement.contents())(scope); // angular idiom
}

In the controller for dashboard-box:

$scope.show = function($e){
    $log.log("dashboard box show(), was=", $scope.showMe, $e);

    if ($e) $e.stopImmediatePropagation(); // <<<<<<<<<<< without this, double-hits!!

    $scope.showMe = ! $scope.showMe;
    // etc
};

In the directive for dashbox-column-with-row-heading:

restrict: "E",
scope: true,
templateUrl: "dashbox-column-with-row-heading.tpl.html"
controller: function(){
    // specialized UI for this directive
}

I am using ionicframework rc-1.0.0 and angularjs 1.3.13.

What is happening here is that that you are double-compiling/linking the ng-click directive: 1) first time Angular does that in its compilation phase - it goes over the DOM and compiles directives, first dashboardBox, then its children, together with ngClick), and 2) - when you compile with $compile(element.contents())(scope).

Here's a reduced example - demo - that reproduces your problem:

<foo>
  <button ng-click="doFoo()">do foo</button>
</foo>

and the foo directive is:

.directive("foo", function($compile) {
  return {
    scope: true,
    link: {
      pre: function(scope, element, attrs, ctrls, transclude) {
        $compile(element.contents())(scope); // second compilation
        scope.doFoo = function() {
          console.log("foo called"); // will be called twice
        };
      }
    }
  };
});

What you need to do instead is to transclude the content. With transclusion, Angular compiles the content at the time that it compiles the directive, and makes the content available via the transclude function.

So, instead of using $compile again, just use the already compiled - but not yet linked, until you tell it what it should be linked to - contents of the directive.

With the foo example below, this would look like so:

.directive("foo", function($compile) {
  return {
    scope: true,
    transclude: true,
    link: {
      pre: function(scope, element, attrs, ctrls, transclude) {

        transclude(scope, function(clone, transcludedScope){
          var newEl = createNewElementDynamically();
          $compile(newEl)(transcludedScope); // compile just the newly added content

          // clone is the compiled and now linked content of your directive's element
          element.append(newEl);
          element.append(clone);
        });

        scope.doFoo = function() {
          console.log("foo called");
        };
      }
    }
  };
});