Im using Grails server side and Angular client side.
If the user is not allowed to access a page I redirect with Grails using response.sendError(404)
It is working but PrivateProfileController is calling before the redirection and it shows the template private.html for 1 seconds then redirecting to the template 404.html.
Is there a way to avoid displaying private.html before redirecting ?
Thank you
Grails controller's method
def privateProfile(){
if(NOT_AUTHORIZED){
return response.sendError(404)
}
}
Angular
app.config( ['$routeProvider', '$locationProvider',
'$tooltipProvider', '$compileProvider', '$httpProvider',
function($routeProvider, $locationProvider, $tooltipProvider,
$compileProvider, $httpProvider) {
$locationProvider.html5Mode(true) ;
$routeProvider
.when('/:username/private', {
templateUrl: '/js/angular/app/userprofile/templates/private.html',
controller: 'PrivateProfileController'
})
.when('/404', {
templateUrl: '/js/angular/app/error/404.html' ,
controller: 'HTTPErrorController'
})
.otherwise({
});
$httpProvider.responseInterceptors.push(
['$location', '$q', function ($location, $q) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 404) {
$location.path('/404');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
}
}]);
}
]).run(function ($rootScope) {
$rootScope.$on('$locationChangeStart',
function(evt, absNewUrl, absOldUrl) {
//console.log('Start : old', absOldUrl, 'new ',absNewUrl );
});
$rootScope.$on('$locationChangeSuccess',
function(evt, absNewUrl, absOldUrl) {
//console.log('Success : old', absOldUrl, 'new ', absNewUrl);
});
});;
app.controller("HTTPErrorController", function ($location) {
console.log("HTTPErrorController")
});
There are two components acting separately asynchronously:
rendering of the template based on the route
XHR request responding with the authorization status.
By the time the http response is intercepted (btw response interception is deprecated), the route completes its part by rendering content and bootstrapping the corresponding angular controller (PrivateProfileController). On receiving the response, the response gets intercepted and the routing is done to /404. There comes the latency of 1 sec. (The time it took to complete the call to Grails at server side plus interception)
What can be done is making the authorization call part of routeProvider's resolve for that particular route:
$routeProvider
.when('/:username/private', {
templateUrl: '/js/angular/app/userprofile/templates/private.html',
controller: 'PrivateProfileController',
resolve: {
authz: function($q){
//Create a promise
//Call the service (REST call to Grails)
//Get back 404
//Reject the promise if 404
//route to 404
//return promise
}
}
})
.when('/404', {
templateUrl: '/js/angular/app/error/404.html' ,
controller: 'HTTPErrorController'
})
.otherwise({
});
If routing to /404 is a problem inside resolve then, use $routeChangeError event which gets fired on rejected promise.
Refer this question answered by Misko himself and this post as well to see how resolve works.
Related
Very simply, after an API call, depending on the return value, how is the appropriate view loaded? Consider having
search.html
views/found.html
views/notfound.html
Search's controller makes an AJAX call to a service and gets a good or bad result. Now I want the appropriate view to load, without user having to click. I just can't figure out how to do this and have looked at scores of routing/view examples. I'm using HTML5 mode.
app.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'search.html',
controller: 'searchCtrl'
})
.when('found', {
templateUrl: 'views/found.html',
controller: 'foundCtrl'
})
.when('notFound', {
templateUrl: 'views/notFound.html',
controller: 'notFoundCtrl'
})
.otherwise({
templateUrl: 'search.html',
controller: 'searchCtrl'
});
$locationProvider.html5Mode({
enabled: true,
requiredBase: true
});
And in the controller ..
$scope.post = function (requestType, requestData) {
var uri = 'Search/' + requestType;
$http.post(uri, requestData)
.success(function (response) {
$scope.searchResult.ID = response.ID;
$scope.searchResult.Value = response.Value;
// how is the view/route loaded without a user click?
'found';
return true;
}).error(function (error) {
// how is the view/route loaded without a user click?
'notFound';
return false;
});
I'm just lost after getting back the response on how to invoke a view within the template.
Since you are using ngRoute use $location.path() instead of $state.go(). The $location.path() method accepts a url specified in route configuration. E.g.:
$location.path('/found');
Say your controller is AppController, then the complete code will look something like:
angular.module('app', ['ngRoute'])
.controller('AppController', function ($location, $http) {
$scope.post = function (requestType, requestData) {
var uri = 'Search/' + requestType;
$http.post(uri, requestData)
.success(function (response) {
$scope.searchResult.ID = response.ID;
$scope.searchResult.Value = response.Value;
// how is the view/route loaded without a user click?
$location.path('/found');
}).error(function (error) {
// how is the view/route loaded without a user click?
$location.path('/notFound');
});
});
Refer https://docs.angularjs.org/api/ng/service/$location for api documentation of $location.path
Using angular-ui-router, I have something like:
.state('page', {
url: '/page',
templateUrl: '/page.html'
})
This template URL may return a "401 Unauthorized". Is it possible to handle the http response when the router tries to load the url and handle it, so I can show some message or redirect the user?
You can register an interceptor for your application. The implementation of this interceptor
$httpProvider.responseInterceptors.push([
'$q',
'$location',
'$rootScope',
(function($q, $location, $rootScope) {
return {
responseError: function(response) {
if (response.status === 401) {
console.log(response.status + ' intercepted');
$location.path('/unauthorized/');
$q.reject(response);
}
return response;
}
};
});
]
After this you need to register /unauthorized in your states with a custom page template.
I need to pass a route parameter to the server responding with a template, so I'm trying to use a templateProvider per several articles/other stack overflow docs.
I'm not getting any javascript errors, but the following templateProvider method is never even executed. When the templateUrl property is not commented out, this route works fine.
$stateProvider
.state('org.contacts.add', {
url: '/add',
views: {
'org#': {
// templateUrl: '/templates/issues/add',
controller: 'ContactsAddController',
templateProvider: function($route, $templateCache, $http) {
var url = '/templates/' + $route.current.params.org + '/contacts/add';
$http.get(url, {cache: $templateCache}).then(function(html){
return html;
});
}]
}
}
})
After some experimentation, it seems the $route was causing trouble. Taking that out and using $stateParams at least fires this code/controller.
However, while I see the ajax call firing and the proper html response, it's never loaded to the view.
templateProvider: function ($stateParams, $http, $templateCache) {
var url = '/templates/contacts/add';
$http.get(url, {cache: $templateCache}).then(function(html){
return html;
});
}
I'm guessing you need to return a $promise for this to work. Check out the example used on the UI-Router wiki:
$stateProvider.state('contacts', {
templateProvider: function ($timeout, $stateParams) {
return $timeout(function () {
return '<h1>' + $stateParams.contactId + '</h1>'
}, 100);
}
})
Notice the line that begins with return $timeout(.... Have you tried returning the $promise that is created by doing $http.get()?
I am new to AngularJs. I have a single page app with routes configured having a controller and a view. The view get loaded inside the <ng-view></ng-view> element of the index.html page. Inside the controller I am making a http call to get the data and binding the data to the $scope. For success scenarios this works fine but if there is an error how do I plug in another view instead of the default view configured inside the angular route. PLease let me know.
To implement common scenario for processing ajax errors you can implement custom request interceptor and redirect user to error page (or login page) according to error status:
myApp.factory('httpErrorResponseInterceptor', ['$q', '$location',
function($q, $location) {
return {
response: function(responseData) {
return responseData;
},
responseError: function error(response) {
switch (response.status) {
case 401:
$location.path('/login');
break;
case 404:
$location.path('/404');
break;
default:
$location.path('/error');
}
return $q.reject(response);
}
};
}
]);
//Http Intercpetor to check auth failures for xhr requests
myApp.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('httpErrorResponseInterceptor');
}
]);
Plunker here
Use $location.url() to redirect to a 404.html when error is occured
$http.get(url,[params])
.success(function(data, status, headers, config){
// bind your data to scope
})
.error(function(data, status, headers, config) {
$location.url('/404');
});
Then configure your $routeProvider
$routeProvider
.when('/404', {
templateUrl: '404.html',
controller: 'Four04Controller'
})
you could use: https://github.com/angular-ui/ui-router
In case of error, you can trigger a "error" state.
I had the same problem some weeks ago and I have resolved in this way
If you use $stateProvider instead of $routeProvider you can do like this:
function routerConfig($stateProvider, $urlRouterProvider) {
$stateProvider
.state('404', {
url: '/404',
templateUrl: '404.html'
})
.state('home', {
url: '/',
templateUrl: 'home.html'
});
$urlRouterProvider.otherwise('/404');
}
Pay attention to $urlRouterProvider.otherwise(url), which is the function that gets called when the provider doesn't find the requested url, so it automatically redirect to the url provided in this function.
Is it possible to [execute a function] e.g. open a modal dialog window from the routeProvider when a certain route is requested?
myApp.config(function($routeProvider) {
$routeProvider
.when('/home',
{
controller: 'HomeCtrl',
templateUrl: 'Home/HomeView.html'
}
).when('/profile/:userId/changepwd',
function(){
$dialog.messageBox(title, msg, btns)
.open()
.then(function(result){
alert('dialog closed with result: ' + result);
});
}
).otherwise({ redirectTo: '/home' });
});
PS: I want to cancel a route and instead open a dialog box. Opening the dialog box is not the only issue. Cancelling the route is the major issue.
You can pass your function as dependency in resolve and it will wait until dependency is resolved and when your dialog ends then change the route and modify history as you wish using $location
var app = angular.module('myApp', [])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {
template: ' ',
controller: //empty function,
resolve: {
data1 : function($dialog, $location) {
var promise = $dialog.messageBox(title, msg, btns)
.open()
.then(function(result){
alert('dialog closed with result: ' + result);
//Use [$location][1] to change the browser history
});
return promise;
}
}
});
}]);
Building on Rishabh's answer, and using sergey's location.skipReload from this Angular Issue you can use the following to create a dialog on route-change, defer the url-change indefinitely (in effect 'cancelling' the route change), and rewrite the URL bar back to '/' without causing another reload:
//Override normal $location with this version that allows location.skipReload().path(...)
// Be aware that url bar can now get out of sync with what's being displayed, so take care when using skipReload to avoid this.
// From https://github.com/angular/angular.js/issues/1699#issuecomment-22511464
app.factory('location', [
'$location',
'$route',
'$rootScope',
function ($location, $route, $rootScope) {
$location.skipReload = function () {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
return $location;
};
return $location;
}
]);
app
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/home', {
controller: 'HomeCtrl',
templateUrl: 'Home/HomeView.html'
})
.when('/profile/:userId/changepwd', {
template: ' ',
controller: '',
resolve: {
data1: function($dialog, location, $q){
$dialog.messageBox(title, msg, btns)
.open()
.then(function(result){
//fires on modal close: rewrite url bar back to '/home'
location.skipReload().path('/home');
//Could also rewrite browser history here using location?
});
return $q.defer().promise; //Never resolves, so template ' ' and empty controller never actually get used.
}
}
})
.otherwise({
redirectTo: '/'
});
This feels like it leaks unresolved promises, and there may be a neater solution, but this worked for my purposes.
You can redirect the route to the same partial. You can do this by watching for a change in route using the following code. You can also show a dialog from here.
$rootScope.$on( '$routeChangeStart', function(event, next, current) {
if ( next.templateUrl == "xyz.html" ) {
//other validation logic, if it fails redirect user to the same page
$location.path( "/home" );
}
});