Skip to content
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

transitionTo with params #175

Closed
kpgarrod opened this issue Jun 14, 2013 · 34 comments
Closed

transitionTo with params #175

kpgarrod opened this issue Jun 14, 2013 · 34 comments
Labels

Comments

@kpgarrod
Copy link

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
Copy link
Contributor

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
Copy link
Author

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 #108#108.
However, the trailing slash must be included.


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

Keith Garrod

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

@adambabik
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

ajoslin commented Jun 14, 2013

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

@laurelnaiad
Copy link

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
Copy link
Contributor

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
Copy link

xixixao commented Jun 20, 2013

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
Copy link

(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
Copy link

xixixao commented Jun 21, 2013

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
Copy link

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
Copy link

@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
Copy link

@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 😄, 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
Copy link
Contributor

@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
Copy link

xixixao commented Jun 22, 2013

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
Copy link

@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
Copy link
Author

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//issues/175#issuecomment-19862997
.

Keith Garrod

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

@ksperling
Copy link
Contributor

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

@kpgarrod
Copy link
Author

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//issues/175#issuecomment-20164966
.

Keith Garrod

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

@damrbaby
Copy link

damrbaby commented Jul 1, 2013

@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
Copy link

damrbaby commented Jul 1, 2013

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
Copy link
Contributor

@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
Copy link

damrbaby commented Jul 2, 2013

@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
Copy link
Contributor

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
Copy link

damrbaby commented Jul 2, 2013

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
Copy link
Contributor

@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
Copy link
Contributor

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

@damrbaby
Copy link

damrbaby commented Jul 2, 2013

@timkindberg what about dynamic query paramaters?

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

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

@timkindberg
Copy link
Contributor

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
Copy link
Contributor

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
Copy link

damrbaby commented Jul 2, 2013

@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
Copy link
Contributor

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

@gsklee
Copy link

gsklee commented Jul 11, 2013

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
Copy link
Contributor

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

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

No branches or pull requests

10 participants