71

I have a directive that can be used multiple times on a page. In the template of this directive, I need to use IDs for an input-Element so I can "bind" a Label to it like so:

<input type="checkbox" id="item1" /><label for="item1">open</label>

Now the problem is, as soon as my directive is included multiple times, the ID "item1" is not unique anymore and the label doesn't work correctly (it should check/uncheck the checkbox when clicked).

How is this problem fixed? Is there a way to assign a "namespace" or "prefix" for the template (like asp.net does with the ctl00...-Prefix)? Or do I have to include an angular-Expression in each id-Attribute which consists of the directive-ID from the Scope + a static ID. Something like:

<input type="checkbox" id="{{directiveID}} + 'item1'" /><label for="{{directiveID}} + 'item1'">open</label>

Edit:

My Directive

module.directive('myDirective', function () {
    return {
        restrict: 'E',
        scope: true, 
        templateUrl: 'partials/_myDirective.html',
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            ...
        } //controller
    };
}]);

My HTML

<div class="myDirective">
  <input type="checkbox" id="item1" /><label for="item1">open</label>
</div>
0

4 Answers 4

94

HTML

    <div class="myDirective">
        <input type="checkbox" id="myItem_{{$id}}" />
        <label for="myItem_{{$id}}">open myItem_{{$id}}</label>
    </div>
Sign up to request clarification or add additional context in comments.

8 Comments

Elegant solution. $id is the unique scope id provided by $rootScope for every child scope. Obviously that can be used for any view which will have a different scope which mostly is the case.
Should this also combine the {{::expression}} trick from @llan's answer to avoid creating more watches?
@penguin359 yeap, you can do one time binding with {{::$id}} as well. At the time my comment was made 1.3 was not out yet.
Interesting that it is not possible to query nested elements by dynamic selector inside directive's link method: element.find(`#myItem_ ${scope.$id}`), because template haven't compiled its dynamic values yet…
Is there any solution to this? (i.e. Nik's comment). What if within the link function you need to modify the elements with dynamic id... is there no way to achieve this?
|
51

UPDATE

Angular 1.3 introduced a native lazy one-time binding. from the angular expression documentation:

One-time binding

An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).

Native Solution:

.directive('myDirective', function() {

    var uniqueId = 1;
    return {
        restrict: 'E',
        scope: true,
        template: '<input type="checkbox" id="{{::uniqueId}}"/>' +
                  '<label for="{{::uniqueId}}">open</label>',
        link: function(scope, elem, attrs) {
            scope.uniqueId = 'item' + uniqueId++;
        }
    }
})

Only bind once:

  • If you only need to bind a value once you should not use bindings ({{}} / ng-bind)
  • bindings are expensive because they use $watch. In your example, upon every $digest, angular dirty checks your IDs for changes but you only set them once.
  • Check this module: https://github.com/Pasvaz/bindonce

Solution:

.directive('myDirective', function() {

    var uniqueId = 1;
    return {
        restrict: 'E',
        scope: true,
        template: '<input type="checkbox"/><label>open</label>',
        link: function(scope, elem, attrs) {
            var item = 'item' + uniqueId++;
            elem.find('input').attr('id' , item);
            elem.find('label').attr('for', item);
        }
    }
})

5 Comments

Should be mentioned that since 1.3 (still in RC at the time), you can do a bind once using the notation {{:yourExpression}}.
The one-time-binding feature is now documented here: docs.angularjs.org/guide/expression#one-time-binding
we can use this technique also by following approach link: function(scope, elem, attrs) { var item = 'item' + uniqueId++; elem.find('input').attr('id' , item); var element=document.getElementById(item); now in element you will get dom object and you can do anything :). it's really good technique for dynamically manipulate dom where element id is set dynamically!}
Mohammed your example is not clear. Pleas could you add an answer which shows more clearly what you are saying?
@Mohammadtanvirulislam DOM manipulation is generally a bad Idea. Why would you want to do what you stated? And it doesn't really add something to the original question/answer - it's an entirely different topic.
2

We add a BlockId parameter to the scope, because we use the id in our Selenium tests for example. There is still a chance of them not being unique, but we prefer to have complete control over them. Another advantage is that we can give the item a more descriptive id.

Directive JS

module.directive('myDirective', function () {
    return {
        restrict: 'E',
        scope: {
            blockId: '@'
        }, 
        templateUrl: 'partials/_myDirective.html',
        controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
            ...
        } //controller
    };
}]);

Directive HTML

<div class="myDirective">
  <input type="checkbox" id="{{::blockId}}_item1" /><label for="{{::blockId}}_item1">open</label>
</div>

Usage

<my-directive block-id="descriptiveName"></my-directive>

Comments

1

Apart from Ilan and BuriB's solutions (which are more generic, which is good) I found a solution to my specific problem because I needed IDs for the "for" Attribute of the label. Instead the following code can be used:

<label><input type="checkbox"/>open</label>

The following Stackoverflow-Post has helped:

https://stackoverflow.com/a/14729165/1288552

1 Comment

But if you're using Bootstrap you don't use that approach. Because Bootstrap doesn't allow input's to be embedded inside labels.

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.