sambomartin
sambomartin

Reputation: 6813

AngularJS - Directives in a template

I'm trying to find the best approach for dynamically creating the sub-contents of an element created using a directive.

To give a simple example, say I have directive that say creates a basic div child element.

<div mydirective></div>

results in

<div mydirective><div></div></div>

The directive might look something like:

angular.module('demo', [])
.directive('mydirective', function() {
    return {
        restrict: 'A',           
        template:   '<div></div>'
    }
})

Then say I wanted to create an "extension" to this directive that makes the inner div blue. Because I don't know what future extensions I'll need I don't want to switch the template or bind any specific attributes.

I don't know what of these are possible, or what the normal approach is... but can you

1) Add an additional directive to the parent element (bit like css) i.e.

<div mydirective mybluedirective></div>

Where the mybluedirective looks for the inner div and manipulates it?

or

2) Can you include directive dynamically in the template, e.g.

<div mydirective subdirective="mybluedirective"></div>

angular.module('demo', [])
.directive('mydirective', function() {
    return {
        restrict: 'A',           
        template:   '<div [[[THE SUBDIRECTIVE PROPERTY FROM SCOPE??]]></div>'
    }
})

Is there a way you can intercept pre-compile to inject the directive?

a third option as I've ready is to use a factory and inherited classes to do the work for the directive, but that seems an overkill.

I'm guessing there is an easy way I don't know about, any advice appreciated

EDIT:

I think what I'm trying to do is dynamically modify the template to use another directive before it's compiled.

Upvotes: 3

Views: 826

Answers (3)

Michael Kang
Michael Kang

Reputation: 52867

Option 1 - Dynamically Modifying the Template

If you want to dynamically modify a template before compilation, you can use the template function:

HTML:

<div mydirective subdirective="mybluedirective"></div>

JS:

angular.module('demo', [])
.directive('mydirective', function() {
    return {
        restrict: 'A',           
        template:   function(element, attr) { 
             return '<div ' + attr.subdirective + '></div>'
        }
    }
})

Option 2 - Programmatic Access to Template

Alternatively, you could expose an API from mydirective that mybluedirective could use. This solution requires mydirective to delay compilation (because the template will be compiled manually), and more thought as to how mydirective is designed and intended to be extended:

HTML

<div mydirective mybluedirective></div>

JS

angular.module('demo', [])
.directive('mydirective', function($compile) {

    return {
        restrict: 'A',           
        template:   '<div></div>',
        // we want a child scope so that we don't pollute the parent scope
        scope: true,
        controller: function($scope, $element) {
            var attrs = {};
            // expose an API to add attributes dynamically
            this.addAttr = function(attr, value) {
                attrs[attr] = value;
            }
            $scope.attrs = attrs;
        },
        compile: function(element, attr) {
            // save the template
            var template = element.find('div');

            // empty the contents so it is not compiled (we're manually compiling during link)
            element.empty();

            return function(scope, element, attr) {
                // add the attributes to the template
                angular.forEach($scope.attrs, function(value, key) {
                    template.addAttr(key, value);
                }
                // add the template to the DOM
                element.append(template);

                // link the template to scope
                $compile(template)(scope);
            }

        }

    }
})
.directive('mybluedirective', function() {
     return {
          restrict: 'A',
          require: 'mydirective',
          link: function(scope, element, attr, mydirectiveController) {
               mydirectiveController.addAttr('ng-class','blue');
          }
     }
});

Upvotes: 3

JeffryHouser
JeffryHouser

Reputation: 39408

I use an ng-class to bind to an exposed variable inside the directive. You'd also need to add a scope, and if you want to set a default, then a controller or link function.

Here is a quick sample:

angular.module('demo', [])
.directive('mydirective', function() {
    return {
        restrict: 'A',           
        // add the ng-class to the template.  It binds to options.someClass
        template:   '<div ng-class="options.someClass"></div>',
        // give the directive its own Scope with an input object named options
        scope: {
            options : "="
        },  
        // Give the directive a controller
        controller: function($scope) {
            //  This is an init function to default the input objects 
             this.onInit = function(){
                   //  If the options object does not exist; create it 
                    if(!$scope.options){
                        $scope.options = {};
                    }
                   //  If our custom someClass variable doesn't exist; then give it a default value
                    if(!$scope.options.someClass){
                        $scope.options.someClass= "defaultClass";
                    }            }
             // call the onInit() function to set up the defaults
             onInit();
        }
    }
})

Then I also provide a CSS as part of the directive containing the default styles. You can put this in practice relatively easily. In your controller create our options object:

myDirectiveOptions = {
   someClass = 'myCustomClass';
}

In your view, do this:

<div mydirective options="myDirectiveOptions"></div>

I have found this to be simple, yet very powerful.


Using this approach; to change the directive--ng-class--dynamically; you merely have to update the options object. Somewhere in your controller, you can do:

myDirectiveOptions.someClass = 'someOtherCustomClass';

The ng-class inside of myDirective should pick it up automatically thanks to the magic of Angular binding. This change does not have to be inside myDirective's controller.

Upvotes: 2

Boyko Karadzhov
Boyko Karadzhov

Reputation: 586

You can use a second directive that looks for the result of the first one. Just make sure that "mybluedirective" has higher priority than "mydirective".

Js Fiddle: http://jsfiddle.net/XHJC2/

angular.module("demo", []).
    controller('SampleCtrl', function ($scope) {

    }).
    directive('mybluedirective', function () {
        return {
            priority: 4000,
            restrict: 'A',           
            link: function (scope, elem, attr) {
                elem.find('div').html('modified!');
            }
        };
    }).
    directive('mydirective', function() {
        return {
            priority: 40,
            restrict: 'A',           
            template:   '<div>TEST</div>'
        };
    });

Here is doc about "priority" in directives: https://docs.angularjs.org/api/ng/service/$compile#-priority-

Upvotes: 1

Related Questions