Set URL query parameters without state change using Angular ui-router - angularjs

How should I update the address bar URL with a changing query parameter using AngularJS' ui-router to maintain state when refreshing the page?
Currently, I am using $state.transitionTo('search', {q: 'updated search term'}) whenever input changes, but the problem is that this reloads the controller, redraws the window and loses any text input focus.
Is there a way to update stateParams and sync it to the window URL?

I was having trouble with .transitionTo until I updated to ui-router 0.2.14. 0.2.14 properly changes the location bar (without reloading the controller) using a call like this:
$state.transitionTo('search', {q: 'updated search term'}, { notify: false });

edit: Played around with this some more today, and realized that angular ui-router has a similar option as the native routerProvider: "reloadOnSearch".
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#options-1
It's set to true by default, but if you set it to false on your state, then the state change won't happen when the query parameters are changed. You can then call $location.search(params); or $location.search('param', value); and the URL will change, but ui-router won't re-build the entire controller/view. You'll probably also need to listen for the $locationChangeStart event on the root scope to handle back and forward history actions within your app, as these also won't cause state changes.
I'm also listening for the $stateChangeSuccess event on my controller's scope to capture the initial load of the page/route.
There is some discussion on github for using this feature with path changes (not just URL changes): https://github.com/angular-ui/ui-router/issues/125 but I haven't tested that at all since my use case was specific to query string parameters.
The previous version of my answer mentioned this github issue:
https://github.com/angular-ui/ui-router/issues/562
But that's a slightly separate issue, specifically showing a modal of one state over another state without the non-modal state changing. I tried the patch in that issue, but it's clear that it isn't meant for preventing the entire state from reloading on URL change.

Update May 19, 2015
As of ui-router 0.2.15, the issue of reloading the state on query parameter changes has been resolved. However, the new update broke the history API back and forward capabilities with query parameters. I have not been able to find a workaround for that.
Original
Jay's answer didn't work for me, and neither did a ton of other answers. Trying to listen to $locationChangeStart caused problems when trying to go back and forth in the browser history as it would cause me to run code twice: once when the new state changed and another because $loationChangeStart fired.
I tried using reloadOnSearch=false, but that prevented state changes even when the url path changed. So I finally got it to work by doing the following:
When you change $location.search() to update the query parameters, use a "hack" to temporarily disable reloading on search, set query parameters, then re-enable reloading.
$state.current.reloadOnSearch = false;
$location.search('query', [1,2]);
$timeout(function () {
$state.current.reloadOnSearch = undefined;
});
This will ensure that query parameter changes do not reload the state and that url path changes will reload the state properly.
However, this didn't get the browsers history to change the state (needed for knowing when a query parameter changes to re-read the URL) when a query parameter was part of the url. So I also had to add each query parameter's name to the url property of the state.
$locationProvider.html5Mode(true);
$stateProvider
.state('home', {
url: '/?param1&param2&param3',
templateUrl: 'home.html',
controller: 'homeCtrl as home',
});
Any parameter names on the url are optional when listed this way, but any changes to those parameter names will reload the state when hitting the back and forward buttons on the browser.
Hopefully others find this useful and it doesn't take them multiple days to figure out how to do it (like I did).

Related

Refreshing page removes non-URL $stateParam (ui-router)

Im using UI-Router in my application and I have a state parameter where it doesn't really make sense for it to be part of the url. According to the documentation I am creating the parameter without specifying it in the state url, as such:
.state('contact', {
url: "/:contactId/contact",
params: {
otherParam: null
},
templateUrl: 'contacts.html'
})
This works as intended, but I noticed that if I manually refresh the page, the parameter gets reset to the default null.
For example, if I redirect to the state as such:
$state.go('contact', {contactId: 42, otherParam: 11});
Everything works as expected ($stateParams.contactId is 42, $stateParams.otherParam is 11)
However, once I refresh the page, $stateParams.contactId is still 42, but $stateParams.otherParam has been set back to null. Is it possible to persist this non-URL parameter across a browser refresh?
URLs in a SPA are meant for managing the UI state, i.e.: which components to display and with which info (ex. detail component with id). The router resolves the state from this. A good example is to think of the url as something to be sent by email (or whatever) to someone else and expect for their router to resolve the state. So, briefly put I would say no to your question.
Your problem is that you want to persist state of the application that is specific to the browser session, right? Browsers are equipped with mechanisms for that and I'd recommend taking a look into local storage or session storage to solve your problem and retrieve the info you need on the resolve method in your state declaration.
It's sort of possible, but you shouldn't do it. If it is something that should persist on refresh, that means that by definition it SHOULD be in the URL, since you expect it to be a component of the resource.
If you really really want to break convention and do this, you'll need to do something that saves the data in window.name and retrieves it on reload. This is definitely an anti-pattern, and anyone could get access to that data, but like I said, it's possible.

Using UI router, how to pass parameters to url without altering the state?

