I have two templates as a.html and b.html. Now a.html is for logged in users and b.html for people who are not. Authentication is through an api running on a separate sub-domain. Now i have a call that tells whether a person is authenticated in scope.
How can i optionally load templates based on that. One method is i load b.html and conditionally redirect authenticated users but that is not something i am looking for.
Here is my ngRoute codes
var app = angular.module('app',['ngRoute']);
app.config(function ($routeProvider,$locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/',{
templateUrl : '/templates/b.html',
controller : 'mainController'
})
.otherwise({
templateUrl : 'templates/404.html',
controller : 'mainController'
});
});
app.controller('mainController', function($scope) {
});
The following code could somehow satisfy your requirements but it is in fact a bit tricky since i used ng-include instead of directly setting the templateUrl.
The key is to use the resolve param in $routeProvider. when u pass a $promise as a member in it, the router will wait for them all to be resolved.
app.controller('MainController', ['$scope','loadedTemplate',function ($scope,loadedTemplate) {
//inject the template from the resolve function
$scope.loadedTemplate = loadedTemplate;
}]);
//Service to keep loginStatus
app.factory("loginService",function(){
return {
isLogin:false,
loginStatusFetched:false,
}
})
app.config(['$routeProvider',function ($routeProvider) {
$routeProvider.
when('/', {
template: "<div ng-include='loadedTemplate'></div>",
controller: 'MainController',
resolve:{
loadedTemplate:function($q, $http,$route,loginService) {
console.log("fetching login status");
if(loginService.loginStatusFeteched){
if(loginService.isLogin){
return "a.html"
}
else{
return "b.html"
}
}
console.log("fetching remote authentication");
var deferred = $q.defer();
$http({method: 'GET', url: 'http://jsonplaceholder.typicode.com/posts/1'})
.success(function(data) {
//update the login status here
loginService.loginStatusFeteched = true;
loginService.isLogin = true;
if(loginService.isLogin){
deferred.resolve("a.html");
}
else{
deferred.resolve("b.html");
}
})
.error(function(data){
//error
deferred.resolve("b.html");
});
return deferred.promise;
}
}
})
}]);
I attempted to achieve what u required by setting the templateUrl to a function, but it only takes routeParams, and when the $promise in resolve get resolved, this function has already been executed and there is no way to change the current loaded template.
There is still other choices that you can try. You may use angular-ui-router instead of ng-route to handle state changes, in which u may set child states and present them with the same url conditionally.
But I believe there might be better ways to achieve what you want. If anyone has any better solution, please let me know.
Related
Im building an app in angularJS where a user can Login to the the admin Panel.
Router config
app.config(["$routeProvider","$locationProvider",($routeProvider,$locationProvider)=>{
$locationProvider.hashPrefix("");
$routeProvider
.when("/",{
templateUrl: "app/views/enterticket.html"
})
.when("/adminlogin",{
templateUrl: "app/views/adminlogin.html",
controller: "adminlogin"
})
//panel is the admin section
.when("/panel",{
controller: "panel",
templateUrl: "app/views/panel.html",
})
}])
There's a service that authenticates the user to grant access to the admin panel,
auth service
app.service("auth",["$http","$location",function($http){
let api = (window.location.origin)+"/auth";
this.auth = function () {$http.get(api).then((rez)=>{
//is session is not set on server
if(!rez.data.state){
window.location.href = "#/adminlogin"
})}
}])
And finally theres the admin component
app.controller("panel",["$scope","$http","auth",function($scope,$http,auth){
auth.auth();
$scope.addTicket = ()=>{
let bearer = $scope.bearer;
let api = (window.location.origin)+"/addticket";
let postfields = JSON.stringify({"bearer":bearer});
$http.post(api,postfields).then((rez)=>{
console.log(rez);
})
}
}])
This setup is working well and there is restriction for the admin panel, however, when someone attempts to visit the admin panel without logging in, the browser flashes the admin panel template and then quickly reverts back to the admin login. Can someone suggest a way of preventing that awkward flash or even tell me a better way of implementing angularJS routeGuards, thank so much
You need to use resolve in when.
I forked another project and you need to tweak the code as per your requirement. (http://plnkr.co/edit/PAYTAr1sOoCmkP8q)
angular.module('app', ['ngRoute']);
angular.module('app')
.factory("authService", function($q, $timeout){
return {
verify: function(){
var deferred = $q.defer();
$timeout(function(){
// deferred.resolve(true); // if the user is allowed to access admin panel
deferred.reject(false); // if the user is not logged in
},1000);
return deferred.promise;
}
};
});
angular.module('app')
.controller('mainCtrl', function($scope) {
$scope.message = "Message in the main controller";
});
angular.module('app')
.controller('templateCtrl', function($scope) {
$scope.hello = " World";
});
angular.module('app')
.config(function($routeProvider, $locationProvider) {
$routeProvider.
when('/option1', {
templateUrl: 'template.html',
controller: 'templateCtrl',
resolve: {
greeting: function(authService, $location){
return authService.verify().then(function(result){
return true; // if the user is logged in, it will allow the user to access login panel
}, function(result){
$location.path('/login');// if not, it will redirect to login page
return false;
})
}
}
})
.when('/login', {
template: '<div>Login Message</div>'
})
});
Hey Alaksandar Gene i got your answer to work however it was still flashing the template. I guess the problem was from the resolve not awaiting the http call. So i decided to use cookies instead.
app.service("mainauth",function($http,$q,$timeout,$cookies){
this.mainauth = ()=>{
var defer = $q.defer();
let api = (window.location.origin)+"/auth";
if($cookies.get("ticket_admin") == null){
defer.resolve(false);
}
return defer.promise;
}
})
Then for the routing:
app.config(["$routeProvider","$locationProvider",($routeProvider,$locationProvider)=>{
$locationProvider.hashPrefix("");
$routeProvider
.when("/",{
templateUrl: "app/views/enterticket.html"
})
.when("/adminlogin",{
templateUrl: "app/views/adminlogin.html",
controller: "adminlogin"
})
.when("/panel",{
controller: "panel",
templateUrl: "app/views/panel.html",
resolve: {
mainauth: function(mainauth,$location){
mainauth.mainauth().then((rez)=>{
if(!rez){
window.location.href = "#/adminlogin"
}
})
}
}
})
}])
I suppose theres a risk of somebody imitating the cookies, and gaining access so ill put up another service to verify the session incase that happens
app.service("auth",["$http","$cookies",function($http,$cookies){
this.forceAuth = function(){
let url = (window.location.origin)+"/auth";
$http.get(url).then((rez)=>{
if(!rez.data.state){
window.location.href = "#/adminlogin"
}
})
}
}])
I can then inject the above service in the controller for added security.
PS some might wonder why i didnt inject a service that verifies the cookies directly into the controller, thats because it was still causing a flash
i have the following code in my route provider:
project.config(['$routeProvider','$locationProvider',
function ($routeProvider,$locationProvider){
$routeProvider.
when('/start',{
templateUrl: 'start.html',
controller: 'startController'
}).
when('/event',{
templateUrl: 'event.html',
controller: 'eventController'
}).
when('/report',{
templateUrl: 'report.html',
controller: 'reportController'
}).
otherwise({
redirectTo: '/'
});
I have a rootscope variable admin with value of either 1 or 0
My current code sends the user to the start page if an unknown value is given to the route provider. I want the route provider to redirect the user to the event page if the user is an admin or to the start page if the user isnt an admin when an unknown value is given to the route provider.
I was thinking something like this might work, but it didnt.
otherwise({
redirectTo: defaultRedirect
});
$rootScope.admin == 1 ? $rootScope.defaultRedirect = "/event" : $rootScope.defaultRedirect = "/start"
It throws me an error.
Help is appreciated. Thanks.
No it will not work like that. Instead what you can do is have resolve in otherwise block, where you can check if loggedin user is admin or not (by checking $rootScope variable or calling authentication service). If service resolves user being admin then you can use $location service to change route using $location.path("/event") or $location.path("/start"). The code look like:
$routeProvider.otherwise({
resolve: {
load: [
'$rootScope', '$q', 'authentication', '$location', function ($rootScope, $q, authentication, $location) {
var defer = $q.defer();
function chooseRoute() {
if (authentication.identity === undefined) {
$location.path('/logon');
} else {
$location.path('/somewhere');
}
defer.reject();
}
if (authentication.identityResolved)
chooseRoute();
else {
var unbind = $rootScope.$watch(function () {
return authentication.identityResolved;
}, function (resolved) {
if (!resolved)
return;
chooseRoute();
unbind();
});
}
return defer.promise;
}
]
}
});
Another way is, you can check event $routeChangeStart in config/run block & then accordingly checking if user is admin you can redirect user to specific routes using $location service.
I try to load route only after promises are resolved
var app = angular.module("thethaoso", ["ngRoute"]);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider) {
$routeProvider
.when('/', {
resolve: {
message: function (repoService) {
return repoService.getMsg();
}
}
});
}]);
app.factory('repoService', function ($http) {
return {
getMsg: function () {
return "hihihi";
}
};
});
app.controller('teamLoadCtrl', function ($scope,message) {
$scope.message= message;
});
View:
<div ng-app='thethaoso' ng-controller='teamLoadCtrl'>
{{message}}
</div>
Always get the error Error: [$injector:unpr]http://errors.angularjs.org/1.3.7/$injector/unpr?p0=messageProvider%20%3C-%20message%20%3C-%20teamLoadCtrl
full code at http://jsfiddle.net/c0y38yp0/5/
Am I missing something ?
Thanks all.
The problem is that you have not specified a template and a controller to resolve the message object to. If you used the following syntax, it will work.
.when("/", {
templateUrl: "yourView.html",
controller: "yourController",
resolve: {
message: function(yourService){
return yourService.get();
}
}
Here is a working jsfiddle: http://jsfiddle.net/c0y38yp0/10/
You can also resolve the promise manually in your controller like so:
repoService.getMsg()
.then(function (msg) {
$scope.message = msg;
}
When the promise is resolved onto the scope as I did above, the ui will update. You can show a loading bar and use ng-hide to make the pages feel fluent while the loading occurs.
When you resolve, service have to return promise not value.
Here is example service
app.factory('repoService', function ($http, $q) {
var user = {};
var q = $q.defer();
$http.get('https://api.github.com/users/Serhioromano')
.success(function(json){
user = json;
q.resolve();
}).error(function(){
q.reject();
});
return {
promise: function() {
return q.promise;
},
get: function() {
return user;
}
};
});
The point here is that you return promise only. You handle how you save result. And then you can use this result like in get(). You know that by the time you call get() the user variable already set because promise was resolve.
Now in router.
app.controller('MainCtrl', function($scope, repoService) {
$scope.user = repoService.get();
});
app.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: '/view.html',
controller: 'MainCtrl',
resolve: {
message: function (repoService) {
return repoService.promise();
}
}
})
.otherwise({ redirectTo: '/' });
});
You return promise by repoService.promise()
In controller repoService.get() is triggered only after that promise resolved.
So you get your data.
Another thing in your code, you used ng-controller. But that thing is not binded to router and thus it avoid if it is resolved or not. You have to delete ng-controller and use controller router controller: 'MainCtrl',.
This affect your HTML
<body ng-app="myapp">
<ng-view></ng-view>
<script type="text/ng-template" id="/view.html">
<p>Hello {{user.name}}!</p>
</script>
<body>
You have to use <ng-view> to include subtemplate there and then in sub template you can use scope of the controller.
See plunker.
There are a few things wrong with the code you posted, in contrast to the code you are attempting to draw inspiration from.
When you resolve a route with the $routeProvider, the results are applied against an element <ng-view></ngview>, not a base element <div> as you have specified. Without the <ng-view> element, there is no way for the $routeProvider to bind the correct controller to the correct html fragment. Using ng-controller instantiates a controller instance when the dom element is rendered, and does not allow passing parameters to the controller as you have tried. Thus your resolution error due to an unknown message object. Effectively, message is not available outside the $routeProvider instance.
I am new to AngularJS. I made a simple app that have a login function using AngularJS. I used routing and on resolve i put some logic to check if user is logged in and then only proceed accordingly. I have everything working fine, the problem is, when i am not logged in, if i browse to /home it doesn't load the main.html page(that's how it's supposed to be) but a GET request gets called and that returns content of main.html in console.My code looks like this:
app.config(function($routeProvider){
$routeProvider.when('/', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
//checks if user is logged and returns boolean
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(!data.logged){
defer.resolve(data);
$location.url('/');
}
else{
defer.resolve(data);
$location.url('/home')
}
});
return defer.promise;
}
}
})
.when('/home',{
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
resolve:{
test: function($http, $q,$location){
var defer = $q.defer();
$http.post('login/getLoggedUser', {}, {}).success(function(data){
if(data.logged){
defer.resolve(data);
$location.url('/home');
}
else{
defer.resolve(data);
$location.url('/')
}
});
return defer.promise;
}
},
})
.otherwise({ redirectTo: '/' });
});
When i direct to /home, GET http:/localhost:8080/an-grails/partials/main.html is called in console which contains the content of main page. How do i disable this call? Is there any other method to do this? I read documentation on AngularJS official page and also watched few videos of Egghead.io about resolve and got idea that controller and template gets loaded only after resolve is processed, So what am i doing wrong?
The simplest way to manage rights in your different routes is to catch the $routeChangeStart which is fired by the $route service everytime the route is changed.
With this, you can access the actual route and the next one. This object is the same that you register with $routeProvider.when(). You just have to add a boolean and compare this boolean with the actual user status.
$rootScope.$on("$routeChangeStart", function(event, next, current) {
if (next.loggedOnly && !user.isLogged()) {
// You should implement a isLogged method to test if the user is logged
$location.replace();
// This prevent a redirect loop when going back in the browser
return $location.path("/");
}
}
And inside your route declaration use :
$routeProvider.when('/home', {
templateUrl: 'partials/main.html',
controller: 'MainCtrl',
loggedOnly: true
});
I need to build a User that can be the resut of different REST API call (each way comes from a specific route).
Let's say for this example that we can visit:
http://myapp/#user/:pseudo
http://myapp/#user/:user_id
angular.module('Test').config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/user/:pseudo', {
templateUrl: 'views/user.html',
controller: 'userFromPseudoCtrl'
}).
when('/user/:user_id', {
templateUrl: 'views/user.html',
controller: 'userFromIdCtrl'
}).
otherwise({
redirectTo: '/'
});
}
]);
then, i have 3 different controllers:
userFromPseudoCtrl
userFromIdCtrl
userCtrl (To control the view)
angular.module('Test').controller('userFromPseudoCtrl', function($User, $http) {
$http.get('/getUserFromPseudo/test')
.success(function(User) {
$User.set(User);
});
});
angular.module('Test').controller('userFromIdCtrl', function($User, $http) {
$http.get('/getUserFromId/test')
.success(function(User) {
$User.set(User);
});
});
angular.module('Test').controller('userCtrl', function($scope, $User) {
$scope.User = $User;
});
This way is not good because the userCtrl is called before the $http callback (from the router's controllers), so the User is actually empty into the page (i was hopping it will be automatically updated).
Before i try to manage with it (using $rootScope.$apply()), i am wondering what is the more optimize way to do this kind of process (loading datas from router's controller then display it).
Do you use as many controllers as i do ? Do you process these REST APIs calls in the same controller that "bind" your view ? I am interesting to know !
When you define your routes you can define an additional value named resolve which is an object where each field is a promise that when resolved will be injected into your controller:
Route Definition:
when('/user/:pseudo', {
templateUrl: 'views/user.html',
controller: 'userFromPseudoCtrl'
resolve: {dataNeeded: userPseudoService.getUserData()});
Service (new):
angular.module('Test').service('userPseudoService', function($http){
return $http.get('/getUserFromPseudo/test');
});
Controller:
angular.module('Test').controller('userFromPseudoCtrl', function(dataNeeded){});
The route will not change until the promise is resolved.