Skip to content

conditional parameter for ui-sref directive #1489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bianchimro opened this issue Oct 28, 2014 · 37 comments
Closed

conditional parameter for ui-sref directive #1489

bianchimro opened this issue Oct 28, 2014 · 37 comments

Comments

@bianchimro
Copy link

It would be nice to have an additional parameter to the ui-sref directive to add a condition to the activation of the link, just like ng-disabled acts for a button or an anchor.

For example a ui-sref-if param, if present, could enable the link when a condition is true:

<span ui-sref="app.somestate" ui-sref-if="!editing"> Link to some state </span>

(desired behaviour: link is not active if editing is true)

Another possible approach could be a parameter that disables the link when a condition is true like ui-sref-disabled :

<span ui-sref="app.somestate" ui-sref-disabled="editing"> Link to some state </span>

(same desired behaviour: link is not active if editing is true)

@intellix
Copy link

I was just looking for something like this. Was thinking it would be awesome if you could do ng-if conditions based on if the state is active or inactive. For example, an arrow:

<li>
    <a ui-sref="home">
        <span class="arrow" if-sref-active></span>
        <span class="opposite-arrow" if-sref-inactive></span>
    </a>
</li>

If the sref is currently active, display the arrow. If it's not, show the inactive thing

@27leaves
Copy link

@intellix I would prefer something like adding .ng-click-active (adding .ui-sref-active). But I think that's another use case than @bianchimro's.

@bianchimro
Copy link
Author

@intellix like @creat-or said, mine is another use case.
I'd like to trigger the ui-sref link only when certain conditions apply.

@mallowigi
Copy link

@intellix What about doing it with css and ui-sref-active? ui-sref-active adds the given class to the link when the state is active, so doing something like:

<a ui-sref="home" class="home" ui-sref-active="home_active">
  <span class="arrow"></span>
  <span class="opposite-arrow"></span>
</a>
.home > .arrow {
  display: none;
}
.home > .opposite-arrow {
  display: block; /* or inline or whatever */
}

.home.home_active > .arrow {
  display: block;
}
.home.home_active > .opposite-arrow {
  display: none;
}

@mallowigi
Copy link

Same goes for the top comment.

@bianchimro
Copy link
Author

@mallowigi I don't think so. I want the element itself to be the link and not its children and I don't want the link to be activated on a state basis but on a condition (angular expression) basis,

@mallowigi
Copy link

First of all, what is a disabled link? Is it a link with no URL? I haven't come across such thing.

Second of all, you can use interpolated ui-sref to return different states according to the expression.

@bianchimro
Copy link
Author

@mallowigi

First of all, what is a disabled link? Is it a link with no URL? I haven't come across such thing.

Sorry, probably i am using an incorrect term. But what I mean is just preventing the navigation to the linked url.

Second of all, you can use interpolated ui-sref to return different states according to the expression.

👍 Yes, that would make it, thanks for the suggestion
Anyway the attribute I propose could still be useful, as it could make such expressions more simple. Here is an example:

<span ui-sref="{{ editing?  ' ' :  'app.somestate' }}"> Link to some state </span>

vs

<span ui-sref="app.somestate" ui-sref-disabled="editing"> Link to some state </span>

Probably just a matter of taste.
Thanks!

@mallowigi
Copy link

I think there is a ui-router plugin library out there for all kinds of additional behaviors, ui-state-additions iirc. See if there is such thing there, otherwise propose your own!

@benbenwilde
Copy link

To be clear, WORKAROUND: ui-sref="{{condition ? '.childState' : '.'}}"

@LoicMahieu
Copy link

A nice directive eatClickIf has been proposed on: http://stackoverflow.com/questions/25600071/how-to-achieve-that-ui-sref-be-conditionally-executed

@markbrown4
Copy link

I find this better suited to the controller.

<a ng-click="viewItem(item)"> Link to some state </a>
$scope.viewItem = function(item) {
  if (item.editing) {
    alert('hold on there buddy.');
  }
  else {
    $state.go('item', { id: item.id });
  }
}

@angelarted
Copy link

Wow! @benbenwilde ! Your workaround made my morning!!!!!!!!!!!!!!!!!
Anyway it should be beautifull to have a ui-sref-condition like parameter...
Wainting for it

@benbenwilde
Copy link

@markbrown4 using the controller does not add href to an <a> tag.

@markbrown4
Copy link

Correct. My point was that I find conditional logic like this best left out of the template altogether.

mallowigi has a good question "First of all, what is a disabled link?"

@nateabele
Copy link
Contributor

If you're just trying to disable a click, you should be able to do it pretty simply by either intercepting the event and canceling it with ng-click, or doing something equivalent with a custom directive that can be optionally parameterized.

One of the fundamental design ideas behind directives is that they're composable: using common interfaces (like bindings and DOM events) you can combine directives that don't know anything about one another in order to create new behavior.

This means not overloading a single directive with many different responsibilities, and it also doesn't mean having to write lots of special cases for combining two pieces of otherwise-general behavior.

Anyway, if my suggestion doesn't work (you shouldn't really need to write a scope method...), let me know and we'll address that.

