Staticly adding AngularJS directive from another directive

I'm trying to use a Bootstrap popover (via ui-bootstrap) with some static but complex content.

The popover itself works -- applying this directly to a particular element results in it displaying as expected:

<div data-popover="test"></div>

Using basic dynamic content from the scope also works:

<div data-popover="{{key}}"></div>

However I want to generate the content in a more complex way, involving an angular service (it's all local, though -- no ajax or other asynchronous code involved). This works:

<div data-popover="{{getPopoverText(key)}}"></div>

But it results in the call being made every digest, when in my case I know that the value will never change once found. (Sadly I'm using AngularJS 1.2.23, which predates one-time-binding support, so I can't use that.)

So I tried to do this with a directive:

<div data-generate-popover="key"></div>

module.directive('generatePopover', ['myService', function(myService) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var key = scope.$eval(attrs.generatePopover);
            var content = myService.getPopoverText(key);
            element.removeAttr('data-generate-popover');
            element.attr('data-popover', content);
        },
    };
}]);

This does run correctly and swaps the attributes, and the data-popover attribute contains the correct content to be displayed. However the popover does not appear when it should (presumably AngularJS hasn't noticed that I added a directive).

My latest attempt (inspired by this question) was to recompile the element after changing it:

module.directive('generatePopover',
    ['myService', '$compile',
function(myService, $compile) {
    return {
        restrict: 'A',
        priority: 10000,
        link: {
            pre: function (scope, element, attrs) {
                var key = scope.$eval(attrs.generatePopover);
                var content = myService.getPopoverText(key);
                element.removeAttr('data-generate-popover');
                element.attr('data-popover', content);
                $compile(element)(scope);
            }
        },
    };
}]);

This seems to work as expected. My question is: is this the right way to do it, and are there any negative consequences of it? Is there a better way other than upgrading to get one-time-binding support?

Edit: Apparently one negative consequence is that this extra compile doesn't work properly if the element has some other complex directive (in particular ngRepeat; possibly also ngIf etc too but I didn't test that).

The problem here is, the auto initialization of the popover will happen only once at the dom ready event, any changes done after that will not initialize the plugin.

So try to initialize the plugin your self

module.directive('generatePopover', ['myService', function (myService) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var key = scope.$eval(attrs.generatePopover);
            var content = myService.getPopoverText(key);
            element.removeAttr('data-generate-popover');
            //set the popover content here
            element.attr('data-content', content);
            //here initialize the 
            element.popover();
        },
    };
}]);

You shouldn't worry too much about the fact that angular executes all watchers on every digest cycle, otherwise you will never stop worrying. Even a simple binding like this {{key}} is in fact a function that will parse scope.key on every digest cycle. In fact, it is even done many times per digest cycle.

That's how Angular works (at least before 1.3, and this will totally change with angular 2), and it's pretty well optimized: your only concern should be to make the function getPopoverText as fast as possible: jQuery strictly prohibited here!

Now if you really want to do it, your approach seems the right way to do that. If you still want the key to get updated but only when you decide and not on every digest cycle, you could add

scope.$on('popover.update', updateThePopover);

and broadcast this event every time it is needed. But again, a typical angular application can have thousands of watchers and still performs super smoothly if the watchers itself are well coded, so I would highly recommend to not waste time and complexify the code for a single one of them, and instead focus on having a simple as possible getPopoverText method. Just an advice from my own experience.

PS: To lower the number of watchers executed, try modularising the app in many isolated directives, and prefer scope.$digest over scope.$apply to only digest the isolated scopes and not all of them.

Since posting this question I've simplified things a bit (although there's a little double-handling, which I'm not entirely happy about):

<div data-generate-popover="key" data-popover="{{tooltip}}"></div>

module.directive('generatePopover', ['myService', function(myService) {
    return {
        restrict: 'A',
        priority: 10,
        link: function (scope, element, attrs) {
            var key = scope.$eval(attrs.generatePopover);
            element.removeAttr(attrs.$attr.generatePopover);

            scope.tooltip = myService.getPopoverText(key);
        },
    };
}]);

(In this case I know that there will only be one of these per scope defined higher up by ngRepeat, so I don't need to worry about multiple tooltips overwriting each other.)

Having said that, this is still a hack and I'm looking forward to replacing it with a one-time binding when I can upgrade AngularJS.