Dynamically adding Angular directives

All we need is an easy explanation of the problem, so here it is.

I’m fairly new to angular JS and am finding it a steep learning curve, I get the feeling im really missing the point here but here goes:

I want to add a directive to my page from a controller. So I thought if I add the directive tag to the page, the directive and associated controller/template etc get added with it. After reading about the $compile method, I thought this would then be used to bind this directive to its newly created tag. This part is commented out below, but with or without this, I need the word login to appear and its controller to control it?

I can find lots of examples of similar around the web when the directive tag is on the page at load time, and can get those to work fine, so this is whats making think it is related to the $compile method – what am I missing?

HTML:

<div ng-app="application" ng-controller="myController"></div>

JS:

var myApp = angular.module('application', []);

myApp.controller('myController', ['$scope', function($scope) {

        function showLoginDirective () {
            $scope.login = angular.element(document.createElement('login'));

            angular.element(document.body).append($scope.login);
        };

        showLoginDirective();
    }
]);

angular.module('directives', [])
    .directive('login', function($compile) {
        return {
            restrict: 'E',
            controller: 'LoginController',
            template: '<div>login</div>',
            link: function(scope, element, attrs) {
                //$compile(element.contents())(scope.$new);
                console.log('should I not have a div containing login controlled by loginController at this point?');
            }
        };
});

the above code is also here: http://jsfiddle.net/d5n6L/7/

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You shouldn’t really be dynamically adding elements to the page with Angular. Many people, myself included, who come from a jQuery background, assume that we can continue with this practice and just add things to the page as we need them.

However, with Angular, the logic should really all be visible in the markup. What does that mean? In your case, you should have the directive there no matter what, and then control its visibility with ng-show or ng-hide or ng-class.

So, something like this would be best:

<login ng-show="showLogin"></login>

And then you can use your directive as you programmed.

Note that you can also define an inline controller (assign an array of dependencies and a function of those dependencies as the controller property of your directive). That keeps all related code in the same place.

E.g.,

angular.module('directives', [])
    .directive('login', function($compile) {
        return {
            restrict: 'E',
            controller: ['$scope', function($scope) {

                function showLoginDirective () {
                    $scope.showLogin = true;

                };

                 showLoginDirective();
              }
            ],
            template: '<div>login</div>',
            link: function(scope, element, attrs) {
                //$compile(element.contents())(scope.$new);
                console.log('should i not have a div containing login controlled by loginController at this point?');
            }
        };
});

Method 2

Rather than compiling dynamically from within your controller I suggest you use ng-if to declaratively express which DOM elements should exist on the DOM.

HTML

<div ng-app="application" ng-controller="myController">
    <div ng-if="showLogin" login></div>
</div>

JS

var myApp = angular.module('application', []);

myApp.controller('myController', ['$scope', function($scope) {

        function showLoginDirective () {
            $scope.showLogin = true;
        };

        showLoginDirective();
    }
]);

angular.module('directives', [])
    .directive('login', function($compile) {
        return {
            restrict: 'E',
            controller: 'LoginController',
            template: '<div>login</div>',
            link: function(scope, element, attrs) {
                //$compile(element.contents())(scope.$new);
                console.log('should i not have a div containing login controlled by loginController at this point?');
            }
        };
});

Method 3

I want to add a directive to my page from a controller.

You should be able to define a boolean variable on the $scope that determines whether or not to show the login.

$scope.loginShouldBeShowing = false;
$scope.showLogin = function() {
  $scope.loginShouldBeShowing = true;
};

You can then use this in the template, with the ngIf directive, to only show the login template if this variable is set to true

<login ng-if="loginShouldBeShowing"></login>

You can see this in your modified JSFiddle at

http://jsfiddle.net/jK9zr/2/

I’ve also added a button so you can see in the console that the link function only runs after after you press the button, and loginShouldBeShowing gets set to true

I can find lots of examples of similar around the web when the directive tag is on the page at load time, and can get those to work fine, so this is whats making think it is related to the $compile method

From my understanding and previous use, it’s perfectly usual to include directives in the template that are only used in certain situations, i.e. when certain $scope variables are set to certain values, using ngIf, or maybe ngSwitch or ngShow. I think things would soon get very messy if you tried to $compile every part of the template that may or may not be used at any one time. Although I’m only a relative beginner in AngularJS, so far I’ve only ever had to use $compile when evaluating attributes of custom directives, so

<login after-login="doThisFunction()"></login>

might then need to use $compile to call doThisFunction() at an appropriate point.

As a small sidebar, the names of your variables are a bit telling that in the controller, you’re concerned with what’s happening in the template. It’s more usual to keep some sort of model/business “state” in the controller, and then show the appropriate thing in the template. So you might have in the controller:

$scope.loginState = 'loggedOut';

And then in the template:

<login ng-if="loginState == 'loggedOut'"></login>

Edit: I also noticed that the login directive was in a different module to the rest of the app. I suspect this caused issues, so I modified that aspect in my JSFiddle so there was only the one module.

Edit: I think I am confused between $compile and $parse above, so I would check against the docs/other sources about my use of $compile.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply