angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

transitionTo with params #175

Closed kpgarrod closed 11 years ago

kpgarrod commented 11 years ago

I am having difficulty passing parameters with $state.transitionTo.

My states are defined like this:

.state('clients', {
  abstract: true,
  templateUrl: 'views/clients/clients.html',
  controller: 'ClientsCtrl'
})
.state('clients.leads', {
  url: '/leads',
  templateUrl: 'views/clients/leads/list.html',
  controller: 'LeadsCtrl'
})
.state('clients.prospects', {
  url: '/prospects',
  templateUrl: 'views/clients/prospects/prospects.html',
  controller: 'ProspectsCtrl'
})

In LeadsCtrl I have a function:

$scope.myFunction = function () {
  $state.transitionTo('clients.prospects', {a:'1'});
};

The function correctly transfers state to clients.prospects and ProspectsCtrl executes, but when I do:

console.log("ProspectsCtrl StateParams: ", $stateParams.a)

in ProspectsCtrl, the result is undefined.

What am I missing here please?

adambabik commented 11 years ago

In your state definition, a url property doesn't contain any parameters. Try add one:

.state('clients.prospects', {
  url: '/prospects/:id',
  templateUrl: 'views/clients/prospects/prospects.html',
  controller: 'ProspectsCtrl'
})

Then, you can access it in this way:

console.log("ProspectsCtrl StateParams: ", $stateParams.id)

Parameters that are not defined are not passed to $stateParam. More info in docs.

If you want this parameter to be optional, check issue number #108. However, the trailing slash must be included.

kpgarrod commented 11 years ago

Thanks for the rapid response! I really appreciate you taking the time.

Does that mean that I can't add anything to the state which is not included in a url, or have I misunderstood something?

The url doesn't see to be the ideal place to store application state for an app with multiple views on a page. I thought that was the problem that ui-router was addressing. If the state data still has to be stored in the url, how is it helping?

I'm still trying to get my head around Angular and ui-router, so any pointers would be welcome.

Thanks again.

On 14 June 2013 11:40, Adam Babik notifications@github.com wrote:

In your state definition, a url property doesn't contain any parameters. Try add one:

.state('clients.prospects', { url: '/prospects/:id', templateUrl: 'views/clients/prospects/prospects.html', controller: 'ProspectsCtrl'})

Then, you can access it in this way:

console.log("ProspectsCtrl StateParams: ", $stateParams.id)

Parameters that are not defined are not passed to $stateParam. More info in docs https://github.com/angular-ui/ui-router/wiki/URL-Routing.

If you want this parameter to be optional, check issue number #108https://github.com/angular-ui/ui-router/issues/108. However, the trailing slash must be included.

— Reply to this email directly or view it on GitHubhttps://github.com/angular-ui/ui-router/issues/175#issuecomment-19448004 .

Keith Garrod

Tel: +27-83-3000-988 Fax: +27-86-5757781

adambabik commented 11 years ago

You're welcome. That's what Github is, not only a tool to share open source code, but also a platform to post issues, discuss ideas and so on. ui-router has helped me a lot so now I'd like to help the community answering questions :)

I'd like to notice that I'm not a guy from the angular-ui team so take my explanation with a pinch of salt. I'm following the project just for about a month.

Does that mean that I can't add anything to the state which is not included in a url [...]

Actually, you can but it's a bad idea. You have an access to the state configuration in this way: $state.current and it's a plain object.

If you want to keep some additional stateless data, which is not connected with the URL and is gone after refreshing the page, you may want to create custom service. See http://docs.angularjs.org/api/AUTO.$provide#service.

ui-router is a library to handle navigation and UI of your app. It works closely with the URL of a page and changes UI when the URL changes. It's like you go to a new place and ui-router changes the surroundings.

If the state data still has to be stored in the url, how is it helping?

It helps in this way that it figures out which view should be displayed, which resources should be retrieved and which controller should be run. Hence, the URL must have enough information to do that and it must be unambiguous.

Ask yourself a question what user should see if he types /prospects. It cannot display different things at the same time. It seems legit to have /prospects/:id to display a specified prospect.

