-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Abstract States, Non-Abstract Parent States and Default Child States #27
Comments
Yes, that's essentially whats currently implemented, minus the idea of a "default child". In the sample, 'contacts' is abstract, so $state.transitionTo('contacts', ...) simply throws an exception. To go to 'contacts.list' via code you just $state.transitionTo('contacts.list', ...). It is tempting to call 'contacts.list' the "default child" of 'contacts', because it uses URL '', however another way of looking at it (and this is how the current implementation treats it), is that there is no requirement for the URL of a child state to begin with the URL of it's parent state, and all state URLs are completely independent, "absolute" URLs (that get added to $urlRouterProvider). The ability to define state.url relative to the parent state's URL is simply a convenience provided by $stateProvider.state(), because it's very common thing to do (and there is an 'escape' mechanism of prefixing the URL with '^' to tell $sP.state() to treat it as absolute). In the sample app, it would be completely possible to change the URL of contacts.list to '^/list' (i.e. the absolute path '/list'), or to completely remove the URL from the 'contacts' state and instead copy&paste the '/contacts/' prefix into all it's child states URLs. Would you still call contacts.list the default child of contacts in that case? |
Well I do see your point, I guess I just thought if someone can theoretically set a default child via an empty URL then they may also want the same ability when not using urls. I see how it's not as big of a deal as I initially imagined though so I'd say this is more a feature request now than an emergency. Will put in v.2. |
I'd like to have abstract child states - basically for having a different "layout" that would be used. For example, a layout that is the default one, Where "default" and "compact" are different layouts - the reason for me being that there might be two different views for a child layout - ie; one inside of the main layout, and then the same one in a popout window, looking a bit different, but the same view being used. But in my case, the two views are nearly the same, just with some elements "turned off". Right now I've done thing by splitting a lot of stuff, but the base templates for each controller violate DRY, and one change in one template piece that's shared will have to be changed across multiple files. I'm struggling to find a more efficient way to do this. I'm thinking of nested views, but having trouble conceptualizing on how I would organize it, maybe that's the way to go. |
Nevermind, after reading about it some more, I think nested views are what I need to go. Just have to figure out the best way to organize it all. Maybe a ui-view for the body, and just sub-views inside of that view to put it all together. |
After going over this again and again, I am wondering, what exactly is the use-case for "abstract states"?... Taken the example, why not just do all the necessary stuff on |
@nshahzad if you want to have the ability to switch between "compatible" layouts for the state, you could make 'template' or 'templateUrl' a function that switches between those layouts at runtime based on a user setting or a state parameter (the $stateParams object gets passed to the template or templateUrl function). This is probably easier than duplicating all your states. |
@ksperling : "The ability to define state.url relative to the parent state's URL is simply a convenience provided by $stateProvider.state(), because it's very common thing to do (and there is an 'escape' mechanism of prefixing the URL with '^' to tell $sP.state() to treat it as absolute)." This feature (absolute paths when starting with '^' symbol) is very useful (at least in my project). I was thinking that it was the default behavior, but it seems like it's not. I was looking for it in the documentation, but couldn't find it there. But luckily I was able to find this thread. It would be nice if wiki would be updated. In fact, I will try to do it myself if no one minds. |
@kmalin I see that you modified the wiki. Thanks! |
@jeme & @ksperling I'd like to talk more about abstract states as well. I'm not sure they are a user-friendly aspect of ui-router. I brought it up multiple times early on and @ksperling felt strongly about them and I've gone back and forth on how I feel. I've seen new users use them incorrectly and also be very confused about them. I'm getting the notion that we need to look at this feature more deeply. What is its exact purpose that makes it unique and needed in ui-router? What problem is it solving?? |
AFAIK they are used to DRY state definitions, when multiple states share things in common (like abstract classes in OO programming). Maybe you can think of a good way to approach this from the docs? |
Maybe I find them intuitive because I think of states as being in some sense quite similar to classes in an OO language (I mostly do Java by day...) @nateabele is right, they factor out commonalities. As an example, say your app had 'clients' and a 'suppliers' parts with different top level nav. You could define an abstract 'clients' state with a template like this: Now you might say that you should put whatever the default child does into that abstract parent state and make it non-abstract, but that's not a good idea for two reasons:
I think the confusion comes from people thinking about the structure of their app too much in terms of URLs, and from the idea that if a URL like "/foo" maps to state FOO, and "/foo/bar" maps to state BAR, then BAR must be a child of FOO. It's as if each additional "/" means one more nesting level in the state tree. This is not the case! Going with the sample app contacts.details and contacts.list states again, what's going on would probably be easier to understand if the URLs of those two pages were "/contacts/list" and "/contacts/details", and "/contacts" would be an invalid URL. But people (reasonably, given how things traditionally work on the web) expect /contacts to be the URL for the default child. If you were writing static HTML pages, you might have two files, "/contacts/index.html" and "/contacts/details.html". Note that they're both inside the "/contacts/" directory, index.html is on the same nesting level as any other child of contacts, the web server just happens to let you use a shorter URL to access it, so really it's not much of a 'default', it just happens to have a shorter URL. |
@ksperling your example of "/contacts/index.html" and "/contacts/details.html" falls into the old days of the web, and people rarely use such structure in my experience any more... instead they would have "/contacts" and "/contacts/details", which would have to map to "/contacts/index.html" and "/contacts/details/index.html" in the traditional days... Today we can decouple the URL from where the resource is actually located, it may not even be a file anywhere. The thing is, "Abstract" doesn't really serve the explanation you put forth, that's the ability to define a default child that does that. All Abstract really does is to deny you the ability to go to that state... And it's funny we keep saying "default child"... In my mind... there is no default child... there is a number of states and then there is the routes that map to them... so the actual mapping is just:
And so I think we should turn the thing up side down when we explain it, and focus on what that "default" child does, and forget about "abstract" all together. Abstract could go into it's own explanation on how to deny you the ability to activate a particular state. I think we will find that then, no one really ends up using abstract. (unless the state machine only understands a child route of `` if the parent is abstract, in which case this whole post is sort of invalid) |
@jeme I agree that there is fundamentally no default child -- but I think some of the confusion people have with designing their state hierarchies come from assuming that there is... seems like my web 1.0 analogy isn't a good explanation either. You don't HAVE to use abstract, but it lets you do two convenient things:
Given the miniscule amount of code the implementation of this feature takes up, I think it's worthwhile to have it. I don't think it's a very important feature for beginner / getting started docs. If you have a child route of '', the child and the parent will end up with the same route, so you'll only ever hit the parent state unless you transition via code (because the parent route gets added first and routes are evaluated in order). |
Yeah, that's a good idea. Maybe just omit abstract states from the docs for beginners, and save it for a section with more advanced concepts (of which I'm sure there will be several). |
Hmmm.... So... provider
.route('/home', goto fubar)
.route('/home', goto home); Would go to Fubar i hear, that is opposite than what I do... for Angular-routing it would go to home... |
Ideally I would like a construct like that to throw at config time -- it's clearly nonsense to have two identical routes and probably a configuration error. Maybe 'incomplete' would be a more descriptive name for the non-classical-OO crowd, or at least a term that can be used in the description of the feature. |
That depends on what those lines means... In my case there is only one route... it is overwritten as we make the second call, just like in the original router. Which is what I have leaned against... I didn't wan't to throw an error if people find the ability to reroute things during run-time useful... And since I now use that ability... well... :) But that isn't what we are suppose to discuss here. Merely stated it to clarify why my views was as they was. |
I'm still just feeling confused. Not in how Sounds like
It seems like we could achieve the same results but make it more user-friendly if we renamed What do you think about them apples? |
But remember, |
@timkindberg 'navigable' is internal and not part of the public API, plus it only refers to navigation via URL, as opposed to the state being able to be activated at all. I agree with @nateabele that 'abstract' should be familiar for most developers. It means the entity in question "defines stuff but you can't use it directly, you can only inherit from it" |
Alright I'll give in again, you guys drive a hard bargain :). Though just to clarify my suggestion was to not use the existing navigable internal property, but create a new one for the state config object because the name worked well, it just happened to be the same name as the internal property. |
I was just reading through this discussion, and was wondering if you had any input on my situation. I have a register form which is used to register new users based on invitations by an existing user. The new users get a /user/get-started/[hash] url which they click to get started. My thinking was to keep the same URL throughout the whole register process, and only change the templates. Currently I have four states: 'getStarted' When the user navigates to the URL I would like to go to state I have read back and forth through the documentation, but have not found a way of getting to my sub state. Any ideas would be appreciated. |
Just assign your url directly to the validateHash child state, and then transition between the siblings using $state.transitionTo() |
I would like to second @timkindberg's original request to support default child states. The use case is simple. Consider the 'contacts' state which may have three child states 'contacts.list', 'contacts.new', 'contacts.edit'. Other parts of the program just want to transition to 'contacts' without needing to know which child state is the default. Its the job of the router to know that 'contacts.list' is the default landing state for contacts. Maybe in the future a new child 'contacts.favorites' will be implemented and become the new default. Nobody but the contacts routing logic should know this. As it stands right now, I must either navigate by url instead of state name, or use a constant to define the default state for contacts, or some similar semi-ugly solution. |
I think this is the single most important use-case for supporting default child states. Following @kbaltrinic's example, with existing behavior you're essentially forced to manually find and update all relevant instances of The original proposal seems pretty elegant to me, but I'd suggest one minor addition:
Having the ability to set |
I didn't realize #1235 is a duplicate of this. My solution is to simply do I do not think there should be any 'automatic' selection of a child. You either can't navigate to an abstract, or if you do, a default MUST be supplied in the state definition. |
+1 |
I agree with @kbaltrinic and @timkindberg's original request! I have hit this exact use case with list, add and edit. +1 |
I'm in favor of @ProLoser's proposed implementation. We can roll that in once |
@nateabele Hopefully we can support returning a string and (state, param) objects like the following: .state('Account.Security', {
redirectTo: 'Account.Security.ChangePassword', // OR
redirectTo: { state: 'Account.Security.ChangePassword', params: { id: 123} }, // OR
redirectTo: ['$state', 'authenticationService', function($state, authenticationService){
//Some logic
return 'Account.Security.ChangePassword'; // OR
return { state: 'Account.Security.ChangePassword', params: { id: 123} };
}]
}); Personally I prefer redirectTo over abstract since it better explains what is happening. |
@acollard yes and no. I feel it makes sense of you read it as "on entering this state directly" but it's not as intuitive when you think about going directly to a child state. I would prefer documenting it as "the abstract key can be true or you can give it a redirectTo string or function" |
The |
There are two things which would be nice to have out-of-the-box.
It seems like both of these things are possible using the |
I think allowing you to create a redirect callback will allow you to do the
|
Probably so, but currently history management is weak bordering on non-existent, so that particular use case is part of a larger set of things that need to be considered. |
I think FWIW, it's pretty easy to add basic support for a BTW, one might also consider specifying the default child state configuration by assigning a string value to the .state({
abstract: '.defaultChild'
}) |
UI-Router 1.0 preview is available: https://github.com/angular-ui/ui-router/tree/feature-1.0 |
God I am going to have to review this thoroughly because I don't like the component router |
We've been getting a lot of that. |
Is there an elegant version of this: #948 (comment) that includes using resolved data? I'm racking my brain trying to come up with a way on the current router to have a default child based on resolved values. I'm getting a bit of redirect-loop-complexity with my old implementation thus far. |
I do like to think that something like the following could be implemented, native to UI-Router: |
@cScarlson in 1.0 that is super easy: http://plnkr.co/edit/YskNwwmQPm6AA6PNj0Cz?p=preview |
@christopherthielen: sorry to comment on this old post, but I'm thinking this answer is just what I need. When I try your code though, $state is always null, so the Can you maybe give me a hint what to look for in such a case? |
I know we went back and forth on this several times already, but I think I may have found a need for this feature again.
It seems as though we are trying to get to a place where states can function completely on their own without any url routing (correct?). So how would we know which child state to load automatically without specifying a
url
of''
? Seems like we would need adefault
boolean property or something similar.Let me also attempt to define abstract states, and non-abstract parent states. Please let me know if I'm on the right track.
Abstract State
transitionTo
method) without activating one of its children.url
ordefault
property)default
child state is set, first child state found will be used as defaultNon-Abstract Parent State
transitionTo
method or url), though any view directives will not be populated until navigation to on of it's child state OR one of its child states is set as default child (either with emptyurl
ordefault
property)The text was updated successfully, but these errors were encountered: