56

I am using "draggable" directive to support image dragging. However, as per the role of the user, I need to disable image dragging for certain groups of users. I have used following code.

<!--draggable attribute is used as handle to make it draggable using jquery event-->           
<li  ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">            
<!-- Images and other fields are child of "li" tag which can be dragged.-->                    
</li> 

The method dragSupported is in the template scope and returns true or false. I don't want to create two big duplicate <li> elements by using ng-if for each value returned by dragSupported(). In other words, I am not looking for the following approach to solve this.

<!--draggable attribute is used as handle to make it draggable using jquery event-->           
<li ng-if="dragSupported() ==true"  ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">            
<!-- Images and other fields are child of "li" tag which can be dragged.-->                    
</li>
<!--remove "draggable" directive as user doesn't have permission to drag file -->
<li ng-if="dragSupported() !=true"  ng-repeat="template in templates"  id="{{template._id}}" type="template" class="template-box">            
<!-- Images and other fields are child of "li" tag which can be dragged.-->                    
</li>

Is there any other approach to avoid code duplicity?

Meligy
  • 35,654
  • 11
  • 85
  • 109
joy
  • 3,669
  • 7
  • 38
  • 73
  • Bind your dragSupported function into the directive's scope with "&" and check it in the link function. Or add an attribute to the directive that you can use to do the same. – aet Sep 14 '13 at 05:15
  • I would suggest put a checking in directive to make it draggable or not using this drag supported function. – ssilas777 Sep 14 '13 at 05:16

4 Answers4

58

ng-attr-<attrName>

Support for conditionally declaring an HTML attribute is included with Angular as the dynamically-titled ng-attr-<attrName> directive.

Official Docs for ng-attr

Example

In your case, the code might look like this:

<li
    id="{{template._id}}"
    class="template-box"
    type="template"
    ng-repeat="template in templates"
    ng-attr-draggable="dragSupported() === true"
></li>

Demo

JSFiddle

This contains examples of usage for the following values: true, false, undefined, null, 1, 0, and "". Note how typically-falsey values may yield unexpected results.

Walter Roman
  • 4,621
  • 2
  • 32
  • 36
  • 1
    I liked this approach. In my new development I am using this approach. Thanks for sharing. – joy May 03 '15 at 21:29
  • 19
    What if your attribute has parameters passed into it? – Jamie Street Aug 20 '15 at 14:11
  • 2
    I can't get this code to work... Here's a jsFiddle: http://jsfiddle.net/thsd0vfc/1/. For me when I use curly brackets the attr is always added whether or not the value exists in the scope. And when I don't use brackets, the attr is never added. Am I missing something? – gkpo Sep 28 '15 at 15:04
  • @MonsieurNinja Try taking a look at my updated answer. – Walter Roman Oct 14 '15 at 19:01
  • @WalterRoman I'm trying to use ng-attr to switch between two directives --> ng-repeat or collection-repeat depending on a variable. How do I use this when the directive takes inputs? Specifically, I want to do this: https://gist.github.com/pliablepixels/d130af01a04176c9e90e – user1361529 Nov 07 '15 at 13:33
  • 1
    @user1361529 Check out your Gist for a fast, though untidy, approach. – Walter Roman Nov 07 '15 at 22:30
  • @JamieStreet AFAIK this can be only used in a Boolean fashion (eg. you may only have this attribute set or not set). – Walter Roman Jun 08 '16 at 13:28
  • 1
    Updated docs (don't see anything about ngAttr on the linked page): https://docs.angularjs.org/guide/interpolation – ZachB Aug 17 '16 at 20:50
  • @ZachB Thanks for providing the new link! I've updated the answer with it. – Walter Roman Feb 14 '17 at 16:34
  • @JamieStreet `layout-xs="{{!loggedUser ? 'column' : 'row'}}"` – Alex Ivasyuv Mar 03 '17 at 12:40
  • I like the `ng-attr-` solution. However it didn't really work for my conditional attribute directive. I have a `ng-attr-min-date` directive which will add mindate validation to a date field. If the config for this field would state that there ought to be no minDate validation, then the `min-date` attribute would not be present on the input field. However, the directive would already be initialized and firing away. Removing the attribute doesn't disable the directive. Inside the directive I can mange to have it do nothing if the min-date config is negative. Anyone else noticed this behaviour? – Mattijs Apr 26 '17 at 04:11
  • 14
    This answer is *Wrong*. If you add `ng-attr-directiveName="{{expression}}"`, the directive always gets added to the element regardless of expression. Even when the expression returns `undefined`, angular runs the directive. See this link for more information: https://github.com/angular/angular.js/issues/14575 – Arashsoft Aug 11 '17 at 17:10
  • Did NOT work for me either. It adds the attribute but that is not treated as a directive by angular. – oomer Oct 29 '21 at 01:18
  • "it works on Html attributes but not on angular directives (as attribute)" – Reza (one of the comments on https://stackoverflow.com/a/46912525/4997994) – oomer Oct 29 '21 at 01:25
6

Thanks Jason for your suggestion. I took little different approach here. Since I don't want to change the "scope" variable therefore I used "attrs" to check if drag is allowed or not. Following is approach I tool which seems good so far.

Directive code:

app.directive('draggable', function () {
    return {
        // A = attribute, E = Element, C = Class and M = HTML Comment
        restrict: 'A',
        replace:true,
        link: function (scope, element, attrs) {

            if(attrs.allowdrag =="true")
            {
                element.draggable({
                cursor: 'move',
                helper: 'clone',
                class:'drag-file'
                });
            }

        }
    }
});

HTML Code:

<ul> 
         <!--draggable attribute is used as handle to make it draggable using jquery event-->           
        <li  ng-repeat="template in templates" draggable allowdrag="{{userHasPrivilege()}}" >            
                <!--Ohter code part of li tag-->                   

        </li> 

</ul>

Controller is having implementation of userHasPrivilege().

Not sure if this is correct way or not. Looking for thoughts.

joy
  • 3,669
  • 7
  • 38
  • 73
  • If you have the userHasPrivilege() function in the scope then you can just use it in the directive without needing to send it as an attribute of it. – will824 Sep 02 '14 at 19:16
  • 2
    @will824 That defeats the purpose of having the directive as generic as possible, since you'd be forced to define that function wherever the directive is used. – Avram Tudor Sep 05 '14 at 10:45
3

There is no way to directly add or remove an attribute from an element. However, you could create a directive that simply adds the attribute to the element when the condition is met. I've put something together that illustrates the approach.

Demo: http://jsfiddle.net/VQfcP/31/

Directive

myApp.directive('myDirective', function () {
  return {
    restrict: 'A',
    scope: {
        canDrag: '&'
    },
    link: function (scope, el, attrs, controller) {
        /*
$parent.$index is ugly, and it's due to the fact that the ng-repeat is being evaluated 
first, and then the directive is being applied to the result of the current iteration      
of the repeater.  You may be able to clean this by transcluding the repeat into the 
directive, but that may be an inappropriate separation of concerns. 
You will need to figure out the best way to handle this, if you want to use this approach.  
  */
        if (scope.canDrag&& scope.canDrag({idx: scope.$parent.$index})) {
            angular.element(el).attr("draggable", "draggable");
        }
    }
  };
});

HTML

<ul>
    <!-- same deal with $parent -->
    <li ng-repeat="x in [1, 2, 3, 4, 5]" my-directive="true" can-drag="checkPermissions(idx)">{{$parent.x}}</li>
</ul>

Controller

function Ctl($scope) {
   $scope.checkPermissions = function(idx) {
     // do whatever you need to check permissions
     // return true to add the attribute
   }
}
Jason
  • 15,915
  • 3
  • 48
  • 72
0

I used a different approach as the previous examples didn't work for me. Perhaps it has to do with using custom directives? Perhaps someone can clear that up.

In my particular example, I'm using ui-grid, but not all ui-grids should use pagination. I pass in a "paginated" attribute and then $compile the directive based on true/false. Seems pretty brutish but hopefully it can push people in a positive direction.

HTML

<sync-grid service="demand" paginated="true"></sync-grid>

Directive

angular
    .module('app.directives')
    .directive('syncGrid', ['$compile', SyncGrid]);

function SyncGrid($compile){
    var nonPaginatedTemplate = '' +
        '<div>' +
        '   <div ui-grid="gridOptions" class="grid"></div>' +
        '</div>';

    var paginatedTemplate = '' +
        '<div>' +
        '   <div ui-grid="gridOptions" class="grid" ui-grid-pagination></div>' +
        '</div>';


    return {
        link: link,
        restrict: 'E',
        replace: true
    };

    function link(scope, element, attrs) {

        var isPaginated = attrs['paginated'];

        var template = isPaginated ? paginatedTemplate : nonPaginatedTemplate;
        var linkFn = $compile(template);
        var content = linkFn(scope);
        element.append(content);

        // Continue with ui-grid initialization code
        // ...

    }
}
Will Lovett
  • 1,241
  • 3
  • 18
  • 35