Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Router - $watch state params not working

I'm using Angular UI router (very latest version) and I'm trying to update the search parameters while not reloading the whole page. I want to $watch the $stateParams but the watch isn't working with my settings.

For clarity, when I use transitionTo() I want the URL to be updated and I want the $stateParams to be updated and a $watch to be fired on the $stateParams, but I don't want the whole controller to be run again. reloadOnSearch: false stops the controller from running again but has the side effect of stopping the $watch from firing, or so it seems. Anyway here is my code

$stateProvider config:

$urlRouterProvider.otherwise("/search");

$stateProvider
    .state('search', {
        url: "/search?locationtext&method",
        templateUrl: "templates/searchResults.tpl.html",
        controller: 'SearchResultsCtrl as searchResults',
        reloadOnSearch : false
    })

Button click handler:

$state.transitionTo($state.current, {locationtext: 'london'}, {
    location : true,
    reload: false,
    inherit: false,
    notify: true
});

$watch the State params:

$scope.stateParams = $stateParams;

$scope.$watchCollection('stateParams', function(){
    $log.info("State params have been updated", $scope.stateParams);
});

This is what is happening:

1) When the state is first loaded, the watch fires and the stateparams are logged to the console.

2) When I click the button, the state is transitioned and the url says #/search?locationtext=london

3) The watch is not fired at this point, although a console.log shows that the $state.params have been updated

4) If I hit the browser back button locationtext=london is removed from the URL but again the watch doesn't fire.

Not sure what I'm doing wrong here. I assume I have set up the watch correctly otherwise it wouldn't fire when the controller is first loaded?

Any ideas would be greatly appreciated.

Edit:

I have also added the following

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
    console.log("$stateChangeStart", $rootScope.stateParams);

});

but the event isn't even firing, despite the url changing and the $state.params being different after the transitionTo();

Edit 2:

I've been trawling through the code and it seems as if setting reloadOnSearch to false means that if you only change search parameters then nothing happens, even though the URL changes the transition doesn't happen and the $stateChangeStart event isn't broadcast.

This isn't what I was expecting. I was expecting all of that to happen but the controller not to be reloaded. So now I'm looking for a way to change the search parameters and watch for changes so that I can update my search results. It looks like the Angular UI Router has no way to do this because you either get a controller reload or no events.

like image 594
jonhobbs Avatar asked Aug 24 '15 17:08

jonhobbs


1 Answers

The problem is that $stateParams is not $stateParams. They might seem similar, but apparently they aren't.

What I mean by this is that if you manually delete the following line from the angular-ui-router source code, your watcher will trigger:

var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);

(Line 3534, of the built distributable; Line 1385 of src/state.js for unbuilt version.)

An alternative to removing the line is to use angular.copy as is used elsewhere in the file:

var _stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
copy(_stateParams, $stateParams); // This project has a short-hand for angular.copy

Admittedly, this is an incomplete answer because I don't know why this is the case. Perhaps it's a bug in angular-ui-router, perhaps it's by design. Your best bet might be to either respond to one of the many open tickets regarding empty $stateParams (these can be found at: https://github.com/angular-ui/ui-router/search?q=stateParams+&type=Issues), or if you think that you have extra information, create one.

In the interim, you can make your code work by watching $state.params, instead:

$scope.$watchCollection(function(){
    return $state.params;
}, function(){
    $log.info("State params have been updated", $scope.stateParams);
});

EDIT: Some test code to play with: http://plnkr.co/edit/0UqUzZDSfMtPf3bSjBS7?p=preview

In this plunker, you can change the html to include the -updated version of angular-ui-router to see that removing the line actually makes both types of watcher work correctly.

like image 110
DRobinson Avatar answered Nov 07 '22 12:11

DRobinson