@benbenwilde
Copy link

Disabling the click would be great except that it doesn't remove the href from the <a> tag.

@markbrown4
Copy link

@benbenwilde why is that a problem?

@benbenwilde
Copy link

@markbrown4 I'm not even sure why your asking. Do you think it's ok if the user can't click the link yet they can still hover to see the link or right click to access the link?

Anyways, a scenario this feature would be good for is when you use the same view for two different states, but only want a link in one of the states.

For example, consider two states with the same view where in the first state a given link should work because 1: the user has permission, and 2: the ui-sref tag contains a valid relative state name (e.g. .edit). In the 2nd state, It's the same view, just read-only for the users without permission. If the 2nd state had the same link, the ui-sref wouldn't even be valid because it would be pointing to a relative state that doesn't exist, and even if it did point to the same valid state with an absolute name, the user then would get a 404 or 403 from the server when accessing the link because they don't have permission to load the data for the .edit state.

This would be a nice feature and would come in handy quite a bit, and cut down on duplicate html when u want dynamic links to different states. Especially useful when your <a> tag wraps a div or another element with some non-trivial html which you don't want to duplicate.

The workaround is okay but is not super great because it only evaluates once on load.

@markbrown4
Copy link

"Do you think it's ok if the user can't click the link yet they can still hover to see the link or right click to access the link?"
It depends, for the case above of preventing navigation because the user is editing something then yes, it's fine to allow the user to follow the link in a new tab.

I'm not convinced there should be a link there at all in your permissions use case, but you're right that stripping the href based on a condition might be an easier thing to implement in the view than changing the html. The real question is if ui-sref should be used at all if you don't want to generate a link.

@benbenwilde
Copy link

I do want to generate a link.

@nateabele - you have a feature request and a use case in my previous comment. Thanks for your time!

@benbenwilde
Copy link

@nateabele - Possible implementation would be to add ui-sref-con (for conditional) directive. The directive could merely evaluate the expression on the scope and then do what ui-srefdoes from there. Also would be cool if the ui-sref-con had a watch on the expression so that it could update the link if anything changed. And if the expression evaluates falsy then no href would be added. ui-sref would obviously be preserved just as it is today.

ui-sref-con="user.isAdmin ? 'contacts.detail({ id: contact.id })' : ''"
ui-sref-con="('admin.contacts' | isState) ? '.detail({ id: contact.id })' : ''"

Alternatively the expression could still be put on the ui-sref attribute and the existence of the ui-sref-con next to it would tell it to evaluate it as an expression.

ui-sref="user.isAdmin ? 'contacts.detail({ id: contact.id })' : ''" ui-sref-con
ui-sref="('admin.contacts' | isState) ? '.detail({ id: contact.id })' : ''" ui-sref-con

One more alternative would be to split up the state and condition, assuming we don't have a use case for changing the link to point to multiple different states, but merely turning it on or off. Then ui-sref can check for the ui-sref-con attribute to know whether or not to insert the href. And if ui-sref-con exists it could then watch for changes as well. This is probably my favorite solution.

ui-sref="contacts.detail({ id: contact.id })" ui-sref-con="user.isAdmin"
ui-sref=".detail({ id: contact.id })" ui-sref-con="'admin.contacts' | isState"

Food for thought...

Update - looking back at the first post in this issue, the original idea from @bianchimro is the 3rd solution above, but with ui-sref-if instead of ui-sref-con. I like his idea better.

@nateabele
Copy link
Contributor

@benbenwilde As I've tried to explain here and elsewhere, what you're suggesting is bad design. That kind of thinking results in a combinatorial explosion of unnecessary, case-specific directives.

There are two things I'm willing to do that would give UI-Router the flexibility to accommodate the different use cases presented without devolving the design into a dozen case-specific bolt-ons:

  • As I've stated on other issue threads, there should be a separate directive that explicitly handles dynamic state references; the purpose of a separate directive is so that the syntax can be tailored to that use case, and so that it doesn't compromise performance for people who don't need the extra $watch() calls — let me know if anyone is interested in working on this, and I'll be happy to point you in the right direction & provide feedback
  • Expose the promise returned from $state.go() to a separate directive event tied to uiSref, per this discussion thread

@angelarted
Copy link

Well, I don't think it is bad design, for example I have a case in which I use the same template to show a list of stores an to show the 'no-store' message, when there are no store results for your search or near you.
So I need to specify wether or not the template should add an href, because if I put the href only with the 'id' of the store empty, the details store page will be shown anyway.
Of course I should add a condition in the shown of the details page (as suggested above) but, isn't general directives thought to be usefull for most common issues or needs of developers?
I think this is a very general case.
Otherwise you colud say that even ng-show is not necessary, because you can always do something by your self.
(Sorry for my bad english)

@nateabele
Copy link
Contributor

@angelarted Please don't apologize for your English. Not only is it perfectly understandable, but I guarantee it is better than my Italian. 😉

Otherwise you colud say that even ng-show is not necessary, because you can always do something by your self.

You're right. Let me try to explain my point a little differently. The idea is to develop small directives that can be composed together easily for each specific situation.

