0

I'm having some problems with a directive that I want to be dynamically added as the user clicks a button. What would be the best approach to achieve this?

In my research I've found element.bind (from jqLite), but I haven't found lots of examples on the internet.

Here's my code:


HTML CODE

<div class="form-group">
    <button class="btn btn-info" ng-click="addAttr()">Add Atributtes</button>
</div>
<addattributes options="attributes" attr="obj"></addattributes>


ANGULARJS DIRECTIVE

.directive('addattributes', function () {
return {
    restrict: 'E',
    scope: {
        attributes: '=options',
        attr: '='
    },
    template: 
    '<div ng-class="classInput">' +
    '   <div class="col-md-8" style="padding-left: 0;">' +
    '       <label>Nome do Atributo</label>' +
    '       <input type="text" class="form-control" placeholder="Attrs name" ng-model="attrname" ng-change="validation()">' +
    '   </div>' +
    '   <div class="col-md-4" style="padding-right: 0;"> ' +
    '       <label>Tipo do Atributo</label> ' +
    '           <select class="form-control" ng-options="attribute.name for attribute in attributes" ng-model="attrtype"></select>' +
    '   </div> '+
    '   <div class="clearfix"> '+
    '   <div> '+
    '   <button type="button" class="btn btn-default btn-xs pull-right" ng-click="changeButton()" style="margin-top: 1em;" ng-show="showBtn == true"> Adicionar </button>       ' +
    '   </div> {{attr}}'+
    '</div>',
    link: function(scope,element,attrs){
        scope.showBtn = true;
        scope.classInput = 'form-group';
        scope.attrtype= scope.attributes[2];

        scope.changeButton = function(){
            scope.showBtn = false;
            scope.attr = {
                name: scope.attrname,
                type: scope.attrtype
            };
        }

        scope.validation = function(){
            if(scope.attrname.length < 6) {
                scope.classInput = 'form-group has-error';
            } else {
                scope.classInput = 'form-group has-success';
            }
        }


    }
};})

Thanks in advance, have a great day!

4
  • what does addAttr() function do? Commented Jan 16, 2015 at 11:37
  • nothing yet, that would be the function to add the html :) Commented Jan 16, 2015 at 11:38
  • I'm sure you read it already, but manipulating DOM from the controller is an anti-pattern. Commented Jan 16, 2015 at 11:39
  • So how should i add the same directive times and times when the user clicks on a button? Commented Jan 16, 2015 at 11:40

2 Answers 2

2

You don't "add a directive" in the controller. This means that you are manipulating DOM, which you should not do.

Instead, think in terms of ViewModel. What ViewModel property does this directive display/alter? Say it is an "attribute", so add an attribute to $scope.myAttributes (to avoid name collision with some attributes property that you have) array.

$scope.myAttributes = [];
$scope.addAttr = function(){
   $scope.myAttributes.push({}); // empty object to hold some property of an "attribute"
}

Then, in the View, ng-repeat over attributes:

<div class="form-group">
    <button class="btn btn-info" ng-click="addAttr()">Add Atributtes</button>
</div>
<addattributes ng-repeat="attribute in myAttributes" attr="attribute"></addattributes>

(I'm not fully understanding what your addattributes directive does, so I'm assuming that it sets an attribute object via attr)

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

5 Comments

Where in his question did he say he wanted to manipulate the DOM in a controller? The question features a directive and it's perfectly acceptable to have one directive add another directive - not everything can be done with ng-repeat.
@EdHinchliffe, it's not a directive that adds a directive - it's the click of a button outside a directive with a function addAttr defined in the controller. Read my comment to his question and his reply.
Ah yes you're right, however the HTML that calls the addAttr is within a directive template, so he could simply move the function to the link function.
@EdHinchliffe, the section "HTML Code" of his question is not a directive template - it's the View hosting the directive.
I stand corrected ;) I've upvoted this answer, but I've started another so I'll finish!
1

Without giving some details of your use-case, it's hard to tell whether what you're trying to achieve could be more simply (and importantly more intuitively) done with ng-show, ng-switch or some other ng- directive, but we'll assume that your approach is well thought through.

As @New Dev has already pointed out in another answer, you should try to keep DOM manipulation inside a directive, and NOT in a controller - this will help with testability, separation of concerns, and many, many other things.

The below code demonstrates how to do what you're trying to do, but in an abstract way, because your example looks too complex for a simple answer.


If your directive is an element called newDirective adding it is a three step process:

  1. Create the element and append it to the DOM
  2. Compile the element
  3. Link it to a scope

You should do this in the link function of a directive where you have access to the element:

link: function (scope, element, attributes) {    
  scope.addDirective = function () {
  // create the new directive element
    $newDirective = angular.element('<new-directive>');
    // Append it to the DOM
    element.append($newDirective);
    // compile it and link it to the scope
    $compile($newDirective)(scope);
  }
}

Note that a directive's compile function returns its link function, so the compile & link is actually two steps in one line.

See the runnable code snippet below. myDirective adds newDirectives. The newDirective's template contains a button that removes it. Clicking the remove button works, proving the link function has been run and the directive is functioning properly.

angular.module('app', [])
.directive('myDirective', function ( $compile ){
  return {
    restrict: 'E',
    template: '<button ng-click="addDirective()">Click me!</button>',
    link: function (scope, element, attributes) {    
      scope.addDirective = function () {
        // create the new directive element
        $newDirective = angular.element('<new-directive>');
        // Append it to the DOM
        element.append($newDirective);
        // compile it and link it to the scope
        $compile( $newDirective )(scope);
      }
    
    }
  }
})
.directive('newDirective', function ( $compile ){
  return {
    restrict: 'E',
    template: '<li>{{someText}}' +
      '<button ng-click="remove()">Remove</button>' +
    '</li>',
    link: function (scope, element, attributes) {  
      scope.someText = "If the scope is linked this will show";
      scope.remove = function () {
        element.remove() 
      }
    }
  }
});
<script 
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>

<div ng-app="app">
  <my-directive></my-directive>
</div>

4 Comments

Ed, I would counter that even a directive adds other directives you should reuse as much of Angular's paradigms as you can, and I would use ng-repeat here as well, if only because the <new-directive> might need to do something with the data coming from the page controller via parent directive
@NewDev I absolutely agree with your sentiment that ng- directives should be used where possible. This example is contrived and over-simplified, it was really to demonstrate how to add and compile a directive in the DOM rather than when and why it should be done. That said, <new-directive> can access data from any parent controller this way just as well as it can with ng-repeat.
@EdHinchliffe, you're missing my point. This isn't just a display directive - this is an input directive and it is meant to be "added" in correspondence to adding a new entry in the outer scope's VM - something you are not even showing. I would go so far as to say that what you showed is an anti-pattern for the OP's specific problem. It clearly solved OP's problem since he accepted it, but looking into what he tried to do, I feel like, in his specific case, showing this approach is a disservice.
I don't think I am missing your point - as stated in the question it is possible (likely even) that this could be achieved in a better fashion using just an ng-repeat or similar. The question doesn't give any direct context about what's going on here, so I provided an alternative answer to yours - isn't that what stackoverflow is for? Importantly, using this approach doesn't cause any inefficiencies (I would argue it's more efficient than ng-repeat). The main difference is where the logic is - directive or markup. It's certainly not an "anti-pattern".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.