[angularjs] Enable/Disable Anchor Tags using AngularJS

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

This question is related to angularjs angularjs-directive

The answer is


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";

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>

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


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;
        }

      })
    }
  }
});

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.


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


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

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


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


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>

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>

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>