Related
NOTE: This question is similar to UI-Router and resolve, unknown provider in controller but differs in that it deals specifically with AngularJS 1.5+ and Component-based apps which changes how things are configured for a state resolve.
So I am trying to resolve some data in a child state. I had done this before for a previous resolve but am running into an issue for the 2nd one.
Here is my setup:
App State
I have a parent state "app" and a child state "home". When a User logs in they go through the "app" state which did the resolving and then they get redirected to the "home" state.
angular
.module('common')
.component('app', {
templateUrl: './app.html',
controller: 'AppController',
bindings: {
member: '=',
}
})
.config(function ($stateProvider) {
$stateProvider
.state('app', {
redirectTo: 'home',
url: '/app',
data: {
requiredAuth: true
},
resolve: {
member: ['AuthService',
function (AuthService) {
return AuthService.identifyMember()
.then(function (res) {
AuthService.setAuthentication(true);
return res.data;
})
.catch(function () {
return null;
});
}
],
organization: ['AuthService',
function (AuthService) {
return AuthService.identifyOrganization()
.then(function (res) {
return res.data;
})
.catch(function () {
return null;
});
}
],
authenticated: function ($state, member) {
if (!member)
$state.go('auth.login');
}
},
component: 'app',
});
});
Home State
angular
.module('components')
.component('home', {
templateUrl: './home.html',
controller: 'HomeController',
bindings: {
member: '=',
}
})
.config(function ($stateProvider) {
$stateProvider
.state('home', {
parent: 'app',
url: '/home',
data: {
requiredAuth: true
},
component: 'home',
resolve: {
'title' : ['$rootScope',
function ($rootScope) {
$rootScope.title = "Home";
}
],
}
});
});
And in my controller when I try to console.log the output of what should be there:
function HomeController(AuthService, $state) {
let ctrl = this;
console.log(ctrl.organization);
}
But, I am getting undefined.
My methods in AuthService are getting called the same way for the member resolve so I am not sure what the problem is.
So it turns out that I was simply missing the binding for organization in both the App State and Home State:
bindings: {
member: '=',
organization: '=',
}
NOTE: Because I used bindings, I did not have to inject the data into the Controller itself as is shown in the UI-Router docs at the following link:
https://github.com/angular-ui/ui-router/wiki/Nested-States-&-Nested-Views#inherited-resolved-dependencies
I am not sure why using the bindings allows that but for the purposes of inheriting data from a parent state, it seems to achieve the same result.
EDIT: After rewording my search queries, I was able to find the section in the UI-Router docs that actually shows the same thing that I did:
Instead of injecting resolve data into the controller, use a one-way component input binding, i.e., <.
https://ui-router.github.io/guide/ng1/route-to-component#create-a-component
This seems to connect the data to the specific Controller like how injecting the data into the Controller connects it as well. Although I am still unsure if any under-the-hood differences between binding and injecting exist.
Given that UI-Router shows the same logic that I had used, this seems to be the proper way to allow a Controller access to resolved data for a particular state.
The only other thing I would say is to pay attention to what type of binding you need to use. You can find the different types and their descriptions here under Component-based application architecture and then under Components have a well-defined public API - Inputs and Outputs:
https://docs.angularjs.org/guide/component
Trying to pass on a parameter to my ui router:
$state.go('orderDetail', {myParam: {accountID: $scope.selectedAccount}})
In my stateprovider the orderDetail state looks like this:
.state('orderDetail', {
templateUrl: 'modules/common/orders/partials/detail.html?referrer',
controller: 'OrderDetailCtrl',
url: '/detail/:myParam',
resolve: {
orderDetails: function (myservice, configService, $stateParams) {
console.log('resolve');
var referrer = $stateParams.myParam;
debugger;
console.log('stateParm', referrer.accountID);
console.log('order detail resolving');
//todo remove hardcoded
return myservice.getDetail(configService.config('mock_order_detail').url + '?accountId=2233');
}
}
});
However console.log('stateParm', referrer.accountID); shows up as undefined. How can I pass in the accountID param?
To recognize the state that myParam is object, you need to define its default value inside your state params option, by assigning null or any default value you want for it.
Code
.state('orderDetail', {
templateUrl: 'modules/common/orders/partials/detail.html?referrer',
controller: 'OrderDetailCtrl',
url: '/detail/:myParam',
resolve: {
//resolve code here
}
params: {
'myParam': null
}
}
To make above changes effective you Should update ui-router version to 0.2.13
Github Issue Link
$stateParams accepts only string, however you could stringify your object as pass it as myParam
$state.go('orderDetail', {myParam: angular.fromJson({accountID: $scope.selectedAccount})})
and then
.state('orderDetail', {
templateUrl: 'modules/common/orders/partials/detail.html?referrer',
controller: 'OrderDetailCtrl',
url: '/detail/:myParam',
resolve: {
orderDetails: function (myservice, configService, $stateParams) {
var referrer = angular.toJson($stateParams.myParam);
return myservice.getDetail(configService.config('mock_order_detail').url + '?accountId=2233');
}
}
});
But i don't recommend using stringified objects, better use a service as bridge for states, just because makes ugly URL and not secure, internal state must be hidden from user i belive
So consider the following fragment from my angularUI routing setup. I am navigating to the route /category/manage/4/details (for example). I expect 'category' to be resolved before the relevant controller loads, and indeed it is to the extent that I can put a breakpoint inside the resolve function that returns the category from the category service and see that the category has been returned. Now putting another breakpoint inside the controller itself I can see that 'category' is always undefined. It is not injected by UI router.
Can anyone see the problem? It may be somewhere other than in the code I've provided but as I have no errors when I run the code, it's impossible to tell where the source of the issue might lie. Typical js silent failures!
.state('category.manage', {
url: '/manage',
templateUrl: '/main/category/tree',
controller: 'CategoryCtrl'
})
.state('category.manage.view', {
abstract: true,
url: '/{categoryId:[0-9]*}',
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId).then(returnData); //this line runs before the controller is instantiated
}]
},
views: {
'category-content': {
templateUrl: '/main/category/ribbon',
controller: ['$scope', 'category', function ($scope, category) {
$scope.category = category; //category is always undefined, i.e., UI router is not injecting it
}]
}
},
})
.state('category.manage.view.details', {
url: '/details',
data: { mode: 'view' },
templateUrl: '/main/category/details',
controller: 'CategoryDetailsCtrl as details'
})
The concept is working. I created working plunker here. The changes is here
instead of this
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
//this line runs before the controller is instantiated
return CategoryService.getCategory($stateParams.categoryId).then(returnData);
}]
},
I just returned the result of the getCategory...
resolve: {
category: ['CategoryService', '$stateParams', function (CategoryService, $stateParams) {
return CategoryService.getCategory($stateParams.categoryId); // not then
}]
},
with naive service implementation:
.factory('CategoryService', function() {return {
getCategory : function(id){
return { category : 'SuperClass', categoryId: id };
}
}});
even if that would be a promise... resolve will wait until it is processed...
.factory('CategoryService', function($timeout) {return {
getCategory : function(id){
return $timeout(function() {
return { category : 'SuperClass', categoryId: id };
}, 500);
}
}});
I am asking a similar question to this question: UI Router conditional ui views?, but my situation is a little more complex and I cannot seem to get the provided answer to work.
Basically, I have a url that can be rendered two very different ways, depending on the type of entity that the url points to.
Here is what I am currently trying
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$state.transitionTo('home.first');
} else {
$state.transitionTo('home.second');
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I set up a Resolve to fetch the actual entity from a restful service.
Every thing seems to be working until I actually get to the transitionTo based on the type.
The transition seems to work, except the resolve re-fires and the getEntity fails because the id is null.
I've tried to send the id to the transitionTo calls, but then it still tries to do a second resolve, meaning the entity is fetched from the rest service twice.
What seems to be happening is that in the onEnter handler, the state hasn't actually changed yet, so when the transition happens, it thinks it is transitioning to a whole new state rather than to a child state. This is further evidenced because when I remove the entity. from the state name in the transitionTo, it believes the current state is root, rather than home. This also prevents me from using 'go' instead of transitionTo.
Any ideas?
The templateUrl can be a function as well so you check the type and return a different view and define the controller in the view rather than as part of the state configuration. You cannot inject parameters to templateUrl so you might have to use templateProvider.
$stateProvider.state('home', {
templateProvider: ['$stateParams', 'restService' , function ($stateParams, restService) {
restService.getEntity($stateParams.id).then(function(entity) {
if (entity.Type == 'first') {
return '<div ng-include="first.html"></div>;
} else {
return '<div ng-include="second.html"></div>';
}
});
}]
})
You can also do the following :
$stateProvider
.state('home', {
url : '/{id}',
resolve: {
entity: function($stateParams, RestService) {
return RestService.getEntity($stateParams.id);
}
},
template: 'Home Template <ui-view></ui-view>',
onEnter: function($state, entity) {
if (entity.Type == 'first') {
$timeout(function() {
$state.go('home.first');
}, 0);
} else {
$timeout(function() {
$state.go('home.second');
}, 0);
}
}
})
.state('home.first', {
url: '',
templateUrl: 'first.html',
controller: 'FirstController'
})
.state('home.second', {
url: '',
templateUrl: 'second.html',
controller: 'SecondController'
});
I ended up making the home controller a sibling of first and second, rather than a parent, and then had the controller of home do a $state.go to first or second depending on the results of the resolve.
Use verified code for conditional view in ui-route
$stateProvider.state('dashboard.home', {
url: '/dashboard',
controller: 'MainCtrl',
// templateUrl: $rootScope.active_admin_template,
templateProvider: ['$stateParams', '$templateRequest','$rootScope', function ($stateParams, templateRequest,$rootScope) {
var templateUrl ='';
if ($rootScope.current_user.role == 'MANAGER'){
templateUrl ='views/manager_portal/dashboard.html';
}else{
templateUrl ='views/dashboard/home.html';
}
return templateRequest(templateUrl);
}]
});
I am using AngularJS v1.2.0-rc.2 with ui-router v0.2.0. I want to pass the referrer state to another state so I use the toParams of $state.go like so:
$state.go('toState', {referer: $state.current.name});
According to the docs, this should populate the $stateParams on the toState controller, but it is undefined. What am I missing?
I've created a plunk to demonstrate:
http://plnkr.co/edit/ywEcG1
If you want to pass non-URL state, then you must not use url when setting up your state. I found the answer on a PR and did some monkeying around to better understand.
$stateProvider.state('toState', {
templateUrl:'wokka.html',
controller:'stateController',
params: {
'referer': 'some default',
'param2': 'some default',
'etc': 'some default'
}
});
Then you can navigate to it like so:
$state.go('toState', { 'referer':'jimbob', 'param2':37, 'etc':'bluebell' });
Or:
var result = { referer:'jimbob', param2:37, etc:'bluebell' };
$state.go('toState', result);
And in HTML thusly:
<a ui-sref="toState(thingy)" class="list-group-item" ng-repeat="thingy in thingies">{{ thingy.referer }}</a>
This use case is completely uncovered in the documentation, but I think it's a powerful means on transitioning state without using URLs.
The Nathan Matthews's solution did not work for me but it is totally correct but there is little point to reaching a workaround:
The key point is: Type of defined parameters and toParamas of $state.go should be same array or object on both sides of state transition.
For example when you define a params in a state as follows you means params is array because of using "[]":
$stateProvider
.state('home', {
templateUrl: 'home',
controller: 'homeController'
})
.state('view', {
templateUrl: 'overview',
params: ['index', 'anotherKey'],
controller: 'overviewController'
})
So also you should pass toParams as array like this:
params = { 'index': 123, 'anotherKey': 'This is a test' }
paramsArr = (val for key, val of params)
$state.go('view', paramsArr)
And you can access them via $stateParams as array like this:
app.controller('overviewController', function($scope, $stateParams) {
var index = $stateParams[0];
var anotherKey = $stateParams[1];
});
Better solution is using object instead of array in both sides:
$stateProvider
.state('home', {
templateUrl: 'home',
controller: 'homeController'
})
.state('view', {
templateUrl: 'overview',
params: {'index': null, 'anotherKey': null},
controller: 'overviewController'
})
I replaced [] with {} in params definition. For passing toParams to $state.go also you should using object instead of array:
$state.go('view', { 'index': 123, 'anotherKey': 'This is a test' })
then you can access them via $stateParams easily:
app.controller('overviewController', function($scope, $stateParams) {
var index = $stateParams.index;
var anotherKey = $stateParams.anotherKey;
});
All I had to do was add a parameter to the url state definition like so
url: '/toState?referer'
Doh!
Not sure if it will work with AngularJS v1.2.0-rc.2 with ui-router v0.2.0.
I have tested this solution on AngularJS v1.3.14 with ui-router v0.2.13.
I just realize that is not necessary to pass the parameter in the URL as gwhn recommends.
Just add your parameters with a default value on your state definition.
Your state can still have an Url value.
$stateProvider.state('state1', {
url : '/url',
templateUrl : "new.html",
controller : 'TestController',
params: {new_param: null}
});
and add the param to $state.go()
$state.go('state1',{new_param: "Going places!"});
None of these examples on this page worked for me. This is what I used and it worked well. Some solutions said you cannot combine url with $state.go() but this is not true. The awkward thing is you must define the params for the url and also list the params. Both must be present. Tested on Angular 1.4.8 and UI Router 0.2.15.
In the state add your params to end of state and define the params:
url: 'view?index&anotherKey',
params: {'index': null, 'anotherKey': null}
In your controller your go statement will look like this:
$state.go('view', { 'index': 123, 'anotherKey': 'This is a test' });
Then to pull the params out and use them in your new state's controller (don't forget to pass in $stateParams to your controller function):
var index = $stateParams.index;
var anotherKey = $stateParams.anotherKey;
console.log(anotherKey); //it works!
In my case I tried with all the options given here, but no one was working properly (angular 1.3.13, ionic 1.0.0, angular-ui-router 0.2.13). The solution was:
.state('tab.friends', {
url: '/friends/:param1/:param2',
views: {
'tab-friends': {
templateUrl: 'templates/tab-friends.html',
controller: 'FriendsCtrl'
}
}
})
and in the state.go:
$state.go('tab.friends', {param1 : val1, param2 : val2});
Cheers
I've spent a good deal of time fighting with Ionic / Angular's $state & $stateParams;
To utilize $state.go() and $stateParams you must have certain things setup and other parameters must not be present.
In my app.config() I've included $stateProvider and defined within it several states:
$stateProvider
.state('home', {
templateUrl: 'home',
controller: 'homeController'
})
.state('view', {
templateUrl: 'overview',
params: ['index', 'anotherKey'],
controller: 'overviewController'
})
The params key is especially important. As well, notice there are NO url keys present... utilizing stateParams and URLs do NOT mix. They are mutually exclusive to each other.
In the $state.go() call, define it as such:
$state.go('view', { 'index': 123, 'anotherKey': 'This is a test' })
The index and anotherKey $stateParams variables will ONLY be populated if they are first listed in the $stateController params defining key.
Within the controller, include $stateParams as illustrated:
app.controller('overviewController', function($scope, $stateParams) {
var index = $stateParams.index;
var anotherKey = $stateParams.anotherKey;
});
The passed variables should be available!
Try With reload: true?
Couldn't figure out what was going on for the longest time -- turns out I was fooling myself. If you're certain that things are written correctly and you will to use the same state, try reload: true:
.state('status.item', {
url: '/:id',
views: {...}
}
$state.go('status.item', { id: $scope.id }, { reload: true });
Hope this saves you time!
I'd faced a similar problem. I ended up with a working solution after a lot of googling and trial and test. Here is my solution which would work for you.
I have two controllers - searchBoxController and stateResultController and a parameter named searchQuery to be passed from a view having a search box to a view showing the results fetched from a remote server. This is how you do it:
Below is the controller from which you call the next view using $state.go()
.controller('searchBoxController', function ($scope, $state) {
$scope.doSearch = function(){
var searchInputRaw = $scope.searchQueryInput;
$state.go('app.searchResults', { searchQuery: searchInput });
}
})
Below is the state that would be called when the $state.go() gets executed:
.state('app.searchResults',
{
url: '/searchResults',
views:
{
'menuContent': { templateUrl: 'templates/searchResult.html', controller: 'stateResultController' }
},
params:
{
'searchQuery': ''
}
})
And finally, the controller associated with the app.searchResults state:
.controller('stateResultController', function ($scope, $state, $stateParams, $http) {
$scope.searchQueryInput = $stateParams.searchQuery;
});
And in my case of a parent/child state. all the parameters declared in child state has to be known by the parent state
.state('full', {
url: '/full',
templateUrl: 'js/content/templates/FullReadView.html',
params: { opmlFeed:null, source:null },
controller: 'FullReadCtrl'
})
.state('full.readFeed', {
url: '/readFeed',
views: {
'full': {
templateUrl: 'js/content/templates/ReadFeedView.html',
params: { opmlFeed:null, source:null },
controller: 'ReadFeedCtrl'
}
}
})
The solution we came to having a state that took 2 parameters was changing:
.state('somestate', {
url: '/somestate',
views: {...}
}
to
.state('somestate', {
url: '/somestate?id=:&sub=:',
views: {...}
}
Your define following in router.js
$stateProvider.state('users', {
url: '/users',
controller: 'UsersCtrl',
params: {
obj: null
}
})
Your controller need add $stateParams.
function UserCtrl($stateParams) {
console.log($stateParams);
}
You can send an object by parameter as follows.
$state.go('users', {obj:yourObj});
I was trying to Navigate from Page 1 to 2, and I had to pass some data as well.
In my router.js, I added params name and age :
.state('page2', {
url: '/vehicle/:source',
params: {name: null, age: null},
.................
In Page1, onClick of next button :
$state.go("page2", {name: 'Ron', age: '20'});
In Page2, I could access those params :
$stateParams.name
$stateParams.age
If this is a query parameter that you want to pass like this:
/toState?referer=current_user
then you need to describe your state like this:
$stateProvider.state('toState', {
url:'toState?referer',
views:{'...'}
});
source: https://github.com/angular-ui/ui-router/wiki/URL-Routing#query-parameters