angularjs view is loaded before resolve checks access - javascript

I'm setting up an access control system in angular. This is how it looks so far.
It's doing the ajax to return the current user's role, then checking that role with the access array to see if the user has permission. If not it redirects.
That all works fine, but the view is still being shown for a split second before the redirect.
It may also be important to note that the ajax request is necessary because the user auth is being handled with Laravel, so I made an API for Angular to talk to get information about the user's session.
var app = angular.module('application', ['ngResource']);
app.config(function($routeProvider){
$routeProvider
.when('/admin', {
controller: 'showAdmin',
templateUrl: 'admin.html',
access: ['Admin', 'Manager'],
resolve: AppCtrl.resolve
});
});
function AppCtrl ($scope, getUser, $location, $rootScope) {
}
AppCtrl.resolve = {
getUser : function($q, $http, $location, $rootScope) {
return $http({
method: 'GET',
url: '/api/getUser'
})
.success(function(data, status) {
$rootScope.user = data;
if($rootScope.access.indexOf(data.permissions[0].role_name) < 0) $location.path('/');
});
}
};
app.run(function ($rootScope, sessionFactory, $location){
$rootScope.$on('$routeChangeStart', function (event, next) {
$rootScope.access = next.access;
});
});

Use an ng-cloak directive on the containing element to eliminate the flicker. See this page for an example along with some CSS/browser-specific gotchas.

Related

$state.go() not routing to other state, #/null in url

Using angular ui-router I'm trying to use $state.go() to change to the blogEdit state after creating a new entry with blogCreate to continue editing after saving. When I click to save and trigger addPost() method, it doesnt redirect correctly and I see /#/null as the route in the address bar instead of the expected /blog/post/:postId/edit.
blogModule.controller('PostCreateController', ['$scope', '$state', '$stateParams', 'PostResource',
function ($scope, $state, $stateParams, PostResource) {
$scope.post = new PostResource();
$scope.addPost = function () {
$scope.post.$save(function () {
$state.go('blogEdit', {postId: $stateParams.postId}); // THIS SHOULD REDIRECT TO CONTINUE EDITING POST
});
}
}
]);
blogModule.controller('PostEditController', ['$scope', '$stateParams', 'PostResource',
function ($scope, $stateParams, PostResource) {
$scope.post = PostResource.get({postId: $stateParams.postId});
$scope.updatePost = function () {
$scope.post.$update({postId: $stateParams.postId});
}
}
]);
State route configuration:
var app = angular.module('app', [
'ui.router',
'blogModule'
]);
app.config(['$stateProvider', function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('blog', {
url: '/blog',
templateUrl: 'app/blog/view/blog-list.html',
controller: 'PostListController'
})
.state('blogView', {
url: '/blog/post/{postId:[0-9]}',
templateUrl: 'app/blog/view/blog-detail.html',
controller: 'PostViewController'
})
.state('blogCreate', {
url: '/blog/post/new',
templateUrl: 'app/blog/view/blog-create.html',
controller: 'PostCreateController'
})
.state('blogEdit', {
url: '/blog/post/{postId:[0-9]}/edit',
templateUrl: 'app/blog/view/blog-edit.html',
controller: 'PostEditController'
});
}]);
It seems to do this regardless of what state I try to change to.
I suppose you are saving your post on backend. When you perform save (PUT) operation your backend should return you some response. The response should be like HTTP 201 Entity created and there should be location attribute set (f.e. http://example.com/blog/post/1). Then you can get the id from location header like this:
$scope.post.$save(function (createdPost, headers) {
var postId = headers.location.split("/").pop();
$state.go('blogEdit', {postId: postId});
});
Another way is to just ignore headers and return json response from your backend. F.e. {"postId": 1, "title": "New post", ...}. Then you can do something like:
$scope.post.$save(function (createdPost) {
$state.go('blogEdit', {postId: createdPost.postId});
});
The most important is to know API of your backend (what "it returns").

AngularJS - disable some routes

As title already suggests, I'm trying to disable some routes. I'm using angular seed project, that already has a nice structure.
I'm using JWT and I'm trying to set up a structure where if a certain route requires user to be logged in, and the user is not logged in, it redirects him to the some other page.
On my angular.module I've added the following code:
.run(['$rootScope', 'userService', '$location', function($rootScope, userService, $location) {
userService.init();
$rootScope.$on('$routeChangeStart', function(event, next, current) {
$rootScope.isPrivate = next['authenticate'];
if ($rootScope.isPrivate) {
if (!userService.get())
$location.path('/');
}
});
}]);
And this is a protected route:
angular.module('myApp.view2', ['ngRoute', 'ngCookies'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/admin/vnos-stevilke', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
authenticate: true
}).when('/admin/vnos-stevilke/:id', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
authenticate: true
});
}])
.controller('View2Ctrl', ['$scope', 'webServices', '$location', '$routeParams', function($scope, webServices, $location, $routeParams) {
if ($routeParams.id)
webServices.getBranchById($routeParams.id, function(err, data) {
$scope.branch = data;
});
webServices.getCompanies(function(err, data) {
console.log(data);
console.log('no access!');
if (!err)
$scope.companies = data;
});
}]);
now at first it appears to be working OK: if I'm not logged in, the route is not displayed and I get redirected back to the root. But at a closer look I've noticed that console.log('no access!'); is still displayed in the console. So it appears that controller gets initialized.
It seems like the whole route is loaded and then gets redirected if user is not logged in. That is not the behaviour I'm looking for. I'm trying to HOLD the loading of the route until I'm sure the user is logged in.
Any ideas?
UPDATE:
I changed the code accordingly with the suggestion below, but it doesn't seem to work. Where have I gone wrong?
userService method that checks if user is logged in:
this.isLogged = function() {
var deferred = $q.defer();
if (current === null) return deferred.reject();
else return deferred.resolve(current);
};
Run method:
.run(['$rootScope', 'userService', '$location', function($rootScope, userService, $location) {
userService.init();
$rootScope.$on('$routeChangeError', function() {
$location.path('/');
});
}]);
Restricted page:
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/admin/vnos-stevilke', {
templateUrl: 'view2/view2.html',
controller: 'View2Ctrl',
resolve: function(userService) {
console.log('test');
return userService.isLogged();
}
});
}])
Here, the "test" never displays in console.
You need to just decide if you are letting user into restricted area with resolve route parameter.
If in one of resolve function resolves with a promise object that is rejected it stops entering requested route.
I would write something like:
$routeProvider.when('/restrictedURL',{
...some params,
resolve: function(userService){
return userService.get();
}
}
...and make userService.get return a Promise object that is resolved if session is active and rejected otherwise.
Now.. if promise is rejected a route won't be launched and $routeChangeError event is raised, so you need something like:
angular.module('yourapp').run(function($rootScope){
$rootScope.$on("$routeChangeError",function(){
$location.path('/');
});
});
read more about resolve parameter # https://docs.angularjs.org/api/ngRoute/provider/$routeProvider

MEANJS: Check db for url and route to dynamic Angular state

Context
Users can register with a unique URL slug that identifies their page, e.g. 'http://example.com/slug'.
Current State
In my Express.js file, I successfully check my database to see if the slug exists on a user, then redirect the user from 'http://example.com/slug' to 'http://example.com/#!/slug' to take advantage of Angular's routing.
With Angular, however, I can't use $http or $location services in my router file (since it's taking place inside module.config...see this Stack Overflow explanation for more details).
Desire
Basically what I want to do is route the user to a 'default' view when a valid slug is found, or home if it's not. Any suggestions would be much appreciated.
For reference, my module.config code can be found here (note that the 'default' state I want to use is 'search'):
core.client.routes.js
'use strict';
// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
// Redirect to home when route not found.
$urlRouterProvider.otherwise('/');
// Home state routing
$stateProvider.
state('home', {
url: '/',
templateUrl: 'modules/core/views/home.client.view.html'
}).
state('search', {
url: '/search',
templateUrl: 'modules/core/views/search.client.view.html'
});
}
]);
What I would like to do, is something like this...
'use strict';
// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider', '$http', '$location',
function($stateProvider, $urlRouterProvider, $http, $location) {
// Get current slug, assign to json.
var slug = $location.path();
var data = {
link: slug
};
// Check db for slug
$http.post('/my/post/route', data).success( function(response) {
// Found slug in db
}).error( function(response) {
// Route to home
$location.path('/');
});
// Home state routing
$stateProvider.
state('home', {
url: '/',
templateUrl: 'modules/core/views/home.client.view.html'
}).
state('search', {
// Set URL to slug
url: '/' + slug,
templateUrl: 'modules/core/views/search.client.view.html'
});
}
]);
To directly answer your question, what you want to do is use the routes "resolve" to check for the dependency and redirect to the appropriate view:
angular.module('app', ['ui.router','ngMockE2E'])
.run(function ($httpBackend) {
$httpBackend.whenGET(/api\/slugs\/.*/).respond(function (method, url) {
return url.match(/good$/) ? [200,{name: 'john doe'}] : [404,''];
});
})
.config(function ($stateProvider) {
$stateProvider
.state(
'search',
{
url: '/search?terms=:slug',
template: '<h1>Search: {{vm.terms}}</h1>',
controllerAs: 'vm',
controller: function ($stateParams) {
this.terms = $stateParams.slug;
}
}
)
.state(
'slug',
{
url: '/:slug',
template: '<h1>Slug: {{vm.user.name}}</h1>',
controllerAs: 'vm',
controller: function (user) {
this.user = user
},
resolve: {
user: function ($q, $http, $stateParams, $state) {
var defer = $q.defer();
$http.get('http://somewhere.com/api/slugs/' + $stateParams.slug)
.success(function (user) {
defer.resolve(user);
})
.error(function () {
defer.reject();
$state.go('search', {slug: $stateParams.slug});
});
return defer.promise;
}
}
}
);
});
<div ng-app="app">
<script data-require="angular.js#*" data-semver="1.3.6" src="https://code.angularjs.org/1.3.6/angular.js"></script>
<script data-require="ui-router#*" data-semver="0.2.13" src="//rawgit.com/angular-ui/ui-router/0.2.13/release/angular-ui-router.js"></script>
<script data-require="angular-mocks#*" data-semver="1.3.5" src="https://code.angularjs.org/1.3.5/angular-mocks.js"></script>
<a ui-sref="slug({slug: 'good'})">Matched Route</a>
<a ui-sref="slug({slug: 'bad'})">Redirect Route</a>
<div ui-view></div>
</div>
But, there are a few things you may want to revisit in your example:
Is there a need to perform this check client side if you are already validating and redirecting server side via express?
You seem to be overloading the / route a bit, if home fails, it redirects to itself
You are grabbing slug from $location on app init, not when the view is routed to which could be post init, you need to grab it when ever you are routing to the view
You may want to consider using a GET request to fetch/read data for this request rather than using a POST which is intended generally for write operations (but thats a different story)

Getting URL parameters using $route

I have the following url:
http://myurl.dev/users/32
I want to pass the last parameter 32 to a $http.get request but I can't figure out how to pass it.
So far I have this:
var matchmaker = angular.module('matchmaker', ['ngRoute'], function($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
})
.controller('LocationCtrl', ['$scope', '$http', '$location', '$routeParams', '$route', function($scope, $http, $location, $routeParams, $route) {
var id = $route.current.params.id;
console.log(id);
$http.get('http://myurl.dev/services/' + id ).success(function(data)
{
$scope.applicants = data;
});
}]);
In the console it's saying:
Cannot read property 'params' of undefined
Can anyone tell me what I'm doing wrong please?
Edit:
Angular isn't generating the url, it's a server side generated url
Edit 2.0
Here's the config for the routeProvider with actual route parameters:
var matchmaker = angular.module('matchmaker', ['ngRoute'], function($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
})
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/matchmaker/locations/:id', {
controller: 'LocationCtrl'
});
$locationProvider.html5Mode(true);
});
Put a console.log($routeParams); in your controller and check its value.
If it is Object {} check if you have a route definition using parameters:
var module = angular.module('ngRouteExample', ['ngRoute']);
module.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/test/:id', {
templateUrl: 'test.html',
controller: 'TestController'
});
// configure html5 to get links working on jsfiddle
$locationProvider.html5Mode(true);
});
If so, you will get this output in the console:
Object {id: "42"}
It is because you trying to get value which doesn't exist at that moment, that's how javascript works. You need to specify that you want these values when they are ready using '$routeChangeSuccess' event.
.controller('PagesCtrl', function ($rootScope, $scope, $routeParams, $route) {
//If you want to use URL attributes before the website is loaded
$rootScope.$on('$routeChangeSuccess', function () {
//You can use your url params here
$http.get('http://myurl.dev/services/' + $routeParams.id )
.success(function(data) {
$scope.applicants = data;
});
});
});

