angularjs workout required parameters for any state - angularjs

I an trying to list out all the URL's that exist in the stateprovider (angularjs 1.2.26).
given the example below, (very much cut down state list):
angular.module('app')
.config(function ($stateProvider) {
$stateProvider
.state('app.vendors', {
url: '/vendors',
templateUrl: 'app/vendor/list.html',
controller: 'Vendor.ListController as vm',
})
.state('app.vendor', {
url: '/vendor/{vendorId}',
templateUrl: 'app/vendor/details.html',
controller: 'Vendor.DetailsController as vm',
data: {
subnav: [
{ title: 'Details', icon: 'fa-align-left', state: 'app.vendor', permissions: 'get-vendor', exactStateOnly: true },
{ title: 'Sites', icon: 'fa-archive', state: 'app.vendor.sites', permissions: 'get-site' },
{ title: 'NCRs', icon: 'fa-copy', state: 'app.vendor.ncrs', permissions: 'get-vendor' }
],
requiredPermissions: ['get-vendor']
}
})
.state('app.vendor.sites', {
url: '/sites',
templateUrl: 'app/vendor/site/list.html',
controller: 'Vendor.Site.ListController as vm',
data: {
requiredPermissions: ['get-site']
}
})
.state('app.vendor.site', {
url: '/site/{siteId}',
templateUrl: 'app/vendor/site/details.html',
controller: 'Vendor.Site.DetailsController as vm',
data: {
requiredPermissions: ['get-site']
}
})
.state('app.vendor.ncrs', {
url: '/ncrs',
templateUrl: 'app/vendor/ncr/ncrList.html',
controller: 'Vendor.NCR.NCRListController as vm',
data: {
requiredPermissions: ['get-vendor']
}
});
});
to get to a particular vendor you would use state:
app.vendor({vendorId: 1})
to get to its site
app.vendor.site({vendorId: 1, siteId: 2})
if I pass in the $state object to a controller I can list all the states with state.get().
If I list them the urls only contain the last part (i.e. what is in the config, and relative to its parent). I can use $state.href('app.vendor.site') which will give me almost the whole url, but misses out the parameters. I am trying to find a way at runtime to know what or at least how many parameters it requires.
My goal is to try and create a basic smoke test for every page in our Angular app to ensure it loads something and doesn't through errors in the console. I dont want to have to manually maintain a list of urls with params. (all our params are int IDs so I can simply use "1" in the params to test the url).

The private portion of the state contains params and ownParams objects. You can use a decorator to access those internal variables. See my previous answer regarding exposing the entire internal state object using a decorator: UI-Router $state.$current wrapper for arbitary state
After decorating your state objects, use the $$state() function to retrieve the private portion. Then query the state for its params and generate the href.
angular.forEach($state.get(), function(state) {
var paramKeys = state.$$state().params.$$keys();
var fakeStateParams = {};
angular.forEach(paramKeys, function(key) { fakeStateParams[key] = key; });
console.log($state.href(state, fakeStateParams));
});

Related

creating a route Param using ui-router

I am not able set up and maintain an route param. When I set the param. It goes to the correct place. However, if I change the route the content does not change. I tried to make one view object, but home.name state does not render with this approach. I can only get templates to show using two absolute paths.
Could not resolve '#/home/Adam Harvey' from state 'home'
I found a great link https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service
To start: I created a simple list with ng-repeat. Here is a shortened example
$scope.iterants =
[
{
name: 'Adam Harvey',
type: 'Hotel',
dates: '01/21/2015-01/22/2015',
location: 'Rockville Maryland',
status: 'Confirmed'
},
{
name: 'Jana Harvey',
type: 'Hotel',
dates: '01/21/2015-01/22/2015',
location: 'Rockville Maryland',
status: 'Confirmed'
}
];
In my html template I want to place a ui-sref on top of each person's name as a link to view them on another template.
<td><a ui-sref="itinerary.name{{name:person.details}}">{{person.name}}</a></td>
In my config I set up like so with the controller getting passed the $stateParams
.config(function config( $stateProvider ) {
$stateProvider.state( 'home.name', {
url: '/home/:name',
views: {
"main": {
controller: 'AboutCtrl',
templateUrl: 'src/app/about/about.tpl.html'
}
},
data: {pageTitle:'About'}
});
})
.controller( 'AboutCtrl', function AboutCtrl( $scope, $stateParams ) {
$stateParams.name;
});
In the home controller. I have set up my config and controller like so...
.config(function config( $stateProvider ) {
$stateProvider.state( 'home', {
url: '/home',
views: {
"main#": {
controller: 'HomeCtrl',
templateUrl: 'src/app/home/home.tpl.html'
},
resolve: [{
name: ['$stateParams', function($stateParams){
return $stateParams.name;
}]
}]
},
data:{ pageTitle: 'Home' }
});
})
Updates
The issue is your usage of the ui-sref directive. You have to pass the state name and then parameters as an object, like so:
<td><a ui-sref="home.name({ name: person.name })">{{person.name}}</a></td>

