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

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).