1

I'm trying to create an Angular directive which is just an HTML attribute (my-directive). This directive uses some input attributes (text, length).

<li my-directive text="first" length="6"></li>

Everything works nicely when I use it just once. But I'd like to use it multiple times, for list items:

<ul>
  <li my-directive text="first" length="elements[0].len"></li>
  <li my-directive text="second" length="elements[1].len"></li>
  <li my-directive text="third" length="elements[2].len"></li>
  <li my-directive text="fourth" length="elements[3].len"></li>
</ul>

This is when I experience a weird behaviour: Each list item displays the last item's attributes. I checked it, and the directive controllers receive the different values. Still, only the last one is displayed.

I'm pretty sure someone else bumped into this before, but I couldn't find anything related.

Plunkr here

UPDATE:

It looks like this issue can be solved using ng-repeat, but I'd rather not use that. And still it looks like a bug to me.

1
  • It does look like a bug. Can you share the directive code so we can try and see if something is wrong with it ? Commented Nov 9, 2016 at 13:11

3 Answers 3

4

use isolated scope for directive will solve the problem

http://plnkr.co/edit/pwfbShFYLPMHlSLf48ng?p=preview

.directive('myDirective', function () {
    return {
      restrict: 'A',
      bindToController: {
        // text: '@text',
        length: '='
      },
      scope: {},
      controller: function() {
        var ctrl = this;
        return ctrl;
      },
      controllerAs: 'ctrl',
      template: '{{ ctrl.text }} - {{ ctrl.length }}'
    }
  });
Sign up to request clarification or add additional context in comments.

Comments

2

Well I personally think it can be interpreted as a bug, but honestly is more like a consequence of how bindToController and directives work.

The problem can be described as a scope issue. Directives don't create an isolated scope by default, unless you tell it to do so. According to angularjs docs for bindToController by using bindToController also doesn't create a scope by default so all your directive is doing by a logical accident, is binding your controller scope on the parent scope (i.e., myController). For example, if you add a console.log(scope) on a link function you will see that all scopes are the same for each directive.

  link: function (scope) { console.log(scope);}

However, regardless the fact that it can be way problematic to handle this without the knowledge of this behaviour, it can be done by creating an isolated scope like the following example and using a boolean version of bindToController feature.

  bindToController: true,
  scope: {
    text: '@text',
    length: '='
  }

Complete snippet:

  angular.module('myApp', [])
    .controller('myController', function($scope) {
      $scope.test = 'here';
      $scope.elements = [{
        len: 7
      }, {
        len: 13
      }, {
        len: 12
      }, {
        len: 35
      }, ]
    })
    .directive('myDirective', function() {
      return {
        restrict: 'A',
        bindToController: true,
        scope: {
          text: '@text',
          length: '='
        },
        controller: function() {
          var ctrl = this;
          return ctrl;
        },
        controllerAs: 'ctrl',
        template: '{{ ctrl.text }} - {{ ctrl.length }}'
      }
    });

  angular.element(document).ready(function() {
    angular.bootstrap(document, ['myApp']);
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-controller="myController">
  {{ test }}
  <br>
  <ul>
    <li my-directive text="first" length="elements[0].len"></li>
    <li my-directive text="second" length="elements[1].len"></li>
    <li my-directive text="third" length="elements[2].len"></li>
    <li my-directive text="fourth" length="elements[3].len"></li>
  </ul>
</div>

Comments

1

This may be one solution for this.

<body ng-app="myApp">
    <div ng-controller="myController">
      {{ test }}
      <ul>
        <li ng-repeat="elm in elements" my-directive text="{{elm.text}}" length="elm.len"></li>
      </ul>
    </div>
  </body>

and in script.js

$scope.elements = [
         {
           text:'first',
           len: 7
         },
         {
           text:'second',
           len: 13
         },
         {
           text:'third',
           len: 12
         },
         {
           text:'fourth',
           len: 35
         },
      ]

4 Comments

You're right indeed, ng-repeat would solve this issue. However, I don't want to use it here.
Actually you woulg ng-repeat the <li> tag not the <ul> or the computed result would look like this : <ul><li ...></li></ul><ul><li ....></li></ul>... where what you want here is more <ul><li ...></li><li ...></li>...</ul>
@KiJéy I already edited and it still works fine with ng-repeat on li elements.
@fodma1 Then why not use it ? If you don't want to loop through all the elements, it's still easier to craft a sub-element in your controller and loop through that one or add a boolean field to every element and use a filter in the ng-repeat

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.