nick.stuart
nick.stuart

Reputation: 544

Modify template in directive (dynamically adding another directive)

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.

Upvotes: 3

Views: 3857

Answers (4)

Jakob Jingleheimer
Jakob Jingleheimer

Reputation: 31580

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>

Upvotes: 1

Pierre Maoui
Pierre Maoui

Reputation: 6384

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.

Upvotes: 2

Matt York
Matt York

Reputation: 16261

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

Upvotes: 1

odiseo
odiseo

Reputation: 6784

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/

Upvotes: 2

Related Questions