62

I want to validate certain condition before the browser follow the link dynamically created by ui-router.

I was looking into $rootscope.$on('$stateChangeStart', ..) but I have no access to the controller.$scope from there. I also need to use this in several places in the application and would be cumbersome.

Keep in mind that ui-sref is linked to ui-sref-active (work together), so i can't remove ui-sref and, by say, to use $state.$go('some-state') inside a function called with ng-click.

The condition should be evaluated inside a $scope function and on on-click event (before-transition with the ability to cancel it)

I need something like this:

<li ui-sref-active="active">
      <a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>

I tried:

<li ui-sref-active="active">
      <a ui-sref="somestate" ng-click="$event.preventDefault()">Go Somestate</a>
</li>

<li ui-sref-active="active">
      <a ui-sref="somestate" ng-click="$event.stopImmediatePropagation()">Go Somestate</a>
</li>

And

<li ui-sref-active="active">
    <a ui-sref="somestate">
       <span ng-click="$event.stopPropagation();">Go Somestate</span>
    </a>
</li>

Even

<li ui-sref-active="active">
      <a ui-sref="somestate" onclick="return false;">Go Somestate</a>
</li>

But does not work.

SANDBOX

rnrneverdies
  • 15,243
  • 9
  • 65
  • 95
  • Basically you want to test a certain condition when a user visits that link, and then decide whether he can continue with that link or not? – ryeballar Sep 01 '14 at 08:15
  • yes, but at the application level there is a state change. link not literally. need so I can prevent it from happening. – rnrneverdies Sep 01 '14 at 14:48

9 Answers9

69

This answer inspired me to create a directive that allows me to interrupt the chain of events that end up changing state. For convenience and other uses also prevents the execution of ng-click on the same element.

javascript

module.directive('eatClickIf', ['$parse', '$rootScope',
  function($parse, $rootScope) {
    return {
      // this ensure eatClickIf be compiled before ngClick
      priority: 100,
      restrict: 'A',
      compile: function($element, attr) {
        var fn = $parse(attr.eatClickIf);
        return {
          pre: function link(scope, element) {
            var eventName = 'click';
            element.on(eventName, function(event) {
              var callback = function() {
                if (fn(scope, {$event: event})) {
                  // prevents ng-click to be executed
                  event.stopImmediatePropagation();
                  // prevents href 
                  event.preventDefault();
                  return false;
                }
              };
              if ($rootScope.$$phase) {
                scope.$evalAsync(callback);
              } else {
                scope.$apply(callback);
              }
            });
          },
          post: function() {}
        }
      }
    }
  }
]);

html

<li ui-sref-active="active">
      <a ui-sref="somestate" eat-click-if="!model.isValid()">Go Somestate</a>
</li>

PLUNKER

Community
  • 1
  • 1
rnrneverdies
  • 15,243
  • 9
  • 65
  • 95
  • 4
    They should really include this functionality. It could be renamed to `ui-sref-disable`. Consider opening a pull request, @OneWay! – Andy Fleming Dec 16 '14 at 05:52
  • 5
    This is just awesome. Thanks for this tid bit. I agree with @DesignerGuy, it should be `ui-sref-disabled` – scott Feb 04 '15 at 03:36
  • @DesignerGuy ...we did it, and this is what they answered us to do: copy this module :-D :-D :-D ---- here the link ----> https://github.com/angular-ui/ui-router/issues/1489#issuecomment-91098823 – Simona Adriani Dec 09 '15 at 14:45
  • This is a workaround. There are simpler, effective, generic ways to handle this. Refer to any other answer. – André Werlang Dec 12 '16 at 21:21
  • For me, it's not working when we enable minified version of this directive :( – Jijo Thomas Jan 25 '17 at 04:54
  • 1
    This doesn't look like it would prevent a user from focusing on the link using the keyboard. What happens with screen readers? – elliottregan Aug 04 '17 at 16:43
28

You can use a scope function that will either returns :

  • no state

  • an existing state

    like so :

HTML :

<li ui-sref-active="active">
      <a ui-sref="{{checkCondition()}}">Go Somestate</a>
</li>

JS scope :

$scope.checkCondition = function() {
    return model.validate()
        ? 'someState'
        : '-' // hack: must return a non-empty string to prevent JS console error
}

href attribute will be created only when the function returns an existing state string.

Alternatively, you could do a (ugly) :

<li ui-sref-active="active">
      <a ui-sref="somestate" ng-if="model.validate()">Go Somestate</a>
      <span ng-if="!model.validate()">Go Somestate</span>