In my controller, I use $state.go('home', {"parameters": paramValue, "parameter1": another value, "parameters3": another another value}, {reload:true}); and while this does attempt to go to the home state and passes the parameters to the url, the home state view keeps loading. If I copy the url with the parameters passed, and click enter, everything loads properly.
This is the process of going to the home tab.
-> the url changes to reflect that home state is trying to be reached
-> a second reload occurs when the url gets the parameters passed. The parameters do get passed successfully.
-> It keeps trying to load the home view.
The url is correct and works( parameters and all); as I can copy the link and access it. I've also toggled with reload to false.
Should I try transitionTo?
Hey all for anyone else having a similar kind of problem. The answer to stop the unneccessary reloading is to add the notify:false to state.go('main.home', params, {notify:false});

How to differentiate between browser back button and $state.go (dynamic params)?

I need to simply update the URL params in some cases (through code) and not trigger any events. But, I still need to trigger certain actions if the user goes back using the browser back button or if he changes the url params manually.
Prior to UI-Router v1.x, I used to use { notify: false } in combination with the $stateChangeSuccess event for this kind of stuff. But now that the dynamic params are the way to go I can't figure out how to make this work any more.
I have defined all the params of the route as dynamic, and when I execute the $state.go(".", params), the controller does not get refreshed. Which is expected. The $transition.onSuccess does however still fire which I find slightly odd and I don't expect this event to fire in this case.
When I move back with the back button, or if I change a param manually, the same thing happens. The controller doesn't get refreshed and the $transition.onSuccess fires.
My main problem is how do I know one event came from the user, and the other one came from the code? What am I missing here? I checked out the transition param of the onSuccess callback, but couldn't find anything on it that would help me. Is there a different event/hook I should use or is this simply not possible any more with the latest UI-Router?
Thnx.
Seems like I found a way to find out where the transition came from. If somebody has a different/better solution, please post it, I don't find this the neatest solution ever but it works and considering I couldn't find a better one, I'll stick with it until someone shows me a better way.
// The "to:" ensures the event doesn't trigger for other routes
this.$transitions.onSuccess({ to: "your.route" }, (trans) => {
let changedParams = trans._changedParams();
if (trans._options.source === "url" && // transition came from URL change
changedParams && changedParams.length > 0) { // at least one param changed
// do something
}
});

Why does state.go with notify = false cause a future navigation to keep the current page's URL parameters?

There are some pages in my application where I'd like to update the URL with the current state so that the URL can be shared, but I don't want to reload the page because I can enter the new state without a full reload.
I've attempted to do this with $state.go(state, params, { notify: false }), which works... That is, until I navigate to some other page. When I navigate to the next page, the query parameters in my URL (for example, page number) are set on that page.
To be more concrete: I have a StateA where I call $state.go(StateA, params, {notify: false}). All is well. Then I navigate to StateB, but StateB now has parameters that I set on the previous $state.go call for StateA.
I defined an onEnter event for "StateB" and it shows that the state is being entered twice, once with the correct parameters and then again with the incorrect parameters from the previous $state.go call. If I comment out the $state.go with notify = false, the URL of course doesn't update but the future page transition works as expected.
Also note that this happens despite setting in the HTML template ui-sref-opts="{reload: true, inherit: false"} and my version of ui-router is 0.2.15.
I can't be sure since I haven't experienced it or tested it but if you look at this github.io page, you'll see a part on the notify option. It says this:
notify - {boolean=true}, If true will broadcast $stateChangeStart and $stateChangeSuccess events.
So setting it to false would not broadcast those events. This would in turn not cause a change in the url when you move from StateA to StateB.

in ui-router, how to re-resolve only the most local state?

I have the following state tree, using ui-router,
1 login
2 root (abstract, resolves app-prefs, user-prefs)
2.1 home (builds a refresh button, should refresh whatever is being shown)
2.1.1 dashboard (resolves dashboard-prefs)
2.1.2 search (resolves search-prefs)
2.1.3 etc
2.2 etc
From home when user presses refresh button while in XYZ state, I would like to have the XYZ re-entered in such a way that it re-resolves its own XYZ-prefs but not things above in hierarchy. Something like
$state.go("dashboard", dashboardParams, {please-resolve-only-dashboard})
When I try, from home
$state.go("dashboard", dashboardParams, {reload:true})
that causes everything from root downwards to get re-resolved, which is problematic, and expensive, as I need to re-resolve only dashboard-prefs. I can setup a more elaborate scheme in some resolvers to not re-resolve themselves but that might become a task by itself I'm afraid. Is there another, more idiomatic way?
Thanks
There is a wokring plunker
There is a native way how to do that - just change the parameter of the state you want to reload.
To reproduce the above state definition let's have dashboard defined like this:
.state('dashboard', {
parnet: 'home',
url: "^/dashboard",
params: { updater : 1, },
...
})
What we can see, that we do not touch url at all. It will always be without any change just /dashboard
But we introduce really cool feature of the latest version - params: {}. It defines some parameter - updater in our case. Whenever this parameter is sent, and does differ form its current value, this child state (and only this child state) is re-init
Check it more about state params: {} here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider
Now, we can create this reload link:
<a ui-sref="dashboard({updater : ($stateParams.updater + 1) })">reload</a>
And with this incrementation, we can be sure, reload will reload this state
Check it here

Resources