What is the simplest way to keep query parameters in AngularJS when changing views?

Problem

Is there a simple way to keep the query parameters when linking between views i Angular?

When I use ngHref, the query parameters are lost:

<a ng-href='OtherPage'>Other page</a>

I navigate from this url http://localhost/Page.html#/?stat=77698382, to this: http://localhost/Page.html#/OtherPage.

This solution works, but it requires a function with an explicit redirect:

<a ng-click='redirectToOtherPage()'>Other page</a> 

$scope.redirectToOrderListPage = function() {
    $location.path('OtherPage');
};

Is there a more declarative way to do it?

Problem courtesy of: andersh

Solution

those two are slightly different from one another. I think the code you're looking for is:

<a ng-href='#/OtherPage'>Other page</a>

http://localhost/Page.html#/?stat=77698382 seems off.

You need to put your querystrings before the hash tag like so:

http://localhost/Page.html?stat=77698382#/

Then as you navigate around your app, the queryString will not be replaced.

Solution courtesy of: Mathew Berg

Discussion

Here is some sample code in TypeScript, Angular 1. It parses the old query parameters and merges them into the new ones. (Based on Pankaj's answer.)

  $rootScope.$on('$locationChangeStart', (event:IAngularEvent, newUrl:string, oldUrl:string) => {

    let oldPath = oldUrl.replace(/^([^?#])+/, '$1');
    if (oldUrl.indexOf('?') >= 0 && oldPath !== $location.path()) {
      let queryString =  oldUrl.split('?')[1];
      if (queryString.indexOf('#') >= 0) {
        queryString = queryString.split('#')[0];
      }

      let oldParamStrings:string[] = queryString.split('&');
      let oldParams:{[key:string]:string} = {};
      oldParamStrings.forEach((oldParamString:string) => {
        let keyValue:string[] = oldParamString.split('=');
        oldParams[decodeURIComponent(keyValue[0])] = decodeURIComponent(keyValue[1]);
      });

      let newParams = $location.search();
      if (!angular.equals(newParams, oldParams)) {
        angular.merge(newParams, oldParams);
        $location.search(newParams);
        return;
      }
    }

    // ANY OTHER CODE
    //////
  });
Discussion courtesy of: Kunal

I was facing similar issue. I had to maintain querystring across multiple views. And, i also had to check on $$hash while maintaining the querystring. In my case, I should move the $$hash value only in the portfolio view. I hope this helps.

var onRouteChangeOff = $scope.$on('$locationChangeStart', routeChange);
function routeChange(event, newUrl, oldUrl) {
  onRouteChangeOff();
  if (oldUrl.indexOf('?') >= 0) {
    var queryString =  oldUrl.split('?')[1];
    queryString = queryString.split('#')[0];
    newUrl = $location.$$path + '?' + queryString;
    if($location.$$path === '/portfolio')
    {
      newUrl += '#' + $location.$$hash
    }
    $location.url(newUrl);
  }
  event.preventDefault();
  return;
}

If you are only looking to maintain querystring across views where you do not have any $$hash value, then the following code would be fine.

var onRouteChangeOff = $scope.$on('$locationChangeStart', routeChange);
function routeChange(event, newUrl, oldUrl) {
  onRouteChangeOff();
  if (oldUrl.indexOf('?') >= 0) {
    var queryString =  oldUrl.split('?')[1];        
    newUrl = $location.$$path + '?' + queryString;        
    $location.url(newUrl);
  }
  event.preventDefault();
  return;
}
Discussion courtesy of: Pankaj Shrestha

I currently attach $location service to the scope and use ng-click to navigate, e.g. ng-click="$location.path('/target')".

Any query parameters will not be replaced.

Discussion courtesy of: Dormouse

This recipe can be found in it's original form on Stack Over Flow.