AngularJS Routing with Authorization Process

I'm trying to implement basic authentication routing in AngularJS. I have a model that has a authorize method that returns a promise. I want the routing to wait until that authorize function has returned true or false to continue, once that has completed it should resume the path or redirect the user to the login page.
I think essentially i need to stop routing, call that method and then resume or redirect to login. Below is the code I have so far but i'm not sure how to accomplish the pause/resume. Any ideas?
return angular.module('d', ['ngCookies', 'ngRoute'])
.config(['$routeProvider', '$locationProvider', '$httpProvider',
function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider.when('/',
{
templateUrl: 'views/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when('/login',
{
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
});
$routeProvider.otherwise({ redirectTo: '/404' });
$locationProvider.html5Mode(true);
// handle unauthorized requests by redirecting to login page
$httpProvider.responseInterceptors.push(
['$location', '$q', function ($location, $q) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
$location.path('/login');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}]);
}])
.run(['$rootScope', '$location', 'Auth', function ($rootScope, $location, Auth) {
$rootScope.$on("$routeChangeStart", function (event, next, current) {
$rootScope.error = null;
Auth.authorize().then(function(){
$location.path('/');
},function(){
$location.path('/login');
});
});
}]);
Your solution is very similar to a prototype I wrote a while back.
The idea is that whenever you "touch" the server and get an authentication error, a modal window pops-up asking for a login without changing the URL (you let it change to the new URL and stay there).
My implementation was also based on an interceptor checking for 401. It has a dependency on $rootScope and sets a property "needsLogin" to true. The page template has the login modal window visible when needsLogin === true and hides the ng-view (this is important since the new route has been loaded but it misses its data). Finally, upon successfull login, the login controller does the $route.reload() and then sets $rootScope.needsLogin = false.
Small snippets:
<div id="main" role="main" ng-show="needsLogin === false">
<div ng-view></div>
</div>
<div ng-show="needsLogin === true" ng-include="'views/login.html'" class="overlay"></div>
The login controller can be something like:
function LoginCtrl($scope, $rootScope, $route, $http) {
$scope.login = function () {
$http.post( /* Somehow do your login */ )
.success(function () {
var deregister = $rootScope.$on('$routeChangeSuccess', function () {
// hide login / show ng-view after route has been reloaded
$rootScope.needsLogin = false;
deregister();
});
$route.reload();
})
.error(function () {
/* handle errors */
});
};
}
$route.reload() is not a full page refresh, it merely re-initializes the route (controller/view etc). Hopefully, the call that was rejected before will run again and the page will be fine.

Categories