AngularJS and UI-Router simplify states

I am fairly new to Angular (only been using it a couple of months) and although I have learnt so much since I read my first book, there are a few concepts or best practices that elude me.
This might be one of them. I have a project at the moment which has a set of states, basically it has
collections - which it lists, in this case I have made it a stand alone state, but it requires a centerId parameter (because collections belong to a center).
Create collection - Because this requires the same centerId, this "could" be a child state of collections, but I have found that in doing so, whenever a collection is created I have to update the parent's collections. So I have not made this a child of the collections state, instead it has it's own centerId parameter which I pass.
Edit collection - Again this is the same as Create collection, this state uses the same controller and indeed the same template as create collection.
Deliver - Deliver collection is a state that allows a user to update a collection with a set of specific information (POD, signature, etc) so this has it's own state and template
Collect - Collect is the same as deliver except instead of a POD, it has a POC (Proof of collection), so this uses the same controller and template as deliver
Import - Import is used to import a list of collections via csv.
Now, all these states have one thing in common, they all require the centerId. Like I have previously said, I could make them all children of the collections state, but there is a complication when updating the collections list on the parent state. It is grouped, so the resolved collection actually goes through some code before being presented to the view.
Here is my current state layout:
// Now set up the states
$stateProvider.state('collections', {
url: '/collections/:centerId',
templateUrl: '/assets/tpl/collections/index.html',
controller: 'CollectionsController',
controllerAs: 'controller',
resolve: {
// Resolve our collections before the state loads
collections: ['$stateParams', 'CollectionsService', function ($stateParams, service) {
// Get our center id
var centerId = $stateParams.centerId;
// Return our collections
return service.get(centerId);
}]
},
data: {
requireLogin: true,
pageTitle: 'Collections'
}
}).state('savingCollections', {
url: '',
abstract: true,
resolve: {
// Resolve our statuses
statuses: ['Api', function (api) {
// Return our statuses
return api.get('/api/statuses');
}]
}
}).state('savingCollections.import', {
url: '/collections/:centerId/import',
views: {
'#': {
templateUrl: '/assets/tpl/collections/import.html',
controller: 'ImportCollectionsController',
controllerAs: 'controller',
}
},
data: {
requireLogin: true,
pageTitle: 'Import your collections'
}
}).state('savingCollections.create', {
url: '/collections/:centerId/create',
views: {
'#': {
templateUrl: '/assets/tpl/collections/save.html',
controller: 'SaveCollectionController',
controllerAs: 'controller'
}
},
resolve: {
// Creates a blank collection so that the injection works
collection: function () {
// Return an empty object
return {};
}
},
data: {
requireLogin: true,
pageTitle: 'Create your collection'
}
}).state('savingCollections.edit', {
url: '/collections/:centerId/edit/:id',
views: {
'#': {
templateUrl: '/assets/tpl/collections/save.html',
controller: 'SaveCollectionController',
controllerAs: 'controller'
}
},
resolve: {
// Gets a collection from the API
collection: ['$stateParams', '$q', 'CollectionsService', function ($stateParams, $q, service) {
// Get our ids
var id = $stateParams.id,
centerId = $stateParams.centerId;
// return our collection
return service.get(centerId, id);
}]
},
data: {
requireLogin: true,
pageTitle: 'Update your collection'
}
}).state('savingCollections.receiveCollections', {
url: '',
abstract: true,
params: {
selected: null
},
resolve: {
selected: ['$stateParams', function ($stateParams) {
// Get our selected items
var selected = $stateParams.selected;
// If we have something in our parameters
if (selected) {
// Save them into our session
sessionStorage.selected = angular.toJson(selected);
}
// Return our selected items
return angular.fromJson(sessionStorage.selected);
}]
},
}).state('savingCollections.receiveCollections.collect', {
url: '/collections/:centerId/collect',
views: {
'#': {
templateUrl: '/assets/tpl/collections/receive.html',
controller: 'CollectCollectionsController',
controllerAs: 'controller'
}
},
params: {
collecting: true
},
data: {
requireLogin: true,
pageTitle: 'Collect'
}
}).state('savingCollections.receiveCollections.deliver', {
url: '/collections/:centerId/deliver',
controller: 'CollectCollectionsController',
controllerAs: 'controller',
views: {
'#': {
templateUrl: '/assets/tpl/collections/receive.html',
controller: 'CollectCollectionsController',
controllerAs: 'controller'
}
},
data: {
requireLogin: true,
pageTitle: 'Deliver'
}
});
I hope this is enough information.
If you are unsure what I am trying to do, basically I want to make my states easy to read and understand. I would like it so that if someone looks at the states, they will know exactly what is going on.
Also, I would like to refrain from ugly ui-srefs such as this:
ui-sref="savingCollections.receiveCollections.deliver({ centerId: controller.centerId, selected: [collection]})"
any help would be greatly appreciated.
Your state declarations aren't bad. I find them fairly readable. One bit of advice I have to make things more readable is to to move the resolve functions out of the state declarations to another place and simply reference them by name. You can put them below your state code or you can place them in a different service entirely.
I put mine in a different service, like so:
var resolveFunctions = {
resolveData: function($q, $stateParams, dataService) {
//getData returns a promise
return dataService.getData($stateParams.id);
}
}
angular.module("myModule").constant("resolveFunctions", resolveFunctions);
This resolve function retrieves some data based on an ID in the state parameters.
Then I can use the resolveFunctions service in the place I declare my states:
//Inject resolveFunctions service as a dependency wherever you are defining your states
resolve: {
data: resolveFunctions.resolveData
}
I hope that makes sense.
As for switching between states, I use state parameters to pass data between states. The resolve functions for the new state can then read from the state parameters like in the resolveFunctions example above.
You can use $state.go() in your controller to switch between states with state parameters (you can add an ng-click handler with a $state.go call).
The following code goes to the "savingCollections" state and passes a center ID as a state parameter. Your can either extract the centerId in the controller or in one of the resolve functions using $stateParams.centerId
$state.go("savingCollections", { centerId: centerId });
I haven't done so personally, but I think you can also do that in ui-sref as well.
ui-sref="savingCollections({ centerId: controller.centerId })"
If that ui-sref works, that would be easier than calling $state.go()

