Question: For some reason I can't get my controller to recognize my url parameters across sessions.
Background: I have a nested view called modal that takes a parameter, whose url is /modal/:id (eg: /#/modal/1/ or /#/floorplan/1+2/). Ideally, when the user goes to this url, a modal will automatically open with the resource(s) with the given id.
Since the parent state and the child state(modal) are being handled by the same controller, the modal state has a custom data attribute (modalStatus) in its configuration set to true. When this custom attribute is enabled the modal is displayed.
I can currently go from the parent state to the nested state and trigger the modal but when I start a new session or refresh the page with a url like /modal/3, the application fails to read the parameters ($stateParams), which is being logged as an empty object.
I have tried using onEnter and Resolve but I'm not exactly clear on how to use them in this scenario.
Router
$stateProvider
.state('home', {
name: 'home',
url: '/',
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.state('home.modal', {
url: 'modal/:id/',
data: {
modalState: true
},
controller: 'MainCtrl'
})
Relevant part of controller:
$scope.init = function() {
console.log($stateParams);
if ($state.current.data) {
if ($state.current.data.modalState === true) {
$scope.openModal();
}
}
};
$scope.init();
edit: plunkr
You could try adding the params option:
$stateProvider
.state('home', {
name: 'home',
params: {
id: null
},
url: '/',
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.state('home.modal', {
url: 'modal/:id/',
data: {
modalState: true
},
controller: 'MainCtrl'
});
The child state home.modal should inherit the params of the parent. On your ui-sref from one state to another, pass the param like so:
<a ui-sref="home.modal({ id: x })"></a>
Then your url would turn out to be /modal/:x (where x is the number).
Also the $stateParams should then show the params as you wished.
Related
I have the following UI Router configuration witch includes a login page (/login), an abstract page that will be the parent of all my admin pages (/admin) and a list page that extends from the abstract page (/admin/list):
$stateProvider
.state('login', {
url: '/login',
templateUrl: '/pub/login.html',
controller: 'LoginController',
controllerAs: '$loginCtrl',
onEnter: function () {
console.log("enter /login");
}
})
.state('admin', {
abstract: true,
url: '/admin',
templateUrl: '/tmpl/templateAdmin.html',
onEnter: function () {
console.log("enter admin abstract");
// Validate JWT here and if not authenticated forward to login
$state.go('login');
}
})
.state('admin.list', {
url: '/list',
templateUrl: '/prv/clientsList.html', // loaded into ui-view of parent's template
controller: 'AppController',
controllerAs: '$appCtrl',
onEnter: function () {
console.log("enter admin/list");
}
})
I want to implement a logic that will validate if the user is authenticated when accessing an /admin/* page. In order to do that I added an onEnter function in my abstract state so that I could use $state.go('login'). But this doesn't work. How can I do this? Is there a better way to validate authentication inside UI Router?
In this case, I think it's better if you used resolve instead of onEnter.
Mainly because you want to check if the user is authenticated before navigation. If the objects inside the block don't resolve then the controller passed to the state won't be instantiated.
.state('admin', {
abstract: true,
url: '/admin',
templateUrl: '/tmpl/templateAdmin.html',
resolve: {
currentUser: function (authService) {
return authService.getUser();
}
}
}
The root state would try and resolve the user for all child views nested inside.
Here's the link to the docs on Nested States and Inherited Resolved Dependencies:
https://github.com/angular-ui/ui-router/wiki/Nested-States-&-Nested-Views
I'm new to angularjs and bootstrap and I'm recently working on a web app which requires 2 different set of views (public and private).
For the public view, everyone can see it and it has it's own top menu navbar and the corresponding content.
For the private view, only authenticated users are able to see. The private view should have a totally different top menu and its corresponding content. (Maybe a side menu navbar but this is off topic).
I've read the api page for ui-router from here. And I've implemented the navigation like this. (plunker)
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('public', {
'abstract': true,
views: {
'mainView#': {
templateUrl: 'public.html'
}
}
})
.state('home', {
parent: 'public',
url: '/',
templateUrl: 'home.html'
})
.state('login', {
parent: 'public',
url: '/login',
templateUrl: 'login.html',
controller: 'LoginController'
})
.state('private', {
'abstract': true,
views: {
'mainView#': {
templateUrl: 'private.html'
}
}
})
.state('dashboard', {
parent: 'private',
url: '/dashboard',
templateUrl: 'dashboard.html'
})
.state('settings', {
parent: 'private',
url: '/settings',
templateUrl: 'settings.html'
})
.state('logout', {
parent: 'private',
url: '/logout',
templateUrl: 'logout.html',
controller: 'LogoutController'
})
});
The example plunker I had above is working, but I'm not sure this is the "best" approach for handling such navigation. I'd very appreciate if someone can help me enhancing my solution.
Thanks in advance.
I like your approach and it looks clean. However, one thing I see it lacking is security. You can quickly add security by adding a resolve dependency.
Resolve
You can 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.
The resolve property is a map object. The map object contains key/value pairs of:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}:
If string, then it is an alias for a service.
Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before the controller is instantiated and its value is injected into the controller.
.state('dashboard', {
parent: 'private',
url: '/dashboard',
templateUrl: 'dashboard.html',
resolve:{
promiseObj: function($http){
// $http returns a promise for the url data
// returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError
return $http({method: 'GET', url: '/someUrl'}); // confirm here that the user is logged in
}
})
I am using the angular UI state router using this example:
http://plnkr.co/edit/IzimSVsstarlFviAm7S7?p=preview. The router looks like this it has different urls for each state:
// HOME STATES AND NESTED VIEWS ========================================
.state('home', {
url: '/home',
templateUrl: 'partial-home.html'
})
// nested list with custom controller
.state('home.list', {
url: '/list',
templateUrl: 'partial-home-list.html',
controller: function($scope) {
$scope.dogs = ['Bernese', 'Husky', 'Goldendoodle'];
}
})
// nested list with just some random string data
.state('home.paragraph', {
url: '/paragraph',
template: 'I could sure use a drink right now.'
})
// ABOUT PAGE AND MULTIPLE NAMED VIEWS =================================
.state('about', {
url: '/about',
views: {
'': { templateUrl: 'partial-about.html' },
'columnOne#about': { template: 'Look I am a column!' },
'columnTwo#about': {
templateUrl: 'table-data.html',
controller: 'scotchController'
}
}
});
I would like to implement the UIrouter without changing the urls:at the moment the url changes when I change the state. Suppose I am trying to create a page where I have to add a parameter how can I pass on the parameter?
You can not do that. Only one way is custom implementation of routing. I made one time something like that. All containers for all subpages were fixed. Content was dynamically loaded using $http post. Changes were detected by listening of visible ID of container. A lot of stupid work. Most easier is to use ui - routing.
I have my routes set up as below. Its too frustrating that the view in view.tab is loaded but its controller isn't called. I tried without the paramaters, but it still doesn't work as expected. Does anyone have any idea on how to solve this?
$stateProvider
.state('index', {
url: '/',
templateUrl: viewsRoot + 'restaurants/index.htm',
controller: 'RestaurantsCtrl'
})
.state('view', {
url: '/view',
controller: 'RestaurantsViewCtrl',
templateUrl: viewsRoot + '/restaurants/view.htm'
})
.state('view.tab', {
url: '/orders',
// controller: 'OrdersIndexCtrl',
controller: function ($scope) {
alert('This does not run');
},
views: {
"view": {
templateUrl: viewsRoot + '/restaurants/orders.htm'
}
}
});
$urlRouterProvider.otherwise("/");
You need to declare the controller along side the template:
views: {
"view": {
templateUrl: viewsRoot + '/restaurants/orders.htm',
controller: 'MyController' // (or a function, etc.)
}
The UI-Router wiki sort of alludes to this:
If you define a views object, your state's templateUrl, template and templateProvider will be ignored. So in the case that you need a parent layout of these views, you can define an abstract state that contains a template, and a child state under the layout state that contains the 'views' object.
Controllers are paired with a view. So if it ignores the "template" properties defined on the state, it seems to imply that it will ignore the controller too.
If you want all of your named views to share a controller, define an abstract parent state as the wiki suggests.
In ui-router, when you call state.go, you can define options to prevent the location from changing by passing {location:false}. However, how do I define a state using $stateProvider.state so it doesn't change the location? I've defined a rule for custom URL handling, but now it is changing the location value.
I have foo state -
$stateProvider
.state('foo', {
url: '/foo'
templateUrl: '',
controller: function($scope) {
console.log('made it!');
}
});
I have a custom rule -
$urlRouterProvider.rule(function ($injector, $location) {
// some logic
return '/foo';
});
This works exactly how I want it to except that is changes the location and appends #/foo to the location. I don't want it appended.
I created working example here. It is extended version of the solution presented here: How not to change url when show 404 error page with ui-router
There is a snippet of code, which should show how to use "foo" state without url change.
Firstly, there is our rule, which will redirect to 'foo' state - just in case if the /home is passed as url (an example of decision)
$urlRouterProvider.rule(function($injector, $location) {
if ($location.path() == !"/home") {
return false;
}
var $state = $injector.get("$state");
// move to state, but do not change url
$state.go("foo", {
location: false
})
return true;
});
Here are our example states, which do have url setting
// States
$stateProvider
.state('home', {
url: "/home",
templateUrl: 'tpl.html',
})
.state('parent', {
url: "/parent",
templateUrl: 'tpl.html',
})
.state('parent.child', {
url: "/child",
templateUrl: 'tpl.html',
controller: 'ChildCtrl',
})
Foo does not have url
.state('foo', {
//url: '/foo',
templateUrl: 'tpl.html',
});
Check it here