Ui-Router $stateParams with nested routes - angularjs

I'm trying to route to a url with the selected object's id value within a nested state but it's not working.
ROUTER:
.state('sessions.editor', {
url: '/editor/:id',
templateUrl: 'views/editor.html',
controller: 'EditorCtrl'
})
CONTROLLER (EditorCtrl):
$scope.theSession.$id = $stateParams.id;
//$scope.session object is successfully returned with the $id property.
PREVIOUS CONTROLLER:
//when button is clicked
$state.go('sessions.editor');
However, when I try to set it to $stateParams, the id property becomes undefined. I tested this with another property on $scope.session and that property becomes undefined also when I try to set it $stateParams.
I'm thinking the trouble has something to do with the state being nested. Is this correct?

I believe you need to add the params key to the state for UI-Router, example:
.state('sessions.editor', {
url: '/editor/:id',
controller: 'EditorCtrl',
templateUrl: 'views/editor.html',
params: {
id: {
value : '',//Default
squash: false
}
}
})
Then use your existing implementation in the EditorCtrl for $stateParams.

Related

UI-router: nested state resolving

I have two states: a 'contacts' and a 'contacts.all' which is a child state of the previuos. I want this child state to pre-load data from server:
.state('contacts', {
url: "/contacts",
controller: 'ContactsCtrl'
})
.state('contacts.all', {
url: "/all",
resolve: {
initialContacts: function (contactsCRUD) {
return contactsCRUD.getAll().$promise;
}
}
})
and then inject the to the ContactsCtrl controller:
function ContactsCtrl($scope, initialContacts) {
$scope.Contacts = initialContacts || {};
}
I do need these data preloaded only in the 'contacts.all', not 'contacts' state. If I leave this code as is and try to navigate any of these states then I'll get an inject error saying "unknown provider" for the "initialContacts" because the controller seems to be instantiated before the initialContacts is known.
What is normal way to do it? I know I can add a "resolve" option to the parent state and set initialContacts to something like {} but I dont think it's a good practice.

ui-router changes to inherited object parameter not persisted to sibling state

I am trying to set up some states as follows
state('search', {
url: '',
templateUrl: '/modules/core/views/search.html',
controller: 'SearchController'
}).
state('check', {
controller: 'CheckController',
abstract: true,
template: '<ui-view></ui-view>',
params: {
subject: null
}
}).
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html'
}).
state('check.personal', {
templateUrl: '/modules/core/views/check-personal.client.view.html'
});
Search results are passed into the 'check' state with parameterised ui-sref's.
Within each child state of 'check' I want to change some properties on subject and then call $state.go (preferably without passing subject as a parameter) to move along to the next state in the journey.
I'm finding though that
CheckController is reloaded if the parameter is not supplied on $state.go to subsequent states or if I supply it but change it slightly.
If CheckController is reloaded, changes made to the subject object aren't persisted through the transition as if $stateParams contains a copy of the subject object taken at some point earlier by ui-router.
Is there a way to prevent / control either of these behaviours?
Edit:
I found this pattern works out:
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html',
params: {
subject: null
},
controller: function($scope, $stateParams) {
$scope.$parent.subject = $stateParams.subject;
}
}).
Removing the param from the abstract parent, adding it to the first state in the journey and assigning the property on the parent scope with a small controller.
I think in your config for your child states you can tell ui-router to prevent reloading with this:
state('check.status', {
templateUrl: '/modules/core/views/check-status.client.view.html',
reloadOnSearch: false
})

$stateParams field is undefined if not located within its corresponding ui-view

I am trying for a parent state/view to access a child state/view. I am looking for a workaround.
My state provider configuration is as follows:
.state('advertisement', {
url: '/advertisement', abstract: true, parent: 'authenticated'
})
.state('advertisement.new', {
url: '/new',
abstract: true,
views: {
'#': {
controller: 'AdvertisementSaveCtrl',
templateUrl: 'advertisement/views/advertisement.form.html'
}
}
})
.state('advertisement.new.field', {
url: '/:fieldId',
views: {
'#advertisement.new': {
templateUrl: function ($stateParams){
return 'advertisement/views/fields/advertisement.' + $stateParams.fieldId + '.html';
}
}
}
})
In my markup:
<li ui-sref-active="active" ng-class="{'active': isActive()}"><!-- not in scope! -->
<div ui-view></div><!-- this is the view targeted by advertisement.new.field -->
The advertisement.new.field state changes according to the current field (:fieldId). I have set up a number of links (located outside of my nested ui-view) that change the state by changing the :fieldId state param but obviously, the $stateParam.fieldId is undefined if it is not within the corresponding ui-view div.
To put it differently, it seems the isActive method has no access to $stateParam.fieldId...
Can anyone please provide a workaround?
In your code samples I don't see in which controller the isActive() method is defined. But it must be defined in a controller.
I assume the isActive() function is defined on a controller named MainController.
You can then inject the $state service in this controller and via the $state.params you can access the parameters of the active state even outside of the <ui-view> tag:
app.controller('MainController', function ($scope, $state) {
console.log($stateParams);
this.isActive = function() {
// access params of active state via $state.params
console.log($state.params.fieldId);
};
});

AngularJS ui-router abstract state

I recently migrated from ngRoute to ui-router. I have a page that has 2 sections.
Right section displays current item details,
Left section shows similar items to current item.
Once user clicks a similar item from left list, right section will reload with clicked item id and left section will stay same.
To keep left section still on user item navigations, i defined left section as an abstract state and right section as it's child state. (you cant view similar items if you arent looking to an item).
Left section (listview) is parent and contains a ui-view in HTML to embed item details.
To load similar items on page open, i need to know which item is being loaded by my child state. But i cant define same url for both abstract state and it's child state.
i tried to resolve $stateParams in abstract state with no chance.
my state configuration is below
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('item', {
//url: '/items/:itemName/:itemID'
url: '/items',
abstract: true,
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}).state('item.itemDetails', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
});
how can i access itemID from my abstract state (from ItemListController)?
i solved the problem using angular scope variable. parent contains an ui-view to show child state contents. so child and parent state share same scope therefore i can notify parent about selected item id and parent can update it's view.
parent state has a scope function like this
$scope.notifySelectedItem = function(selectedItemID){};
and child state can call this function from it's scope
$scope.notifySelectedItem($stateParams.itemID);
I would model this as a single state with two named views. The subject of your state is a single item and related data, so your state should probably revolve around that. You have described two views, one which shows the item details, and another which shows other things, but the primary subject is still the one item.
$stateProvider
.state('item', {
url: '/item/:itemID',
views: {
detail: {
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
}, related: {
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}
}
});
In your parent view, add two named ui-views, where the views defined above will plug into:
<div ui-view="related"></div>
<div ui-view="detail"></div>
Now, you can enhance this using resolves to preload the data before the controller is invoked:
$stateProvider
.state('item', {
url: '/item/:itemID',
resolve: {
item: function($http, $stateParams) {
return $http.get('/item/' + $stateParams.itemID);
},
related: function($http, $stateParams) {
return $http.get('/item/' + $stateParams.itemID + "/related");
},
},
views: {
detail: {
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail'
}, related: {
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
}
}
});
app.controller("ItemListController", function($scope, related) { $scope.related = related.data; });
app.controller("ItemDetailController", function($scope, item) { $scope.item = item.data; });

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.

Resources