Best practices for AngularJS CRUDS

I'm trying to figger out the best way to create CRUDS (Create/Read/Update/Delete) in AngularJS by calling API requests back and forth.
I've read some articles about code structure but still I'm not sure how to maintain a clean structure by creating those CRUDS.
In this project I'm using ui-router for state management.
.state('user', {
url: "/user",
templateUrl: "app/views/user/index.html",
controller: "UserController as user",
resolve: {
data: ['UserService', function (UserService) {
return UserService.all();
}]
}
})
.state('createUser', {
url: "/user/create",
templateUrl: "app/views/user/create.html",
controller: "UserController as user"
})
What is the best way to make this work? Because I'm using the data variable, which is being resolved in my user state in the UserController, I'm not able to use the same UserController for my createUser state, because the injected variable cant be resolved.
Is there maybe any way I can check on this so I can just use the UserController for all my user-specific actions?
You can certainly use the same controller if you want, eventhough it is usually a better to have a new controller for create action. Usually people either write a special controller for each CRUD action or sometimes they use the same controller for Create and Update.
In your specific case, if you want to use the same controller, just return an empty array of users as the data variable. Like this:
.state('user', {
url: "/user",
templateUrl: "app/views/user/index.html",
controller: "UserController as user",
resolve: {
data: ['UserService', function (UserService) {
return UserService.all();
}]
}
})
.state('createUser', {
url: "/user/create",
templateUrl: "app/views/user/create.html",
controller: "UserController as user",
resolve: {
data: [function () {
return [];
}]
}
})

Angular UI Router: How to stop reloading based on query parameters change?

Here is my ui-router configuration:
$stateProvider
.state('search.filter.results', {
url: '/results?profileId&keywords',
views: {
'profiles#': {
templateUrl: 'profile-list/profile-list.html',
controller: 'ProfileListCtrl'
},
'profile-summary#search.filter.results': {
templateUrl: 'profile-list/profile-summary/profile-summary.html'
},
'profile-details#search.filter.results': {
templateUrl: 'profile-list/profile-details/profile-details.html',
controller: 'ProfileDetailsCtrl'
}
}
})
ProfileListCtrl has the following method:
$scope.showProfileDetails = function(profileId) {
$state.go('search.filter.results', { profileId: profileId });
};
The problem is, when this method is executed, ProfileListCtrl is instantiated and the view is reloaded.
I would like the reload to happen only if the keywords query parameter changes, but not when the profileId parameter changes.
What would be the best way to achieve this?
I would suggest: split this state into 2 states.
The "parent" keeping the profileId,
the "child" working with keywords.
Until the profileId is not changed, the parent controller won't be re-instantiated.
I created this example with this simplified states definition:
.state('profile', {
url: '/results/:profileId',
views: {
'profiles#': {
//templateUrl: 'profile-list/profile-list.html',
template: '<div>'
...
'<div ui-view=""></div></div>',
controller: 'ProfileCtrl'
},
}
})
.state('profile.results', {
url: '/?keywords',
views: {
'': {
//templateUrl: 'profile-list/profile-list.html',
template: ...
controller: 'ProfileListCtrl'
},
}
})
NOTE: state defintion here is simplified, to just show the split. Not all the details. Also, the child state target parents unnamed view ui-view="", because these should be nested. Finally, parent could even be abstract
This approach also will require some small adjustments of the url. It is not working well if the child url defintion would start with ? like: url: '?keywords'. So I used different pattern:
/results/profileId-value/?kewords=abc

How to send and retrieve parameters using $state.go toParams and $stateParams?

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

Resources