Watch on $location.search does not fire

1,928 views
Skip to first unread message

Mark Waddle

unread,
Sep 27, 2012, 10:29:56 PM9/27/12
to ang...@googlegroups.com
First off I want to say that AngularJS is fantastic, and I am really enjoying developing with it. Thank you Google and the Angular team for providing such a great framework.

So I am building a search page that takes search criteria from the user, executes the search according to the criteria, and renders the result via a template. I need for each search to push a new URL onto the history so the user can use the back and forward buttons to navigate through their searches. I considered using the RESTy way that AngularJS supports with tokens in the path, but that doesn't work well for search. My search parameters include query, filters, page and sort. Path routing requires that all of the parameters be ordered in a specific order, which is not ideal. I would like to support the parameters in any order. Therefore I have decided that placing the parameters in the search fragment, or the query string, is the way to go. Also, this seems to be the standard way things are done with search. If anyone has ideas about this or suggestions for improvement please share.

So my URL format is like this: /search?q={query}&filters={filters}&p={page}&sort={sort}

All of the parameters are optional and the parameter order does not matter.

After a search is triggered I can update the query string by calling $location.search(). This works as expected. However I need to respond to changes to movements forward and backward in the history. I could not find a good way to listen for history events with AngularJS, so the best way I could determine to do that was to watch $location.search() with my controller. The problem is that the watch is only firing when the page first loads, and not anytime after that. Not when it changes due to my code after a search is triggered. Not when the back or forward buttons are clicked.

I have the following route:
$routeProvider
   .when('/search', {
     templateUrl: '/assets/html/searchView.html',
     controller: SearchCtrl,
     reloadOnSearch: false   // do not reload controller when querystring changes
   })

The controller and template are coupled to an <ng-view/> in the page. The reloadOnSearch is disabled to prevent AngularJS from reloading the controller and template every time the query string changes, which happens every time a search is triggered, because of my code described above.

To watch $location.search, I have the following in my controller:
$scope.$watch('$location.search()', function(newQs, oldQs) { ... });

As I said, this watch is only being fired on the initial load of the page. This means I can't handle history state changes. Am I doing something wrong? Is there another way to monitor the history? I suppose it is possible that the reloadOnSearch:false in my route is causing this, but I would expect that would only affect the reloading of the controller and not whether or not watches on $location.search() fires. Is this a bug? Is there a fundamental problem in my approach?

Thanks for reading and any help you can provide,
Mark

hooblei

unread,
Sep 29, 2012, 10:21:06 AM9/29/12
to ang...@googlegroups.com


Hi,

I had the same issue just 2 days ago, my solution was something like this:

$scope.loc = $location;
$scope.$watch("(loc.search()).q", function (val, old) {
   ...
})

will trigger if 'q' changes in the location search

I'm not sure that this is the 'right' way to do it and it would be very useful if one can listen on a $location search changed event or something like that

Markus

Mark Waddle

unread,
Sep 29, 2012, 6:01:20 PM9/29/12
to ang...@googlegroups.com
Thanks so much hublei, that is working for me!

I am watching on "loc.search()" instead because I have multiple parameters I need to watch for. The drawback is that it seems to fire twice whenever the location changes, once with the change, and a second time with what appears to be the same search(), meaning that the objects and their values are the same upon inspection. I had to put in code to manually do a comparison of the values and that is working for me now.

Of course when searches are executed I update the location.search() which also fires the watch twice and I avoid acting on the first watch event by setting a $scope variable before changing the location.search() and checking/resetting that variable in the watch listener. The second watch event is not acted on due to the same comparison code described above.

As I said, this works, but is definitely the biggest hack in my angular app. If anyone has suggestions for improvement please share!

Thanks,
Mark

Mark Waddle

unread,
Oct 3, 2012, 10:51:48 PM10/3/12
to ang...@googlegroups.com
I have found an alternative which seems to work better, which is to listen for the $routeUpdate event. It does not seem to fire twice like the watch on loc.search() did.
Reply all
Reply to author
Forward
0 new messages