How can I pass an object through $stateParams? - angularjs

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.

Related

AngularJS UI-Router Access $stateParams from State's data object

Given the following state in ui-router:
.state('some.state', {
url: '/some/:viewType',
templateUrl: 'myTemplate.html',
controller: 'SomeStateController',
controllerAs: 'vm',
data: {
authorizedFor: [SOME_ROLE]
}
}
I'm trying to use the "data" object for a state to help control access to authorized states. Separately, I handle the $stateChangeStart event to look at data.authorizedFor and act accordingly.
The problem, though, is that the list of authorized roles might change based on the value of :viewType. I thought I could let data:{} be a function, inject $stateParams, and handle the logic there...but that won't do.
So, I tried using the params object instead, but at the $stateChangeStart time, the :viewType is not yet accessible from $state.params or $stateParams.
Stepping through in dev tools, I noticed that $state.transitionTo.arguments is populated, but it seems awfully hacky to go that route.
params: {
authorizedFor: function($state) {
console.log($state.transitionTo.arguments[1].viewType); // has value I need
}
}
Any suggestions?
My suggestion is to use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the $stateChangeSuccess event is fired.
for example:
$stateProvider
.state('profile', {
url: '/profile',
templateUrl: 'profile.html',
resolve:{
'ProfileService': function(ProfileService){
return ProfileService.promise_skillRecommendation_mock;
}
}
})
The profileService code:
var app = angular.module('app').service("ProfileService", function($http){
var myData = null;
var promise_skillRecommendation_mock =
$http.get('Mock/skillRecommendation-mock.json')
.success(function(data){
myData = data;
});
return{
promise_skillRecommendation_mock: promise_skillRecommendation_mock,
get_skillRecommendation: function(){
return myData;
}
};
});
and the controller code which will use this service is:
angular.module('app').controller('ProfileController', function($scope, $http, ProfileService){
$scope.skillRecommendation = ProfileService.get_skillRecommendation();
The object in resolve below must be resolved (via deferred.resolve() if they are a promise) before the controller is instantiated. Notice how each resolve object is injected as a parameter into the controller.
by using this code, the page will be displayed only after that the promise will be resolved.
for more info please view this page: https://github.com/angular-ui/ui-router/wiki

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 to update data on entering state in AngularJS UI-Router?

I'm using UI-Router for AngularJS and here is the question - at the moment when I'm clicking a link which sends me to specific state (using ui-sref) I want to send AJAX request to back end, get data, and render them in the template related to this new state. To which event should I listen for making AJAX request? Could you please give me a code example for this listener? I understand that the question seems to be simple, but I'm new in AngularJS world.
Thank you.
Have a look at the resolve property on your state configuration. See ui-router wiki
Then inject the resolved property as a dependency of the controller.
Example:
$stateProvider.state('about', {
templateUrl: 'about.html',
controller: 'AboutController',
resolve: {
something: function ($http) {
// make ajax request
return $http.get(...).then(function (response) {
return response.data;
});
}
}
})
In the controller, we inject the data that will be resolved, i.e. something :
app.controller('AboutController', function (..., something, ...) {
// The data resolved by ui-router is ready when the controller is instantiated
}

Angular UI Router - Default Parameter

Is there anyway to specify a default parameter for every route using the Angular UI Router?
My app is entered through the context of another application by selecting a user and then navigating to my application. The URL in my application will always have the user ID in the URL so that people can bookmark the URL, email it, etc. So, as you navigate around, the URL always follows a scheme of:
#/{userId}/view/...
#/{userId}/edit/...
etc.
This userId will always be the same for a user inside the app for any route they go to. If they happen to log out, go back to the main app, select a new user and come back to my app, this userId will change, but will be the same value for every route.
Is there anyway to read this value from say a service/factory and then plug it into every route?
EDIT:
I should mention I want to avoid having to explicitly set this parameter on every route when I navigate to a state. For example, I don't want to have to do ui-sref="new-state({userId : blah})" every time I navigate to a new state. That userId will never change in the context of my application.
EDIT AGAIN:
I actually went about this a different way concerning the requirement to not have to send 'userId' to every route manually. Instead of using a directive, I used a $provide.decorator to add this functionality to the 'go' method. I've added an answer below to what I did.
You can declare an abstract parent state from which child states inherit:
$stateProvider
.state('user', {
url: '/:userid',
abstract: true,
resolve:
// assuming some kind of User resource factory
currentUser: function($stateParams, User) {
return User.get($stateParams.userid);
}
}
})
.state('user.view', {
url: '/view', // actual url /:userid/view
controller: function($scope, currentUser) {
// currentUser resource available
}
});
.state('user.edit', {
url: '/edit', // actual url /:userid/edit
controller: function($scope, currentUser) {
// currentUser resource available
}
});
In terms of navigating to a state, you need to pass in the desired user:
$state.go('user.view', {userid: 'myuserid'});
As a consequence it might make sense to create some kind of .go() wrapper method on your currentUser service, so that you needn't specify the user id each time.
UPDATE:
To counter the problem posted in your edit, you could introduce a directive like this:
angular.module('app')
.directive('userSref', function($state) {
return function(scope, elem, attrs) {
var state = 'user.' + attrs.userSref;
elem.bind('click', function() {
$state.go(state, {userid: $state.params.userid});
});
scope.$on('$destroy', function() {
elem.unbind('click');
});
};
});
Then, any future links to user-based states can be done so with:
<a user-sref="view">View User</a>
Instead of writing a directive that handled the auto-sending of userID, I used $provide.decorator as follows:
app.config(['$provide',
function($provide) {
$provide.decorator('$state', function($delegate, UserService) {
// Save off delegate to use 'state' locally
var state = $delegate;
// Save off reference to original state.go
state.baseGo = state.go;
// Decorate the original 'go' to always plug in the userID
var go = function(to, params, options) {
params.userID = UserService.userID;
// Invoke the original go
this.baseGo(to, params, options);
};
// assign new 'go', decorating the old 'go'
state.go = go;
return $delegate;
});
}
]);
I got the idea from this post:
Changing the default behavior of $state.go() in ui.router to reload by default
You can use the "nested states" and "resolves" features of UI-Router to create a hierarchy of states in your app. You'll define a top level state that resolves the userId. Then define any number of child states, and they will automatically inherit the "resolved" userId.
Check out this page of the documentation, in particular the section titled "Important $stateParams Gotcha". I will paste the two code snippets from that page here.
Incorrect method:
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams){
$stateParams.contactId //*** Watch Out! DOESN'T EXIST!! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Correct method using "resolves":
$stateProvider.state('contacts.detail', {
url: '/contacts/:contactId',
controller: function($stateParams){
$stateParams.contactId //*** Exists! ***//
},
resolve:{
contactId: ['$stateParams', function($stateParams){
return $stateParams.contactId;
}]
}
}).state('contacts.detail.subitem', {
url: '/item/:itemId',
controller: function($stateParams, contactId){
contactId //*** Exists! ***//
$stateParams.itemId //*** Exists! ***//
}
})
Since the "contactId" parameter is resolved by the parent state, the child state will inherit that.

`ui-router` $stateParams vs. $state.params

With ui-router, it's possible to inject either $state or $stateParams into a controller to get access to parameters in the URL. However, accessing parameters through $stateParams only exposes parameters belonging to the state managed by the controller that accesses it, and its parent states, while $state.params has all parameters, including those in any child states.
Given the following code, if we directly load the URL http://path/1/paramA/paramB, this is how it goes when the controllers load:
$stateProvider.state('a', {
url: 'path/:id/:anotherParam/',
controller: 'ACtrl',
});
$stateProvider.state('a.b', {
url: '/:yetAnotherParam',
controller: 'ABCtrl',
});
module.controller('ACtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id and anotherParam
}
module.controller('ABCtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id, anotherParam, and yetAnotherParam
}
The question is, why the difference? And are there best practices guidelines around when and why you should use, or avoid using either of them?
The documentation reiterates your findings here: https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service
If my memory serves, $stateParams was introduced later than the original $state.params, and seems to be a simple helper injector to avoid continuously writing $state.params.
I doubt there are any best practice guidelines, but context wins out for me. If you simply want access to the params received into the url, then use $stateParams. If you want to know something more complex about the state itself, use $state.
Another reason to use $state.params is for non-URL based state, which (to my mind) is woefully underdocumented and very powerful.
I just discovered this while googling about how to pass state without having to expose it in the URL and answered a question elsewhere on SO.
Basically, it allows this sort of syntax:
<a ui-sref="toState(thingy)" class="list-group-item" ng-repeat="thingy in thingies">{{ thingy.referer }}</a>
EDIT: This answer is correct for version 0.2.10. As #Alexander Vasilyev pointed out it doesn't work in version 0.2.14.
Another reason to use $state.params is when you need to extract query parameters like this:
$stateProvider.state('a', {
url: 'path/:id/:anotherParam/?yetAnotherParam',
controller: 'ACtrl',
});
module.controller('ACtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id and anotherParam
}
There are many differences between these two. But while working practically I have found that using $state.params better. When you use more and more parameters this might be confusing to maintain in $stateParams. where if we use multiple params which are not URL param $state is very useful
.state('shopping-request', {
url: '/shopping-request/{cartId}',
data: {requireLogin: true},
params : {role: null},
views: {
'': {templateUrl: 'views/templates/main.tpl.html', controller: "ShoppingRequestCtrl"},
'body#shopping-request': {templateUrl: 'views/shops/shopping-request.html'},
'footer#shopping-request': {templateUrl: 'views/templates/footer.tpl.html'},
'header#shopping-request': {templateUrl: 'views/templates/header.tpl.html'}
}
})
I have a root state which resolves sth. Passing $state as a resolve parameter won't guarantee the availability for $state.params. But using $stateParams will.
var rootState = {
name: 'root',
url: '/:stubCompanyId',
abstract: true,
...
};
// case 1:
rootState.resolve = {
authInit: ['AuthenticationService', '$state', function (AuthenticationService, $state) {
console.log('rootState.resolve', $state.params);
return AuthenticationService.init($state.params);
}]
};
// output:
// rootState.resolve Object {}
// case 2:
rootState.resolve = {
authInit: ['AuthenticationService', '$stateParams', function (AuthenticationService, $stateParams) {
console.log('rootState.resolve', $stateParams);
return AuthenticationService.init($stateParams);
}]
};
// output:
// rootState.resolve Object {stubCompanyId:...}
Using "angular": "~1.4.0", "angular-ui-router": "~0.2.15"
An interesting observation I made while passing previous state params from one route to another is that $stateParams gets hoisted and overwrites the previous route's state params that were passed with the current state params, but using $state.params doesn't.
When using $stateParams:
var stateParams = {};
stateParams.nextParams = $stateParams; //{item_id:123}
stateParams.next = $state.current.name;
$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{next:'app.details'}}
When using $state.params:
var stateParams = {};
stateParams.nextParams = $state.params; //{item_id:123}
stateParams.next = $state.current.name;
$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{item_id:123}}
Here in this article is clearly explained: The $state service provides a number of useful methods for manipulating the state as well as pertinent data on the current state. The current state parameters are accessible on the $state service at the params key. The $stateParams service returns this very same object. Hence, the $stateParams service is strictly a convenience service to quickly access the params object on the $state service.
As such, no controller should ever inject both the $state service and its convenience service, $stateParams. If the $state is being injected just to access the current parameters, the controller should be rewritten to inject $stateParams instead.

Resources