I've used $state.go to pass in an 'employee' object as a parameter to a page where the id is also used as a parameter in the url, but when the page is refreshed, all $stateParams are cleared from the state.
How can I prevent this from happening, and somehow store the data for the employee (as I said, employee id is used in url), so that when the page is refreshed, $stateParams is still populated with this employee's data based upon the id in the url?
State definition:
.state('employees/employeeDetails', {
url: '/employees/employeeDetails/:id',
templateUrl: 'views/employees/employeeDetails/employeeDetails.html',
params: {
employee: null,
id: null
},
controller: 'employeeDetailsMainController',
controllerAs: 'employeeDetailsMain',
resolve: {
lazyLoad: function($ocLazyLoad) {
return $ocLazyLoad.load('js/controllers/employees/employeeDetails/employeeDetailsMainController.js');
}
}
})
Directive with $state.go:
app.directive('viewEmployee', function() {
var directive = {
restrict: 'A',
scope: {
employee: '=',
state: '#state'
},
controller: function($scope, $state, $stateParams) {
$scope.goToEmployee = function() {
$state.go('employees/employeeDetails', { employee: $scope.employee, id: $scope.employee.id });
}
},
bindToController: {
employee: '='
},
link: function(scope, element, attrs) {
element.find('button').bind('click', function(index, employee) {
scope.goToEmployee();
})
}
}
return directive;
})
.state('employees/employeeDetails', {
url: '/employees/employeeDetails?employee&id',
templateUrl: 'views/employees/employeeDetails/employeeDetails.html',
controller: 'employeeDetailsMainController',
controllerAs: 'employeeDetailsMain',
resolve: {
lazyLoad: function($ocLazyLoad) {
return $ocLazyLoad.load('js/controllers/employees/employeeDetails/employeeDetailsMainController.js');
}
}
});
use querystring params li these it will retain them after refresh.
for more info check doc ui-router query params
you need to add in the route definition another item called :employee
url: '/employees/employeeDetails/:id/:employee'
But since employee is an object - so unless you wanna define each item as a param in the url or do a lookup based on the id when you get to the state of the employee itself from the backend... you have other options.
use a service to store the employee info
localStorage
UPDATE:
So based on what it seems the only genuine option should be to make a backend query to fetch the employee info based on the ID passed in the route. This is based on the fact that an end user could end up directly on the page and such means that there is a good chance the info will not be available in the service or the localstorage - you could check if its there and if not make a request (smart service).
You can use
$location.search($location.search('employeeDetails', { employee:
$scope.employee, id: $scope.employee.id });
and than get parameters that you need from $stateParams or if you use newest version $transation$ or on state on resolve
selectedFromUrl: ($location) => {
'ngInject';
return $location.search().employeeDetails;
},
If nothing help you can use cache
Related
I have one parent state and child states for that parent. Parent state have one optional parameter reviewId. Everything is working good, when I am sending parameter in $state.go state is opened correctly. What I need is to append (add that optional query parametar in url) when creating new review (id is populated after making back end call).
This is url for opening existing review with id:
http://localhost:3000/#/planning?reviewId=1000.
When creating new review url is:
http://localhost:3000/#/planning. So what I need is after review is saved (Id is populated) to append ?reviewId=newId without state.go.
$stateProvider
.state('root.planning', {
url: '/planning?reviewId=',
abstract: true,
params: {
reviewId: null,
readOnlyMode: null,
locked: null,
editButton: null
},
reloadOnSearch:false,
views: {
'#': {
templateUrl: 'src/app/modules/planning/views/planning.tpl.html',
controller: 'PlanningMainController as reviewvm'
}
}
})
.state('root.planning.planning', {
url: '',
params: {
reviewId: null
},
data: {
planningChild: true,
stateTypeCd : "PLA"
},
reloadOnSearch:false,
templateUrl: 'src/app/modules/planning/views/tabs/tab.planning.view.html',
controller: 'PlanningController as planningvm'
})
EDIT
Tried also to update reviewId in stateParams after Id is accessed and then call $state.reload(). With this approach stateParams.reviewId is null after reload. And I was wondering if I can do this without reload.
$stateProvider
.state('root.planning', {
url: '/planning?reviewId',
abstract: true,
controller: function($scope, $stateParams) {
$scope.reviewId = $stateParams.reviewId;
}
call the function if $statePrams.reviewId is available. hope this will help you
I have a state that looks like something like this
.state('home.foo.bar', {
url: 'view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
reloadOnSearch: false
})
if I navigate to this state in my template with
ui-sref="home.foo.bar({one: valueOne, two: valueTwo})"
all is well and the url is
.com/view?one=valueOne&two=valueTwo
however if I refresh the page the url changes to just .com/view and neither $state.params or $stateParams contains the values.
if I add a resolve step like so
.state('home.foo.bar', {
url: 'view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
resolve: {
p: function($location, $stateParams, $state) {
console.log($location.search(), $stateParams, $state);
if(!$stateParams.one && !$stateParams.two {
$stateParams.one = $location.search().one;
$stateParams.two = $location.search().two;
} else {
return $location.search().one;
}
}
},
reloadOnSearch: false
})
then when I initially navigation to the state in my template the log statement prints nothing for $location.search() but shows oneValue and twoValue in the $stateParams and $state.params. When I refresh its opposite $location.search() shows the parameters and their values correctly but $stateParams and $state.params are empty.
adding the if statement and setting the values explicitly resolves my bug but this cant be working as intended can it?
am I missing something obvious?
I think it might be because you are missing a slash at the beginning or your URL:
.state('home.foo.bar', {
url: '/view?one&two',
views: {
'base': {
templateUrl: 'blah',
controller: 'blahCtrl'
}
},
reloadOnSearch: false
})
I had the same problem. However it turned out that the problem is not angular. Check your routing on the server, there may change url.
In my case, I interfered the regular statechange/statestart/statesuccess
so in my resolving in statesuccess I have added:
//$state.go(toState,toParams);
//when I manupulated the regular ui-router params
$state.go($rootScope.toState,$rootScope.toParams);
So ve aware of passing toParams when you change the regular ui-router parameters control.
I'm following this sample on adding custom data to state objects but can't figure out what I'm doing wrong.
The idea is to have a Users state with a template that have 3 views: header, content and footer. The header-view gets updated with the child state's title and breadcrumb. The unnamed content-view will show the child's template (like a users list) and the footer will show summary data.
I have 2 issues:
My custom data object on my header's current state is undefined. ['Cannot read property 'title' of undefined]
If I exclude the custom data and just set the $scope values directly in the controller, it works fine but now get a 404 error. [GET http://localhost:3000/users 404 (Not Found)]. This makes no sense to me.
Am I on the right track with what I want to do? I'm unsure if I need the custom data or can I just set it in the controller?
angular.module('users').config(['$stateProvider',
function($stateProvider) {
// Users state routing
$stateProvider
.state('users', {
onEnter: function(){
console.log('state change to users');
},
abstract: true,
url: '/users',
views: {
'': {
templateUrl: 'modules/users/views/users.html'
},
'header#users': {
templateUrl: 'modules/core/views/page-header.html',
// data: { //<- this is undefined in the controller
// title: 'Users',
// breadcrumb: 'Home / Users'
// },
controller: function($scope, $state) {
$scope.title = 'Users'; //$state.current.data.title;
$scope.breadcrumb = 'Home / Users'; //$state.current.data.breadcrumb;
}
},
'footer#users': {
templateUrl: 'modules/core/views/page-footer.html'
}
}
})
.state('users.list', {
onEnter: function(){
console.log('state change to users.list');
},
url: '',
templateUrl: 'modules/users/views/users-list.html'
})
.state('signin', {
url: '/signin',
templateUrl: 'modules/users/views/authentication/signin.client.view.html'
});
}
]);
<section>
<div ui-view="header"></div>
<div ui-view=""></div>
<div ui-view="footer"></div>
</section>
You're receiving undefined because you're trying to access a data object belonging to the state itself, rather than the view you've defined it on. Typically, data would be defined against the state, but if you wish to do so against the view, you will need to use:
views: {
'header#users': {
data: {
title: 'Users',
},
controller: function($scope, $state) {
$scope.title = $state.current.views['header#users'].data.title;
}
}
}
As for your 404 error, there is nothing presented in the code that would cause this, so the problem must lay elsewhere.
I have a state that has multiple views declared in it as follows:
$stateProvider
.state('home.details.item', {
url: '^/details',
views: {
'chartsView': {
templateUrl: 'charts.html',
controller: 'chartsCtrl'
},
'gridView': {
templateUrl: 'grid.html',
controller: 'gridCtrl'
},
'detailsView': {
templateUrl: 'details.html',
controller: 'detailsCtrl'
}
}
});
I need to reload one of the views without reloading the whole state, without using $state.go($state.current,null , {reload: true}) , and if possible, from the chartCtrl reload detailsCtrl. Is that possible?
I'd say, that the UI-Router solution should be built arround *states*, not views.
(I created working example here). Other words, if there are
some views which should not be reloaded and
some other views, which should be reloaded
... it calls for state nesting. Let's move that view into child state:
.state('home.details.item', {
url: '^/details',
views: {
'chartsView': {
templateUrl: 'tpl.charts.html',
controller: 'chartsCtrl'
},
'gridView': {
templateUrl: 'tpl.grid.html',
controller: 'gridCtrl'
},
// 'detailsView': {
// templateUrl: 'details.html',
// controller: 'detailsCtrl'
// }
}
})
.state('home.details.item.more', {
views: {
'detailsView#home.details': {
templateUrl: 'tpl.details.html',
controller: 'detailsCtrl'
}
}
})
We also need a state, which will do the reload. We could use other way, e.g. with some changing parameter in state more, but that would mean to change the param value on each call. With this specil state, we can easily reload our state 'more':
.state('reload', {
parent: "home.details.item",
views: {
'detailsView#home.details': {
// this controller will just redirect to 'more' and make it fresh...
controller: ['$state', function($state) { $state.go('^.more')}],
}
}
})
And with these simple controllers we can do all that required stuff:
.controller('chartsCtrl', function ($scope, $state) {
var childName = ".more";
$state.go(childName); // default is a sub state 'more'
})
.controller('detailsCtrl', function ($scope) {
$scope.when = Date.now();
})
Having this: we can call this to reload just details:
<a ui-sref="reload">force reload detail view</a>
Now, when navigating to reload, we will be redirected to state "more" and our view will be rerendered.
SUMMARY:
In general, UI-Router represents state machine. I would strongly suggest:
Do not worry to think in states. Views are just their representation in the DOM.
If there are some features related, they most likely represent state. If others do not relate (should be changed often or rarely) they belong to other state. It could be parent, child or sibling...
Check it here
For the following state definitions and the url http://localhost/#/foo/bar, I expect to receive the console output
/foo
/foo/bar
However, I only get one /foo. Is my expectation wrong? What can I do to get the parent controller invoked as well?
$stateProvider
.state('foo', {
url: '/foo',
controller: function($scope) {
console.log('/foo')
},
abstract: true
})
.state('bar', {
parent: 'foo',
url: '/bar',
controller: function($scope) {
console.log('/foo/bar')
}
})
Update
Even more strange, when I add
onEnter: function() {
console.log('enter')
}
enter is printed (but not /foo/bar).
Update 2
I'd like to add a resolve attribute to the parent route and have all children wait for a deferral to be resolved until their controllers get instantiated. This is what it follows: https://github.com/angular/angular.js/issues/5854 The scope is used to store the result of the deferral (which is a $http response).
On the abstract parent state, there should be a ui-view directive so the child state knows where to insert itself.
.state('foo', {
url: '/foo',
abstract: true,
controller: function($scope) {
console.log("/foo");
},
template: '<ui-view/>'
})
Here is a working example: http://plnkr.co/edit/OskGCBSQGhEXlxaNLZtD?p=preview
Update 2 response
You should be able to just add a resolve to the abstract parent state:
resolve: {
user: function($q, $timeout) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("a resolved user");
}, 1000);
return deferred.promise;
}
}
Here is a working example of that: http://plnkr.co/edit/cm0xUP8LmruHiVBhccuf?p=preview
Yeah, your expectation is wrong: only one route will be matched. If you want to match the second route, you'll need to set the url to /foo/bar, but there's no way to hit them both with one URL.
I'm not sure what you're trying to do but if you want to access url parameters you can use /foo/:paramName and the $routeParams service.