I can't figure out a reasonable way, which doesn't feel like a hack, to solve this rather trivial problem.
I want a guest to see a splash page when they access the index of the website and a logged in user to see their profile, with each page having it's own template and controller. Ideally, there would be two states for one url, and somehow I would be able to automatically alter the active one depending on the loggin status. Both of these views will have their own nested views so ng-include cannot be used (I assume).
I'm quite new to angular and ui router and think I might be overlooking an easy solution to the problem.
Could it be done with named views and ng-show?
If you're using UI Router, just create three states: the root state, with the '/' URL, and two direct descendant states with no URLs. In the onEnter of the root state, you detect the state of the user and transition to the correct child state accordingly. This gives the appearance of keeping the same URL for both child states, but allows you to have to separate states with separate configurations.
The templateUrl can be a function as well so you can check the logged in status and return a different view and define the controller in the view rather than as part of the state configuration
My Solution:
angular.module('myApp')
.config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
controller: function (Auth, $state) {
if (someCondition) {
$state.go('state1');
} else {
$state.go('state2');
}
}
});
});
where state 1 and state 2 are defined elsewhere.
For docs purposes, I used:
$rootScope.$on('$stateChangeStart', function(event, toState) {
if ((toState.name !== 'login') && (!$localStorage.nickname)) {
event.preventDefault();
$state.go('login');
}
});
Using $routeChangeStart didn't work for me.
It is used for me conditional view in ui-route
$stateProvider.state('dashboard.home', {
url: '/dashboard',
controller: 'MainCtrl',
// templateUrl: $rootScope.active_admin_template,
templateProvider: ['$stateParams', '$templateRequest','$rootScope', function ($stateParams, templateRequest,$rootScope) {
var templateUrl ='';
if ($rootScope.current_user.role == 'MANAGER'){
templateUrl ='views/manager_portal/dashboard.html';
}else{
templateUrl ='views/dashboard/home.html';
}
return templateRequest(templateUrl);
}]
});
If I understand the question; you want to make sure that the user who hasn't logged in cannot see a page that requires log in. Is that correct?
I've done so with code like this inside a controller:
if(!'some condition that determines if user has access to a page'){
$location.path( "/login" );
}
Anywhere (probably in some high-level controller) you should be able to just bind a '$routeChangeStart' event to the $rootScope and do your check then:
$rootScope.$on('$routeChangeStart', function(next, current){
if(next != '/login' && !userLoggedIn){
$location.path( "/login" );
}
});
This will get fired every time a new route is set, even on the first visit to the page.
The way I've done this is pretty simple. I made one for our A/B testing strategy. This is the gist:
resolve: {
swapTemplate: function(service) {
// all of this logic is in a service
if (inAbTest) {
this.self.templateUrl = '/new/template.html';
}
}
... other resolves
}
This gets called before the template is downloaded and therefor you're allowed to swap out the template url.
In my case, if two states can share logic of same controller, conditional template is a good choice. Otherwise, creating separate states is a good option.
Related
I have a route defines as follows:
$routeProvider.
when('/projects/', {
controller: 'ProjectCtrl',
controllerAs: 'project_ctrl',
templateUrl: '/static/app/partials/project.html'
}).
After the login finishes I need the user to land on this link, hence in my controller I am using this:
vm.login = function(form) {
if (form.$valid) {
loginService.login(vm.loginFormData.username, vm.loginFormData.password);
loginService.setUpUser()
$location.url("/projects");
}
}
But unfortunately the controller associated with this view is not triggered, that is ProjectCtrl is not triggered. However when I click on the navigation link which uses in the dom, it works fine. Can someone please guide me here, may I am missing something conceptual.
Hence the larger question is how do I redirect a user in the controller using some APIs which also complies with ngRoute based controllers.
Try removing the last / in url so it matches $location.url("/projects");
$routeProvider.
when('/projects', {
I have a link on my page (inside the scope of angular app 1) which changes the hash location
/app/#/location
I have another angular app (2) which is not reacting to the hash location change in the way I expect it to (ie by firing locationChangeStart, and changing state). I don't fully understand why. Anybody can explain this to me?
Edit 1: yes, there are two angular apps on the page, of different angular versions, both bootstrapped (sigh, don't ask). The ui-router configuration looks like this:
$urlRouterProvider.otherwise(($injector) => {
let $state = $injector.get("$state");
[... snip ...]
$state.go('list');
});
// Now set up the states
$stateProvider
.state('messaging', {
url: "/messaging/{param}",
templateUrl: "messaging.html",
controller: 'MessagingController'
})
.state('list', {
url: "/list",
templateUrl: "list.html",
controller: 'ListController'
});
Nothing really too fancy here, and before anybody asks, yes, I do need to check on a state in the otherwise.
The bootstrapping looks like this:
angular.element(document).ready(function() {
angular.element(document.getElementById('app-bootstrap')).prepend('<div ui-view></div>');
angular.bootstrap(document.getElementById('app-bootstrap'), ['app']);
});
Edit 2: this seems like it may be not an angular-related issue at all, as using the window 'hashchange' event directly doesn't seem to fire either; I've confirmed that window.onhashchange is the correct function.
(angular 1.4.7)
I am using UI Router with html5Mode enabled, states are loaded from JSON.
Expected behavior after F5 or when pasting URL is, respectively, having current state reloaded or navigating to the said state, instead the initial application state is loaded.
For e.g. root/parent/child gets redirected to root/.
By the way, navigating with ui-sref works fine.
So, how can the state be retained after page reload?
In order to retain the state of page after reload app, a url represent the state should be gave. when you include ui-route module, url will be parsed and sent to corresponding state. You don't need to parse the url handly in most cases, ui-route born to do this.
Please can you post your code here? Specifically the $stateProvider.
This is an example of a correct $stateProvider and it works fine:
$stateProvider.state('main.admin', {
url: '/admin',
resolve: {},
views: {
'main-content#main': {
controller: 'AdminController as admin',
templateUrl: 'main/admin/admin.tpl.html'
}
}
});
Seems a bit hacky, but works for now.
app.run(['$location', '$state', function ($location, $state) {
function stateFromUrl () {
var path = $location.path(),
hash = $location.hash();
// do JSON states map parsing and find a corresponding to the URL state
return state;
}
if (stateFromUrl) {
$state.go(stateFromUrl);
} else {
$state.go('home'); // initial state
}
}]);
I have an angular app with a homepage that shows a list of things. Each thing has a type. In the nav, there are selectors corresponding to each thing type. Clicking one of these selectors causes the home controller to filter the things shown to those of the selected type. As such, I see the selectors as corresponding to states of the home page.
Now, I'd like to map each of these states to a url route: myapp.com/home loads the home page in default (unfilitered) state, myapp.com/home/foo opens the home page with the foo-type selector activated, and switching from there to myapp.com/home/bar switches to the bar-filtered state without reloading the page.
It's that last bit - triggering "state" changes without reloading the page, that's been particularly tricky to figure out. There are numerous SO/forum questions on this topic but none have quite hit the spot, so I'm wondering if I'm thinking about this in the wrong way: Should I be thinking of these "states" as states at all? Is there a simpler approach?
Also, I'm open to using either ngRoute or ui.router - is there anything about one or the other that might make it simpler to implement this?
Using ui-router, you can approach it like this:
$stateProvider
.state('home', {
url: "/home",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
.state('home.filtered', {
url: "/{filter}",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
This creates a filtered state as a child of the home state and means that you can think of the URL to the filtered state as /home/{filter}. Where filter is a state parameter that can then be accessed using $stateParams.
Since you don't want to switch views, you inject $stateParams into your controller, watch $stateParams.filter, and react to it how you wish.
$scope.$watch(function () { return $stateParams.filter }, function (newVal, oldVal) {
// handle it
});
I have a couple of states the use the same controller. Some of these do not require a URL parameter while some do. How do I avoid a state from being accessible if the URL parameter is not provided?
Example:
I have 2 views or states, list and single. They both share the same controller. I have the routes mapped as follows:
state: app.list
url: /list
controller: appCtrl
state: app.single
url: /single/:id
controller: appCtrl
Now, I want single to be accessed only if the id is specified, other wise redirect to some other page. How is that possible using the same controller?
Approach 1
You can use $urlRouterProvider with when() for redirection.
app.config(function($urlRouterProvider){
// when there is an single route without param, redirect to /list
$urlRouterProvider.when('/single/:id', ['$match', '$state', function($match, $state) {
if($match.id === ""){
$state.transitionTo("app.list");
}
}
]);
});
Working demo: http://plnkr.co/edit/sEoUGGCEge0XbKp3nQnc?p=preview
Approach 2
You can check the param in controller side and redirect it to specific page
myApp.controller('MainCtrl', function($state, $stateParams) {
if($state.current.name == 'app.single' && $stateParams.id === ""){
$state.transitionTo("app.list");
}
});
Working demo: http://plnkr.co/edit/QNF1RHy4Prde4CRhNLFa?p=preview
Note: In the above demos, redirection works when your current state should not be app.single. Means, State will not change if you are in app.single state and trying without param. So go to main page, then click the link without param of single state. it will redirect to list state.