In this case, a small, general-purpose directive with a high priority value could be used to intercept and suppress click events. A directive like that could be used with any clickable element, not just ui-sref.

Plus, then you don't have to have to figure out general-purpose answers for the following:

  • Should we add ui-sref-disabled-class to render the link differently when it's disabled?
  • Do we need a flag to determine whether to still show the link's URL in the status bar?
  • Will an attribute like ui-sref-if confuse developers, since it will behave differently from ng-if?
  • What if a user can't validly exit the current state? Do we need to add a canExitState() method to state definitions?
  • What if we want to render state links differently when canExitState() is false? Something like ui-sref-cant-navigate?
  • Someone else in a separate issue asked for an attribute to define a separate class that could be applied to links while resolves are loading — would that interact with this in some way?

The problem with these requests is that they all involve very general things. However, the way we choose to combine those things is unique to each developer's application. Your use case is probably not as general as you think, and supporting everyone's non-general use cases is a very quick path to a very bloated codebase.

The purpose of UI-Router is to provide a set of tools within the context of the Angular platform to define an application as a state machine, and give developers general tools to manage it as such.

Making a ui-sref non-clickable is a very specific thing, that involves two totally unrelated general-purpose directives: `ui-sref, and a directive to make clickable things non-clickable based on some piece of mutable state. Individually, both of those directives (obviously) have valid use cases, but neither one should have to know anything about the other.

@bianchimro
Copy link
Author

Seems like the discussion has somewhat diverged from the initial issue, but this is very interesting anyway.

My two cents:

  • I don't think that having a disabled link is in general bad design and in my case it makes my markup a lot simpler (and yes, this is a very good reason to use it if it cuts down my html).
    In the end, this is not very different from what ng-disabled does for a button.
  • the 'ui-sref-if' i initially proposed should be seen as an attribute (modifier) to the 'ui-sref' directive and not another directive usable by itself.

So, IMHO, this is not a problem of having many directives that can be combined in too many ways or can generate confusion.

I just find this

<span ui-sref="app.somestate" ui-sref-if="editing"> Link to some state </span>

more elegant than this

<span ui-sref="{{ editing?  ' ' :  'app.somestate' }}"> Link to some state </span>

So I'd like to have this shortcut, but this is just a matter of taste.

@nateabele
Copy link
Contributor

I don't think that having a disabled link is in general bad design

Totally agreed, it should just be a separate directive. When I say 'design', I'm talking software architecture, not UI.

the 'ui-sref-if' i initially proposed should be seen as an attribute (modifier) to the 'ui-sref' directive and not another directive usable by itself.

Again, though, whether it's actually a separate directive or not is beside the point. We could invent a thousand of these attributes for every single use case imaginable, and I would never get another release out, because the (already massive) issue queue would be multiple orders of magnitude bigger than it already is. 😛

I just find this
[...]
more elegant than this

Agreed, and ui-sref doesn't support expressions anyway, so that probably won't work the way you intend (and even if it does, it's likely to break on you).

This is pretty elegant, too:

<span ui-sref="app.somestate" my-disabled="!editing">Link to some state</span>

All the more elegant because a custom directive (i.e. myDirective) doesn't have to know anything about uiSref (and vice-versa), and is usable in a much wider range of contexts.

@calendee
Copy link

I'd suggest closing this. The eatClickIf directive suggested by @LoicMahieu works perfectly for this use case.

@bianchimro
Copy link
Author

Closing this issue since a couple of options have been proposed in the discussion.

Thanks everyone.

@nateabele
Copy link
Contributor

I'd suggest closing this. The eatClickIf directive suggested by @LoicMahieu works perfectly for this use case.

This is exactly the kind of thing I'm talking about.

@suryamandraju
Copy link

without using router can we the link the pages .....another way for linking pages????

@ozkary
Copy link

ozkary commented Feb 10, 2016

+1 @benbenwilde for the work around of using
ui-sref="{{condition ? '.childState' : '.'}}"

example: {{item.editable ? 'app.groupsdetail({id: item.$id})' : '.'}}

@httpete
Copy link

httpete commented Jun 23, 2016

ui-sref="{{tile.naSref || '.'}}" is lovely.

@judsonmusic
Copy link

The dot is throwing an error. invalid state.

@PradyumnaR
Copy link

PradyumnaR commented Sep 14, 2017

@benbenwilde Is this work around working Just commenting on your previous comment

can i write some random state instead of .childState like for example {{condition?'welcome':'.'}}

To be clear, WORKAROUND: ui-sref="{{condition ? '.childState' : '.'}}"

@delta711
Copy link

delta711 commented Nov 2, 2018

Duplicate issue #2957 (fixed)

'ng-disabled' is no longer impotent on anchor tags.

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

HTH

@anatolyg
Copy link

For those looking for a working, out-of-the-box solution to dynamic ui-srefs:

<a ui-state="vm.isHighlighted() ? 'home' : 'filters'">{{ vm.isHighlighted() ? 'Home' : 'Filters' }}</a>

read more: https://ui-router.github.io/ng1/docs/1.0.14/modules/directives.html#uistatedirective

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests