angular ui-router: using URL query parameters to define child state - angularjs

Similar to this question and this issue I'm trying to use URL query parameters to define child states with ui-router :
.state('map', {
url: '/map?country&city'
templateUrl: 'templates/myMapTemplate.html',
// no resolve in this case as using default data for map template
})
.state('map.country', {
url: '?country',
templateUrl: 'templates/myMapTemplate.html',
resolve: {
countryData : ['mapServices', '$stateParams', function(mapServices, $stateParams){
return mapServices.getCountryBounds($stateParams.country) // ajax server call
}]
}
})
.state('map.city', {
url: '?city',
templateUrl: 'templates/myMapTemplate.html',
resolve: {
cityData : ['mapServices', '$stateParams', function(mapServices, $stateParams){
return mapServices.getCityBounds($stateParams.city) // ajax server call
}]
}
})
The idea is that:
- having a different query parameter (or none at all) changes the state
- the resolve: of each child state can be specified to get different information from the server
- the template is the same for each state, but the data (ultimately) fed to the controller / template is different for each child state (retrieved via resolve)
eg.
www.mysite.com/map loads map with default data
www.mysite.com/map?country=US gets country data from server
www.mysite.com/map?city=sao_paulo gets city data from server
I've tried to set this up as above, but it won't work:
- no child state is recognised when adding URL parameters in the child state
Is it possible to use (optional) query parameters to change child state in ui-router?
(The docs don't specify much regarding query params)
Update #1: Created a plunker of the code above
Update #2: Created a 2nd plunker defining /map as an abstract state - which now works for changing states with ui-sref (and injects parameters into the resolve) - but href map?country=US still not recognised... ?

Related

How to reuse a ui-router state in different states?

I have a ui-router state that I use to display some info about a movie in an overlay. The data it displays is always the same, and the data it recieves is also always the same. But I'm using that state multiple times in my ui-router controller, but with different state parameters.
I have 4 states from where I call this trailer state,
home.container-big.trailer
home.suggestions.container-big.trailer
friendprofile.container-big.trailer
home.activities.movie-info.trailer
These are the 4 states I call the trailer state.
So for each click I have a different state although everything except the state name is different.
So I was thinking this,
I've create a stateProvider
$stateProvider
.state('trailer',{
params: {
trailer_link: null
},
url: '',
views: {
"youtube_trailer":{
templateUrl: '../assets/angular-app/templates/_container-trailer.html',
controller: function($scope, $stateParams, $state) {
$scope.trailer_link = $stateParams.trailer_link;
}
}
}
})
And I want to call it inside the home.container-big state like so,
%a{"ui-sref" => ".trailer({trailer_link: '{{ trailer.link }}'})"} Trailer {{$index+1}}
The click produces this error
Could not resolve '.trailer' from state 'home.container-big'
Which makes sence. So I'm thinking I need to pass the current state in the click to the name of the ui-state. Is this possible? Or is there another way I can reuse the same state in different states?

How to check or get the success of $state.go() in angularjs

We have one requirement in which $state.go() opens an HTML page with a dynamic table. Once this $state.go creates/loads the <table> tag then I have to set the values in the cells of this table. But as $state.go uses promise in back-end. So when the method calling this $state.go is called doesn't set the values for the first time. But start it from the next time.
The scenario is:
$scope.myfunction:function() {
$state.go('xyz');
setValues() //should be called once the previous line is completed
}
I have tried:
$rootscope.$on $stateChangeSuccess event //it is not triggered and there is no value inside toState
$state.go().then //which makes my code execute from the second time.
and lot more things.
Please help me out.
From what I understand, you need to populate the cells in the table after the user has transitioned to xyz state.
Adapting current setup
You should structure your project so that you have a controller per view. Using ui-router you can then pass whatever values that need to be populated in the table as params to the controller for state xyz. So it's something like this:
// allow params in xyz state config
$stateProvider
.state('xyz', {
controller: 'XyzController',
controllerAs: 'vm', // only necessary if you use controllerAs,
params: {
cellValue1: null, // this will get populated when the data gets passed by the other state's controller
cellValue2: null
},
templateUrl: 'someTemplateUrl',
url: 'someUrl'
});
// controller for starting state let's call it 'abc'
$scope.myfunction: function() {
$state.go('xyz' {
cellValue1: 'someCellValue1',
cellValue2: 'someCellValue2'
});
}
// controller for xyz
function XyzController() {
setValues(); // will be called on controller load
}
A better solution
A better alternative for what you want to do is to use resolve. It makes more sense to do this from a technical design perspective and will prevent the rest of the page loading before the cells get populated on slower connections.
So within the route config for xyz state add the resolve property and the setValues() method:
$stateProvider
.state('xyz', {
controller: 'XyzController',
controllerAs: 'vm', // only necessary if you use controllerAs,
resolve: {
setValues: setValues()
}
templateUrl: 'someTemplateUrl',
url: 'someUrl'
});
// inject into xyz controller
function XyzController(setValues) {
cellValue1 = setValues.cellValue1;
}
UI-Router state.go() return a promise, so you could do something like:
$scope.myfunction:function() {
$state.go('xyz')
.then(function(){
setValues(); //will be executed after $state.go() succeeds
});
}

How can I pass an object through $stateParams?

I've got a ui-router application with a large number of states, and I need to be able to pass a model through $stateParams. On every $http request, I check the response for a "STATE" parameter, which is returned from the server. If it exists, I execute $state.go(STATE).
So effectively, I've got my $stateProvider:
$stateProvider
.state('Account', {url: '/Account', template: '<ui-view/>'})
.state('Account.name', {
url: '/Name',
templateUrl: 'app/Account/partials/Name.html',
controller: 'AccountNameController as nameVm'
})
And many more states that look just like this.
I have a data model that is just a factory with an object that is get and set via functions. So whenever I call saveAccount(), it takes the model and sends it to a Web API backend. The backend verifies the data and sends it back with a STATE parameter (either account.invalid, account.valid, or account.needsMoreInfo). Here's my $httpInterceptor
.factory('httpInterceptor', ['$q', '$injector',
function ($q,$injector) {
return {
'response': function(response) {
if(response.data.state){
$injector.get('$state').go(response.data.state, response.data.account);
}
return response;
}
};
}
])
As you can see, I'm trying to send the account through the stateparams.
In the controller, I basically need to be able to say vm.account = $stateParams.account
My question is:
How can I modify my $states to both have a named controller and also take a state parameter and access that from the controller?
The reason I'm not passing the data through a service is because there are multiple models, so I can't just provide the name of the service in the $httpInterceptor because it isn't constant.
EDIT: Figured it out
Here's what my controller needed to have in it:
if ($stateParams && $stateParams.data){
vm.Account = $stateParams.data;
}
And here's what the state ended up looking like:
.state('taxAccount.invalid', {
url: '/Invalid?params',
templateUrl: 'app/TaxAccount/partials/Invalid.html',
controller: 'taxAccountInvalidController as invalidVm',
params:{data:null}
})
I didn't realize I could put params:{data:null}. I thought the stateParams had to go in the controller declaration in the state configuration.
Here's what my controller needed to have in it:
if ($stateParams && $stateParams.data){
vm.Account = $stateParams.data;
}
And here's what the state ended up looking like:
.state('taxAccount.invalid', {
url: '/Invalid?params',
templateUrl: 'app/TaxAccount/partials/Invalid.html',
controller: 'taxAccountInvalidController as invalidVm',
params:{data:null}
})
I didn't realize I could put params:{data:null}. I thought the stateParams had to go in the controller declaration in the state configuration.

Angularjs ui-router - pass extras 'unplanned' parameters

I have this simple state :
.state('search', {
url: '/search',
templateUrl: 'views/search.html',
controller: 'search'
})
And I would like to pass any extra unplanned parameters to the controller when using search state or /search route :
ui-sref="search({foo:1, bar:2})"
// would call '#/search?foo=1&bar2',
// match the state and pass foo and bar to the controller (through $stateParams)
When I try this, it matches the otherwise of the router instead. :(
I've read a lot of solutions that imply to declare each parameter in the state:
.state('search', {
url: '/search?param1&param2&param3?...',
})
But I cannot do this as far as the parameters list is not really defined and changes all the time depending on searched content.
Is there a way to achieve this ? Or am I wrong somewhere ?
Thx.
EDIT : When I try to call directly this url : #/search?foo=1, the state search matches but the foo parameter never goes to $stateParams which is empty. I don't know how to get it in.
.state('search', {
params: ['param1','param2','param3'],
templateUrl: '...',
controller: '...'
});
ui-sref="search({param1:1, param2:2})"
Credit goes to Parameters for states without URLs in ui-router for AngularJS

Using $state.params in resolve does not work

I'm currently working on a small application that shows a 'drill down' type of menu. Unlike a TreeView my component only displays the current level.
There are only 3 levels: main, submenu, item. Drilling deeper (on the item) will result in a new state that corresponds to a detail view of the selected item.
I have only 2 states, the first is valid for any of the three levels. The second is the detail state:
$stateProvider.state('menu', {
url: '/menu/:submenu/:item',
templateUrl: 'src/menu/views/menu.html',
controller: 'MenuController',
controllerAs: 'MenuController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
}).state('menu.details', {
url: '/:selectedItem',
templateUrl: 'src/menu/views/menu-details.html',
controller: 'MenuDetailsController',
controllerAs: 'MenuDetailsController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
});
This solutions works correctly, however I'm a bit annoyed by the fact I need to define the resolvemethod twice. I am required to do this as $stateParams contains only the params of that exact state (excluding child states) and I need all parameters to form a valid URL to fetch the corresponding resource via the MenuService.
I know that state.params can be used to get all state params, however it doesn't seem to work in the resolve method: it contains the parameters of the previous state.
Does anybody know how I could fix this? Having a child state that should simply trigger a new view, whilst using the resolve method of the parent state with all state parameters?
If you think I'm completely misunderstanding how states work, then please go ahead and tell me :)

Resources