Hope it clears things up.

timkindberg commented 11 years ago

There is a way to specify params without URL. It's not documented and needs to be. Pretty sure it's a params:[] property on state config object. Accepts an array if params.

ajoslin commented 11 years ago

It would be nice to use transitionTo to define parameters that aren't necessarily in the url, though. It just makes sense.

laurelnaiad commented 11 years ago

At present, you can either define parameters in the url or by using the params array, but not both. In fact, if you specify a params array, your state can't have a url at all. States that can't be accessed by url are good for cases where the user is in the middle of a workflow and the application is controlling the state transitions.

If you do this, the state will still have access to the parameters of its ancestors, so if you want to put additional parameters into mix during a workflow, you can define a child state that has those parameters and transition into it with transitionTo. The parameters that came from the urls (or params arrays) of the ancestor states will still be in $stateParams.

ksperling commented 11 years ago

Hm I suppose having "hidden" parameters in addition to the "static" (currently implemented) and "dynamic" (planned) parameters could make sense. Even though I'm not entirely sure what you'd use them for -- essentially they'd all lose their values when the state gets reloaded from a URL.

xixixao commented 11 years ago

What about "previousUrl" when redirecting to a login view? (I really disagree with the argument that every single page app should have a modal to let users log in). I use the rootScope as a dirty hack right now.

laurelnaiad commented 11 years ago

(I really disagree with the argument that every single page app should have a modal to let users log in). I use the rootScope as a dirty hack right now.

I too don't like to play good cop/bad cop with authorization and routing in the state machine itself. It's easier if the right service interjects itself when the time comes when the user isn't authorized to do something.

Enter angular-http-auth . If you start from the premise that if you can't retrieve it from the server unless you're authorized by the server, then this module may come in handy. If you use it, you have the opportunity to inject a workflow through an event that pops out at the app. The event gives you a chance to supercede everything that's going on in your ui to handle the need to log in, and then retries the requests that started the request. I think it's a great leg up on using server-side security while providing the most important hooks to the client in order to prompt a login. I think with a little tweaking to default behaviors and some options being made configurable, that angular-http-auth is a really valuable asset.

What about "previousUrl" when redirecting to a login view?

I've thought about having some sort of "preconditions" support in a router. Essentially it would be $state allowing for subroutines of login-type things to take place, still using the service, and then pick up where it left off. Essentially queuing the original request behind the preemptive routing that is necessary to meet one or more of the preconditions). As it stands, $state tries to shrug off requests that are pre-empted by newer events that trickle down from the $location service, in favor of completing their transitionTo() processes as they are called.

I recall that @jeme has some additional support for the transition aspects of the state machine in https://github.com/dotJEM/angular-routing ...

xixixao commented 11 years ago

