55

How do I enable/disable anchor tags using the directive approach?

Example:

  1. while clicking on edit link, create & delete needs to be disabled or grayed out
  2. while clicking on create link, edit & delete needs to be disabled or grayed out

JAVASCRIPT:

    angular.module('ngApp', []).controller('ngCtrl',['$scope', function($scope){

    $scope.create = function(){
      console.log("inside create");
    };

    $scope.edit = function(){
      console.log("inside edit");
    };

    $scope.delete = function(){
    console.log("inside delete");
    };

    }]).directive('a', function() {
       return {
            restrict: 'E',
            link: function(scope, elem, attrs) {
                if(attrs.ngClick || attrs.href === '' || attrs.href === '#'){
                    elem.on('click', function(e){
                        e.preventDefault();
                        if(attrs.ngClick){
                            scope.$eval(attrs.ngClick);
                        }
                    });
                }
            }
       };
    }); 

LINK to CODE

2

11 Answers 11

58

Update: Disabling the href works better in the link function return. Code below has been updated.

aDisabled naturally executes before ngClick because directives are sorted in alphabetical order. When aDisabled is renamed to tagDisabled, the directive does not work.


To "disable" the "a" tag, I'd want the following things:

  1. href links not to be followed when clicked
  2. ngClick events not to fire when clicked
  3. styles changed by adding a disabled class

This directive does this by mimicking the ngDisabled directive. Based on the value of a-disabled directive, all of the above features are toggled.

myApp.directive('aDisabled', function() {
    return {
        compile: function(tElement, tAttrs, transclude) {
            //Disable ngClick
            tAttrs["ngClick"] = "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")";

            //return a link function
            return function (scope, iElement, iAttrs) {

                //Toggle "disabled" to class when aDisabled becomes true
                scope.$watch(iAttrs["aDisabled"], function(newValue) {
                    if (newValue !== undefined) {
                        iElement.toggleClass("disabled", newValue);
                    }
                });

                //Disable href on click
                iElement.on("click", function(e) {
                    if (scope.$eval(iAttrs["aDisabled"])) {
                        e.preventDefault();
                    }
                });
            };
        }
    };
});

Here is a css style that might indicate a disabled tag:

a.disabled {
    color: #AAAAAA;
    cursor: default;
    pointer-events: none;
    text-decoration: none;
}

And here is the code in action, with your example

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

6 Comments

It prevents ng-click from firing when a-disabled is false. It does this by changing the function before it gets processed, effectively turning <a ng-click="doSomething()" a-disabled="isDisabled()"> into <a ng-click="!isDisabled() && doSomething()">
Yes, I understand that line as a whole. But I'm specifically wondering about the part that I isolated in my comment. (a, b) will execute both expression a and b but only return the value of b. In this case expression a is a string, so my understanding is that it's redundant. I left it out when I used this directive and it's still working as expected.
I must have grabbed that bit of code from something else and let it go because I didn't understand the Javascript Comma Operator. I agree, it can safely be removed.
For those who might want to perform a different task if it's disabled, simply add this if (angular.isDefined(iAttrs["aOnDisabledClick"])) scope.$eval(iAttrs["aOnDisabledClick"]); before e.preventDefault() and then add the a-on-disabled-click attribute to your anchor and parse it a function in that scope.
Note that pointer-events: none; disables the event from firing. Thus, in such cases, there's no need to disable it via JS (e.preventDefault();). Browser support is good (caniuse.com/#feat=pointer-events).
|
24

My problem was slightly different: I have anchor tags that define an href, and I want to use ng-disabled to prevent the link from going anywhere when clicked. The solution is to un-set the href when the link is disabled, like this:

<a ng-href="{{isDisabled ? '' : '#/foo'}}"
   ng-disabled="isDisabled">Foo</a>

In this case, ng-disabled is only used for styling the element.

If you want to avoid using unofficial attributes, you'll need to style it yourself:

<style>
a.disabled {
    color: #888;
}
</style>
<a ng-href="{{isDisabled ? '' : '#/foo'}}"
   ng-class="{disabled: isDisabled}">Foo</a>

3 Comments

Ng-diabled won't work for tag a as the tag itself does not accept such attribute.
@BehnazChangizi It works, but may not be future-proof. I added an example that uses a class instead.
This is completely future proof, all he is doing is creating an empty href if the link is disabled.
9

For people not wanting a complicated answer, I used Ng-If to solve this for something similar:

<div style="text-align: center;">
 <a ng-if="ctrl.something != null" href="#" ng-click="ctrl.anchorClicked();">I'm An Anchor</a>
 <span ng-if="ctrl.something == null">I'm just text</span>
</div>

2 Comments

Yeah, that's what I'm doing myself. Not very elegant but simple and useful. :)
what if someone wants to show the <a> tag but disabled.
6

Modifying @Nitin's answer to work with dynamic disabling:

angular.module('myApp').directive('a', function() {
  return {
    restrict: 'E',
    link: function(scope, elem, attrs) {
      elem.on('click', function(e) {
        if (attrs.disabled) {
          e.preventDefault(); // prevent link click
        }
      });
    }
  };
});

This checks the existence of disabled attribute and its value upon every click.

1 Comment

Good idea! And I think if (attrs.disabled) should be enough.
4

Disclaimer:

The OP has made this comment on another answer:

We can have ngDisabled for buttons or input tags; by using CSS we can make the button to look like anchor tag but that doesn't help much! I was more keen on looking how it can be done using directive approach or angular way of doing it?


You can use a variable inside the scope of your controller to disable the links/buttons according to the last button/link that you've clicked on by using ng-click to set the variable at the correct value and ng-disabled to disable the button when needed according to the value in the variable.

I've updated your Plunker to give you an idea.

But basically, it's something like this:

 <div>
       <button ng-click="create()" ng-disabled="state === 'edit'">CREATE</button><br/>
       <button ng-click="edit()" ng-disabled="state === 'create'">EDIT</button><br/>
       <button href="" ng-click="delete()" ng-disabled="state === 'create' || state === 'edit'">DELETE</button>
    </div>

4 Comments

The title suggests an anchor tag, not a button.
@ryeballar Did you read the comment made by the OP on the other answer ?
@Julien : you have to mention in the top of your response that you answered the comment rather than the original question.
@SmartyTwiti Done. Could you remove your downvote now?
4

Have you tried using lazy evaluation of expressions like disabled || someAction()?

Lets assume I defined something like so in my controller:

$scope.disabled = true;

Then I can disabling a link and apply inline styles like so:

<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-style="disabled && { 'background-color': 'rgba(99, 99, 99, 0.5)', }">Higher Level</a>

Or better still disable a link and apply a class like so:

<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-class="{ disabled: disabled }">Higher Level</a>

Note: that you will have a class="disabled" applied to DOM element by that statement.

At this stage you just need to handle what you action GoTo() will do. In my case its as simple as redirect to associated state:

$scope.GoTo = function (state) {
    if (state != undefined && state.length > 0) {
        $window.location.hash = state;
    }
};

Rather than being limited by ngDisabled you are limited by what you decide to do.

With this technique I successfully applied permission level checking to enable or disable user access to certain part of my module.

Simple plunker to demonstrate the point

4 Comments

It's a pretty bad habit to put navigation in a click event -- you can't (for example) open a page in a new tab/window if you don't express links as, well, links.
@Coderer Perhaps you would like to add an example demonstrating a better way to handle this usecase?
The two highest rated answers already use href manipulation instead of click events. And I should clarify: if you want a "do something" button, click events are fine, but use a <button>. If you want a "go somewhere" link, use <a href=...> but make the href meaningful.
@Coderer I totally agree with you. But you have to take into account the way the original question is phrased How do I enable/disable anchor tags. I provided another possibility of how I enable/disable clicks on a <div> tag when a condition is met. And in my case, I had to use a <div> tag because of dependency on third party module. It is fair to point that this approach is not the best if you need to navigate somewhere but as demonstrated there is nothing stopping you from doing so.
1

You can create a custom directive that is somehow similar to ng-disabled and disable a specific set of elements by:

  1. watching the property changes of the custom directive, e.g. my-disabled.
  2. clone the current element without the added event handlers.
  3. add css properties to the cloned element and other attributes or event handlers that will provide the disabled state of an element.
  4. when changes are detected on the watched property, replace the current element with the cloned element.

HTML

   <a my-disabled="disableCreate" href="#" ng-click="disableEdit = true">CREATE</a><br/>
   <a my-disabled="disableEdit" href="#" ng-click="disableCreate = true">EDIT</a><br/>
   <a my-disabled="disableCreate || disableEdit" href="#">DELETE</a><br/>
   <a href="#" ng-click="disableEdit = false; disableCreate = false;">RESET</a>

JAVASCRIPT

directive('myDisabled', function() {
  return {

    link: function(scope, elem, attr) {
      var color = elem.css('color'),
          textDecoration = elem.css('text-decoration'),
          cursor = elem.css('cursor'),
          // double negation for non-boolean attributes e.g. undefined
          currentValue = !!scope.$eval(attr.myDisabled),

          current = elem[0],
          next = elem[0].cloneNode(true);

      var nextElem = angular.element(next);

      nextElem.on('click', function(e) {
        e.preventDefault();
        e.stopPropagation();
      });

      nextElem.css('color', 'gray');
      nextElem.css('text-decoration', 'line-through');
      nextElem.css('cursor', 'not-allowed');
      nextElem.attr('tabindex', -1);

      scope.$watch(attr.myDisabled, function(value) {
        // double negation for non-boolean attributes e.g. undefined
        value = !!value;

        if(currentValue != value) {
          currentValue = value;
          current.parentNode.replaceChild(next, current);
          var temp = current;
          current = next;
          next = temp;
        }

      })
    }
  }
});

Comments

1

Make a toggle function in the respective scope to grey out the link.

First,create the following CSS classes in your .css file.

.disabled {
    pointer-events: none;
    cursor: default;
}

.enabled {
    pointer-events: visible;
    cursor: auto;
}

Add a $scope.state and $scope.toggle variable. Edit your controller in the JS file like:

    $scope.state='on';
    $scope.toggle='enabled';
    $scope.changeState = function () {
                $scope.state = $scope.state === 'on' ? 'off' : 'on';
                $scope.toggleEdit();
            };
    $scope.toggleEdit = function () {
            if ($scope.state === 'on')
                $scope.toggle = 'enabled';
            else
                $scope.toggle = 'disabled';
        };

Now,in the HTML a tags edit as:

<a href="#" ng-click="create()" class="{{toggle}}">CREATE</a><br/>
<a href="#" ng-click="edit()" class="{{toggle}}">EDIT</a><br/>
<a href="#" ng-click="delete()" class="{{toggle}}">DELETE</a>

To avoid the problem of the link disabling itself, change the DOM CSS class at the end of the function.

document.getElementById("create").className = "enabled";

Comments

1

You may, redefine the a tag using angular directive:

angular.module('myApp').directive('a', function() {
  return {
    restrict: 'E',
    link: function(scope, elem, attrs) {
      if ('disabled' in attrs) {
        elem.on('click', function(e) {
          e.preventDefault(); // prevent link click
        });
      }
    }
  };
});

In html:

<a href="nextPage" disabled>Next</a>

2 Comments

You should invert the order between the check on disabledand the elem.on('click') redefinition.
Fewer events, better performance, disabled anchors will always be a subset on all anchors
0

I'd expect anchor tags to lead to a static page with a url. I think that a buttons suits more to your use case, and then you can use ngDisabled to disable it. From the docs: https://docs.angularjs.org/api/ng/directive/ngDisabled

1 Comment

We can have ngDisabled for buttons or input tags; by using CSS we can make the button to look like anchor tag but that doesn't help much! I was more keen on looking how it can be done using directive approach or angular way of doing it?
0

ui-router v1.0.18 introduces support for ng-disabled on anchor tags

Example: <a ui-sref="go" ng-disabled="true">nogo</a>

Comments

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.