3

Problem

Dynamically add the ng-bind attribute through a custom directive to be able to use ng-bind, ng-bind-html or ng-bind-html-unsafe in a custom directive with out manually adding to the template definition everywhere.

Example

http://jsfiddle.net/nstuart/hUxp7/2/

Broken Directive

angular.module('app').directive('bindTest', [
'$compile',
function ($compile) {
    return {
        restrict: 'A',
        scope: true,
        compile: function (tElem, tAttrs) {
            if (!tElem.attr('ng-bind')) {
                tElem.attr('ng-bind', 'content');
                $compile(tElem)
            }
            return function (scope, elem, attrs) {
                console.log('Linking...');
                scope.content = "Content!";
            };
        }
    };
}]);

Solution

No idea. Really I can not figure out why something like the above fiddle doesn't work. Tried it with and with out the extra $compile in there.

Workaround

I can work around it might adding a template value in the directive, but that wraps the content in an extra div, and I would like to be able to that if possible. (See fiddle)

Second Workaround

See the fiddle here: http://jsfiddle.net/nstuart/hUxp7/4/ (as suggested by Dr. Ikarus below). I'm considering this a workaround for right now, because it still feels like you should be able to modify the template before you get to the linking function and the changes should be found/applied.

4 Answers 4

2

You could do the compiling part inside the linking function, like this:

angular.module('app').directive('bindTest', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        scope: true,
        link: {
            post: function(scope, element, attrs){
                if (!element.attr('ng-bind')) {
                    element.attr('ng-bind', 'content');
                    var compiledElement = $compile(element)(scope);
                }
                console.log('Linking...');
                scope.content = "Content!";                
            }
        }
    };
}]);

Let me know how well this worked for you http://jsfiddle.net/bPCFj/

Sign up to request clarification or add additional context in comments.

4 Comments

Got something similar working here jsfiddle.net/nstuart/hUxp7/4 Would still like some understanding on to why the original does not work. I understand the compile function is where you should make template changes, so why is this one not getting picked up?
In jsfiddle.net/nstuart/hUxp7/4 $compile(tElem) does not work because $compile() returns a link function, which has to be binded to a scope. Since there's no scope available in the compile step, you can't use $compile there. Hence it's completely normal to use the $compile function in the linking step tho, so I wouldn't say it's just a workaround.
Thanks for the comment, and I figured out the $compile() not working in ..erm...compile because of the missing scope. But my original question still stands, why does altering the template not seem to do anything here? The $compile was my attempt at hacking it to work (which failed).
Aren't you supposed to do sth with the compiledElement after it is created? Does Angular understand with this code that element will now be replaced by compiledElement?
2

This way seems more elegant (no dependency with $compile) and appropriate to your case :

angular.module('app').directive('myCustomDirective', function () {
    return {
        restrict: 'A',
        scope: {},
        template: function(tElem, tAttrs) {
            return tAttrs['ng-bind'];
        },
        link: function (scope, elem) {
            scope.content = "Happy!";
        }
    };
});

jsFiddle : http://jsfiddle.net/hUxp7/8/

From Angular directive documentation : You can specify template as a string representing the template or as a function which takes two arguments tElement and tAttrs (described in the compile function api below) and returns a string value representing the template.

Comments

1

The source code tells all! Check out the compileNodes() function and its use of collectDirectives().

First, collectDirectives finds all the directives on a single node. After we've collected all the directives on that node, then the directives are applied to the node.

So when your compile function on the bindTest directive executes, the running $compile() is past the point of collecting the directives to compile.

The extra call to $compile in your bindTest directive won't work because you are not linking the directive to the $scope. You don't have access to the $scope in the compile function, but you can use the same strategy in a link function where you do have access to the $scope

Comments

1

You guys were so close.

function MyDirective($compile) {
    function compileMyDirective(tElement) {
        tElement.attr('ng-bind', 'someScopeProp');

        return postLinkMyDirective;
    }

    function postLinkMyDirective(iScope, iElement, iAttrs) {
        if (!('ngBind' in iAttrs)) {
            // Before $compile is run below, `ng-bind` is just a DOM attribute
            // and thus is not in iAttrs yet.
            $compile(iElement)(iScope);
        }
    }

    var defObj = {
        compile: compileMyDirective,
        scope: {
            someScopeProp: '=myDirective'
        }
    };

    return defObj;
}

The result will be:

<ANY my-directive="'hello'" ng-bind="someScopeProp">hello</ANY>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.