I looked at angular-http-auth but haven't used it since it didn't provide me with the bulk of what I needed (essentially a client-side redirect to login page and redirect back to original) and since I am navigating back from the view I wanted to simply reload the view and all the required requests (I don't see how I would prevent firing them twice - from the interceptor and from loading the state).

damrbaby commented 11 years ago

This just bit me because of the difficulty in passing a URL as a param. It's doesn't seem possible to even pass an encoded URL as part of a state param, and also doesn't seem possible to set $location.query with a URL as a param.

laurelnaiad commented 11 years ago

@xixixao, I'm still not sure I know how to do authorized client-side stuff the "right way"... but I saw this last night in angular discussion and you might be interested. This is the thread and this is the gist of something that looks like a good start on doing the authorization thing on client side .

Personally, I'd like to see a strategy supported in ui-router through a "preconditions" array, but that gist is another possible way to fly.

@damrbaby, you might want to frame your problem as its own question with some detail. We're deep in the weeds here and the situation you're experiencing might be unrelated -- it's difficult to tell from your comment. Examples on plunker are of course an ideal way to demonstrate issues when they are possible. HTHs!

damrbaby commented 11 years ago

@stu-salsbury Sorry if I wasn't clear... what I meant was that something like this:

.state('upload', url: { '/images/:upload_url' })

Is difficult to do when the $stateParam itself is a URL. If you need to pass URLs around it won't work like this:

$state.transitionTo('upload', { upload_url: 'http://myurl/image.png' })

Cause then you wind up with a URL that looks like this: http://localhost/images/http://myurl/image.png

So basically i feel like a contraint to ui-router is that it's doesn't seem possible to pass a URL as a $stateParam (url encoding it didn't help). Ideally a URL should be passed as a query parameter but ui-router does not support query params in the URL (setting $location.search({ upload_url: 'http://myurl/image.png }) gets overridden during the state transition).

I'll be happy to open a separate issue if you suggest :smile:, but I felt like it's related to this cause if ui-router supported setting a query string in the URL this wouldn't be an issue.

ksperling commented 11 years ago

@damrbaby state parameters automatically have decodeURIComponent called on them (or encode... when synthesizing a URL), so you just need to percent-encode it properly and it should work.

xixixao commented 11 years ago

I think this just shows that people have different needs based on the situation and it would be nice to allow all of them (dynamic params, hidden params, query params), imo.

laurelnaiad commented 11 years ago

@xixixao -- I think some of the issues you're finding are perfect examples of how ui-router is evolving and how it's going to be a "better way" to deal with "routing" and more importantly state management than what is built into angular today.

The $state service is coming to a point where it won't care what URLs are used to refer to states and where it doesn't need them to get from state to state or know its parameters. This is huge for angular apps because they can relegate URLs to those things that are externally interesting, but not involved in how the app functions.

There are some hiccoughs along the way -- places where urls were assumed when state parameters should have been the default go-to way to know what a state is composed of... these will work themselves out with the the strong ui-router team on the task... they're on it.

I think what's important for wider adoption of $state is to get to a point where all of these kinks that assume the presence of a URL are worked out, and then document usage of $state in a way that reflects the lack of need to handle URLs internally, while also demonstrating how states can define URLs such that the outside world can refer to them. It'll be great!

You're ahead of the curve on this... that's a great place to be!

kpgarrod commented 11 years ago

Thanks for all the input. What I needed was the params on the state.config. Now I am trying to resolve some promises in the state config and I need to use a param that I have defined on the state. Any suggestions on how to do that please?

This is a sanitised version of what I am trying:

.state('clients.details', { abstract: true, params: ['clientId'], templateUrl: details.html', controller: 'ClientDetailsCtrl', resolve: { client: function(Client){ return Client(params['clientId']); } } })

Thanks in advance.

On 22 June 2013 21:07, Stu Salsbury notifications@github.com wrote:

@xixixao https://github.com/xixixao -- I think some of the issues you're finding are perfect examples of how ui-router is evolving and how it's going to be a "better way" to deal with "routing" and more importantly state management than what is built into angular today.

The $state service is coming to a point where it won't care what URLs are used to refer to states and where it doesn't need them to get from state to state or know its parameters. This is huge for angular apps because they can relegate URLs to those things that are externally interesting, but not involved in how the app functions.

There are some hiccoughs along the way -- places where urls were assumed when state parameters should have been the default go-to way to know what a state is composed of... these will work themselves out with the the strong ui-router team on the task... they're on it.

I think what's important for wider adoption of $state is to get to a point where all of these kinks that assume the presence of a URL are worked out, and then document usage of $state in a way that reflects the lack of need to handle URLs internally, while also demonstrating how states can define URLs such that the outside world can refer to them. It'll be great!

— Reply to this email directly or view it on GitHubhttps://github.com/angular-ui/ui-router/issues/175#issuecomment-19862997 .

Keith Garrod

Tel: +27-83-3000-988 Fax: +27-86-5757781

ksperling commented 11 years ago

@kpgarrod just add $stateParams as a parameter to the function

kpgarrod commented 11 years ago

Thanks, got it.

On 28 June 2013 03:04, Karsten Sperling notifications@github.com wrote:

@kpgarrod https://github.com/kpgarrod just add $stateParams as a parameter to the function

— Reply to this email directly or view it on GitHubhttps://github.com/angular-ui/ui-router/issues/175#issuecomment-20164966 .

Keith Garrod

Tel: +27-83-3000-988 Fax: +27-86-5757781

damrbaby commented 11 years ago

@ksperling @stu-salsbury

Sorry for the delayed response. I just created a plunkr to illustrate my point: http://plnkr.co/edit/8F3Kn6hvAfusNArCMb9U?p=preview

Basically, yes it does work (momentarily) to have a URL as a state param. But as soon as the state resolves itself the brower redirects to the application root (in Chrome at least) and the app will no longer be bootsrapped (unless you have a default route in place).

In this example as soon as the state goes to /url/http://google.com it resolves to /

Basically it is currently not possible to pass a URL around as a parameter as far as I can tell. Should we open a new issue with this?

damrbaby commented 11 years ago

And just wanted to follow up with a link to a slightly modified version of the previous plunker... only difference is I'm percent-encoding the URL first like @ksperling suggested, which results in the same behavior.

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

ksperling commented 11 years ago

@damrbaby the problem with your plunkr is that you're missing a preventDefault. If you change the 'a' to a 'span' it works as expected: http://plnkr.co/edit/7LQsDrM0I2yDJrgWgxLr?p=preview

damrbaby commented 11 years ago

@ksperling Interesting. However if you were to go to that state directly it won't work. I added a "Reload window" link to show what I mean in this plunkr: http://plnkr.co/edit/fh4r97DSArNFpgIEaJzP?p=preview

After reloading the window the expected behavior is that it would remain on the same state.

ksperling commented 11 years ago

Hm turns out encodeUrlComponent is only used for query parameters at the moment, not path parameters. That should probably be fixed. I seem to recall I didn't do that because core $route treats parameters as 'raw'.

Medium term we'll support typed parameters, where the type defines the encoding/decoding, and have built-in types including 'raw' and 'string'... It seems like 'string' (i.e. auto encoding/decoding) would be the best default, so a break with how $routeProvider handles things by default.

Or should 'raw' be the default for backwards compatibility? Especially in cases where there is a custom regex we should probably not do any additional encoding/decoding.

damrbaby commented 11 years ago

Since $state requires you to pass all paramaters in the path (which is different from $route) I think it should encode / decode them by default. For states with regular expressions would it be possible to decode the path prior to testing the regex?

timkindberg commented 11 years ago

@damrbaby are you aware that parameters can also be placed after the path as query parameters?

url: /myurl?myparam

I'm not sure you can do those ones as regex though...

timkindberg commented 11 years ago

Guess I need to make that clearer in the docs...

damrbaby commented 11 years ago

@timkindberg what about dynamic query paramaters?

$stateProvider.state('url', { url: '/url?url=:url })

$state.transitionTo('url', { url: 'http://google.com' })

timkindberg commented 11 years ago

Query parameters are always dynamic, you wouldn't put the '=:url' part. So you'd just do this:

$stateProvider.state('url', { url: '/url?url' })

$state.transitionTo('url', { url: 'http://google.com' })

Also I updated the Routing wiki page, hopefully things are more clear now. https://github.com/angular-ui/ui-router/wiki/URL-Routing

ksperling commented 11 years ago

Note that query parameters aren't "dynamic" in the sense of the "dynamic parameters" feature that's on the ui-router roadmap that would do two-way binding between $stateParams and URL without triggering a state transition.

Other than the different syntax for specifying the in the URL pattern (and the difference regarding encodeUriComponent), query parameters behave pretty much identical to path parameters at the moment.

damrbaby commented 11 years ago

@timkindberg didn't know about the query params, thanks!

So it looks like it works fine that way: http://plnkr.co/edit/z7XKJsfUqnVEEcRQ8F3O?p=preview

timkindberg commented 11 years ago

Thank you for helping me find a gaping hole in my docs!

gsklee commented 11 years ago

Just got tripped over by this as well... Perhaps there should be a big, red and bold line in the doc stating that "if a passed param does not appear inside the routeUrl template, it will be removed"?

nateabele commented 11 years ago

Follow any additional discussion of typed parameters here: #125.