update $stateParams in ui-router after $location.search() is called - angularjs

I have an angular application which is using the ionic-framework, which uses ui-router on the back end.
In one of my controllers I call:
$location.search('filter','sometext');
I have reloadOnSearch disabled in my routing configuration. I noticed that updating the location does not update the $stateParams. Is there a way to force the $stateParams to update as the location is updated? I looked through the ui-router documentation, but didn't see anything about this scenario, but perhaps I missed it.

I had a similar situation. You cannot track route updates if you had disabled reloadOnSearch, but I found a solution for this case.
Watch $stateParams:
$scope.$watchCollection('$stateParams', function (newParams) {
var search = $location.search();
if (angular.isObject(newParams)) {
angular.extend(search, newParams);
}
$location.search(search).replace();
});
Change $stateParams, not route:
$scope.$stateParams.offset += $scope.$stateParams.limit;
Completely working example.

Related

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

Difference between getting URL query string with $routeParams vs $location.$$search

If I have a specific route parameter that is always being passed as a query string, is there a difference between retrieving the param using angular's $routeParams service and using its $location.$$search service? If so, is one preferable to the other?
URL: //localhost:80/app/profile?id=42
$routeParams approach:
app.controller('ProfileController', ['$routeParams', function($routeParams) {
console.log($routeParams.id);
}]);
$location approach:
app.controller('ProfileController', ['$location', function($location) {
console.log($location.$$search.id);
}]);
I am already injecting $location as a dependency in the controller that needs to perform the behavior detailed in the question. Additionally, the controller's module does not yet have a dependency on ngRoute.
If your route is of type:
when('page/:id')
then using $location search won't give you the ID in the search result. But $routesParams does.
But in case of query params ex:
when('page?key=value&key2=value2')
In this case, you can go for any of $location.search or $routeParams.
It also seems that the $location way is faster in some sense (which is only natural, since $routeParams uses $location to get its values).
To explain:
My site has a mode that's only meant to be used by kiosk tablets, whereas the normal use case is customers using their own devices. To differentiate, I initiate kiosk tablets by going to https://mysite.url/?kiosk, which triggers this (which runs on load):
if($routeParams.kiosk){
$cookieStore.put("kiosk", true);
}
Now, this tended to fail, since $routeParams hadn't had time to become initialized that close to load. Switching to
if($location.search().kiosk){
$cookieStore.put("kiosk", true);
}
mitigated that issue.
(My site is currently stuck on Angular 1.2.19, and was not put together by people who had all current best practices in mind. My example may or may not be relevant to modern projects coded by competent developers ;-) )

UI-Router's resolve functions are only called once

I was going to use ui-routers resolve feature to inject some readiliy resolved promises into my controllers.
I used the example plnkr to make an example.
Consider these nested states: route1 and route1.list.
I have a resolve function called abc defined on route1. Now when I navigate to route1 for the first time, abc is called and will be resolved. Now when I navigate to route1.list and back to route1, abc is not called again.
http://plnkr.co/edit/mi5H5HKVAO3J6PCfKSW3?p=preview
I guess this is intentional, but consider this use-case:
the resolve function retrieves some data via http and should be refreshed/invalidated at some point, maybe on every state change. Is there some way to do this when using nested states?
If the resolve function was called on every inject I could implement it any way I want: Return the old, resolved promise or create a new one.
I have only briefly tested this, but if the states were not nested things would work as I expected. Giving up on nested states because of this stinks though. And having the resolve dependencies available in nested states is actually nice.
Supplying the option reload:true to go() / transtionTo() / ui-sref does the trick :-)
Thanks to Designing the Code for pointing me in this direction. The solution is a bit different though, so I write up this answer.
Reload is documented as follows:
reload (v0.2.5) - {boolean=false}, If true will force transition even if the state or params have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd use this when you want to force a reload when everything is the same, including search params.
The direct way is to change every ui-sref link into something like this: <a ui-sref="route1" ui-sref-opts="{reload: true}">.
To avoid supplying the option at every link I wrote a decorator around $state (see also http://plnkr.co/edit/mi5H5HKVAO3J6PCfKSW3?p=preview):
myapp.config(function($provide) {
$provide.decorator('$state', function($delegate) {
var originalTransitionTo = $delegate.transitionTo;
$delegate.transitionTo = function(to, toParams, options) {
return originalTransitionTo(to, toParams, angular.extend({
reload: true
}, options));
};
return $delegate;
});
});
Try $state.reload();
It will force resolve the resolves again. Though think there is some issue related to this where controllers aren't reloaded. (ui.router n00b here as well)

$routeParams not available during controller initialization

I'm trying to figure out AngularJS and routing. Can someone take a look at this http://jsfiddle.net/spoon16/p9BBr/
Help me understand why $routeParams.i is undefined during initialization. What is the appropriate way to use $routeParams in my controllers?
Basically $routeParams isn't available until the route service has changed the route, meaning you should only inject the service in controllers associated with a specific route (the controller property for the route).
If you in some other controller needs to know current route etc. you should listen to the various events the route service broadcasts, e.g.
$scope.$on('$routeChangeSuccess', function (ev, current, previous) {
// ...
});
Updated fiddle.

How to update a route from inside an AngularJS controller?

I have an AngularJS application into which I want to load some plugins that are discovered by a controller when it starts up. In order for the plug in to work I have add some routes to the $routeProvider from the controller, but there seems to be no way to do this.
Right now I'm using a pretty ugly hack as below:
var routeProvider;
angular.module('example', [], function($routeProvider) {
routeProvider = $routeProvider;
// Set up other routes
}
function Controller($http, $location, $timeout) {
// Use $http to find some plugin
routeProvider.when(plugin.url, plugin.options);
// Ugly hack so that the plugin appears if $location.path() already points to it
var path = $location.path();
$location.path("/");
$timeout(function() { $location.path(path); }, 10);
}
If I don't do the nonsense with $timeout then if I start (load the page) at the route for the plugin it won't load ($route.current remains blank). With the jump between paths the route gets resolved properly and the plugin view loads as it should.
Is there a better way of doing this?
You could tear $routeProvider from the source and make your own version? :-)
$routeProvider is just a provider made for you. https://github.com/angular/angular.js/blob/master/src/ng/route.js
The way that we did this in the end was to have all of the routes downloaded from services before we start up AngularJS and then use that data structure to set up the routes, then bootstrap Angular once we had the information and everything was set up properly.
The only downside of this is the delay in start up, especially if there are multiple service plug ins that you need to handle.

Resources