</li>

Hope this helps

Community
  • 1
  • 1
Jscti
  • 14,096
  • 4
  • 62
  • 87
  • 3
    i still get a console error when trying your `checkCondition()` method. It says `Could not resolve '-' from state 'home'` – chovy Dec 16 '14 at 04:03
  • 5
    I like this answer because it is the fastest way to done. `ui-sref="{{condition ? '.childState' : '.'}}"` – Ben Wilde Jan 13 '15 at 23:05
  • 3
    @chovy a '.' by itself instead of '-' will return the current state – Ben Wilde Jan 13 '15 at 23:06
  • To prevent an error: $rootScope.$on("$stateNotFound", function (event, unfoundState, fromState, fromParams) { if (unfoundState.to == '-') { event.preventDefault(); return; }}); – Rick Love Oct 02 '15 at 20:27
  • @chovy I had the same issue and put it in `href="checkCondition()"` instead after returning `return $state.href('login', {}, {absolute: true});` – timhc22 Apr 13 '16 at 14:59
12

The easiest workaround to conditionally achieve routing without tinkering with directives, scope etc was a workaround i found here - https://github.com/angular-ui/ui-router/issues/1489

<a ui-sref="{{condition ? '.childState' : '.'}}"> Conditional Link </a>

Shankar ARUL
  • 12,642
  • 11
  • 68
  • 69
7

You can always double up on the element and show/hide conditionally

    <li ui-sref-active="active">
          <a ng-show="condition1" style="color: grey">Start</a>
          <a ng-hide="condition1" ui-sref="start">Start</a>
    </li>

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

Chris T
  • 8,186
  • 2
  • 29
  • 39
  • This is not helpful to me, because I don't want to gray-out the button. I simply want to popup an alert that says "You are about to navigate away, are you sure?" – John Henckel Apr 12 '16 at 13:25
3

No need for complicated directives or hacks. The following works fine and allows for specific handling on click of non-sref items:

<a 
  ng-repeat="item in items" ui-sref="{{item.sref || '-'}}" 
  ng-click="$ctrl.click(item, $event)"
>...</a>

And in the controller, a simple click handler for the items which don't have an item.sref:

this.click = function(item, event) {
  if (!item.sref) {
    event.preventDefault();
    //do something else
  }
};
Adam Reis
  • 4,165
  • 1
  • 44
  • 35
2

Based on the answers to How to dynamically set the value of ui-sref you can create a function in your scope for building the URL:

$scope.buildUrl = function() {
  return $state.href('somestate', {someParam: 'someValue});
};

And then conditionally append it to the link with ng-href

<a ng-href="{{ someCondition ? buildUrl() : undefined }}">Link</a>

As you can see in the demo below, ng-href does not add the href attribute if value is negative.

angular.module('app', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
  <a ng-href="{{ condition ? 'http://thecatapi.com/api/images/get?format=src&type=gif' : undefined}}">This is the link</a>
  <br>
  <label for="checkbox">
    <input type="checkbox" id="checkbox" ng-model="condition">
    Link active?
  </label>
</div>
Community
  • 1
  • 1
fracz
  • 20,536
  • 18
  • 103
  • 149
1

I know this is an old question, but for future reference I wanted to offer an alternative solution since I didn't see it in any of the answers so far.

Desired:

<li ui-sref-active="active">
    <a ui-sref="somestate" ui-sref-if="model.validate()">Go Somestate</a>
</li>

Potential solution (template):

<li ng-class="{ active: state.current.name === 'somestate' }">
    <a ng-click="navigateToState()">Go Somestate</a>
</li>

And in the controller:

$scope.state = $state;
$scope.navigateToState = navigateToState;

function navigateToState() {
  if ($scope.model.valid) {
    $state.go('somestate');
  }
}
David Meza
  • 3,080
  • 3
  • 16
  • 18
0

Possible solution for those who still need ng-click working on ui-sref component or its parents.

My solution is to use href instead of ui-sref and to modify Emanuel's directive a bit to be able to stop href and ng-click calls separately.

Planker.

Though it has a few restrictions:

  • will not work with ui-sref
  • you should have different urls for each state because of previous restriction
  • ui-sref-active will not work either
andrfas
  • 1,320
  • 2
  • 10
  • 16
0

For the binary case (link is either enabled or disabled), it "now" (since ~2018) works like this (prevents the click and sets it to disabled):

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

and for other tags as well:

<span ui-sref="go" ng-disabled="true">nogo</span>
jmk
  • 1,778
  • 15
  • 18