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.
Related
I want to protect various routes on my site using Firebase V3, AngularJS and ui.router.
This looks like a similar issue. I've followed the steps from that SO post but its not working for me.
What I expect to happen:
When clicking the FAQ link I should be forwarded to the login page if logged out and should display the FAQ page when logged in.
What actually happens:
FAQ page isn't accessible at all. Logging in doesn't make any difference. It also doesn't forward me to the login page when logged out.
I'm getting this error within my run function.
ReferenceError: Firebase is not defined(…)
I've included AngularFire on the page, if I don't I get a module injector error even if I remove Firebase from the dependency array.
var app = angular.module('app', ['ui.router', 'firebase']);
app.constant('FirebaseDatabaseUrl', 'https://myfbdb.firebaseio.com');
app.config(function($stateProvider, $urlRouterProvider, $firebaseRefProvider, FirebaseDatabaseUrl) {
$firebaseRefProvider.registerUrl(FirebaseDatabaseUrl);
// If a route other than status is requested,
// go to the auth route
//$urlRouterProvider.otherwise('/logintest/login');
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'pages/login.html',
controller: 'LoginController as login'
})
.state('faq', {
url: '/faq',
templateUrl: 'pages/faq.html',
controller: 'FaqController as faq',
resolve: {
// controller will not be loaded until $requireSignIn resolves
"firebaseUser": ["$firebaseAuthService", function($firebaseAuthService) {
console.log('waitForSignIn')
// $waitForSignIn returns a promise so the resolve waits for it to complete
return $firebaseAuthService.$waitForSignIn();
}]
}
})
.state('about', {
url: '/about',
templateUrl: 'pages/about.html',
controller: 'AboutController as about',
resolve: {
// controller will not be loaded until $requireSignIn resolves
"firebaseUser": ["$firebaseAuthService", function($firebaseAuthService) {
// If the promise is rejected, it will throw a $stateChangeError
return $firebaseAuthService.$requireSignIn();
}]
}
})
});
app.controller('FaqController', ['$scope', 'firebaseUser', function($scope, firebaseUser){
console.log('faq')
}]);
app.run(["$rootScope", "$state", function($rootScope, $state) {
console.log('run');
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
// We can catch the error thrown when the $requireSignIn promise is rejected
// and redirect the user back to the home page
if (error === "AUTH_REQUIRED") {
console.log('redirecting to login page')
$state.go("login");
}
});
}]);
AngularFire versions 2.0+ are compatible with Firebase 3.0. Anything below AngularFire 2.0 is for the legacy version of Firebase.
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.
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
});
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.
I have an app with a service which wraps my API calls:
var ConcernService = {
...
get: function (items_url, objId) {
var defer = $q.defer();
$http({method: 'GET',
url: api_url + items_url + objId}).
success(function (data, status, headers, config) {
defer.resolve(data);
}).error(function (data, status, headers, config) {
console.log('ConcernService.get status',status);
defer.reject(status);
});
return defer.promise;
},
and I'm using UI-Router to transition between states:
concernsApp
.config( function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/404/");
$stateProvider.state('project', {
url: '/project/:projectId/',
resolve: {
project: function ($stateParams, ConcernService) {
return ConcernService.get('projects/', $stateParams.projectId);
},
},
views: {
...
}
});
I'm moving from using the normal AngularJS router and I'm having difficulty understanding how to implement 404s. I can see the ConcernService throwing the console.log status as rejected, but how do I catch this in the state router?
The otherwise() rule is only invoked when no other route matches. What you really want is to intercept the $stateChangeError event, which is what gets fired when something goes wrong in a state transition (for example, a resolve failing). You can read more about that in the state change event docs.
The simplest implementation for what you're trying to do would be something like this:
$rootScope.$on('$stateChangeError', function(event) {
$state.go('404');
});
Also, since $http itself is built on promises (which resolve resolves), your ConcernService method can be simplified down to a one-liner (I realize you expanded it for debugging purposes, but you could have just as easily chained it, just FYI):
var ConcernService = {
get: function (items_url, objId) {
return $http.get(api_url + items_url + objId);
}
}
I differ between two 404 states:
Server:
show 404 page depending on server response HTTP Code 404
important to define no URL, so that user stays on URL where the error happened
Client:
URL is not found by angular ui router (none of defined URLs)
Code for Angular UI-Router state:
$stateProvider
.state('404server', {
templateUrl: '/views/layouts/404.html'
})
.state('404client', {
url: '*path',
templateUrl: '/views/layouts/404.html'
});
Code in $httpProvider interceptor:
if(response.status === 404) {
$injector.get('$state').go('404server');
}
And why I used $injector instead of $state is explained here.
You can also try something like this and see if it works for you. You may need to adjust to your needs:
.state('otherwise', {
abstract: true,
templateUrl: 'views/404.html'
})
.state('otherwise.404', {
url: '*path',
templateUrl: 'views/404.html'
})
The $urlRouterProvider only works like a $watch to $location and if the actual URL matches one of the rules defined in the .config() function then it will redirect to the specified route.
Here's what I recommend, define "/404/" as a state:
$stateProvider.state('404', {
url:'/404/',
views:{
...
}
});
And inside the reject() function move to 404 state
if(status == '404'){
$state.transitionTo('404');
}
You will have to add ui-router as dependency of the project module and use the $state provider in your controller in order to be able to use $state.transitionTo()
Here's some info: https://github.com/angular-ui/ui-router/wiki/Quick-Reference#statetransitiontoto-toparams--options
I managed to handle 404 without using $urlRoutProvider since I'm only using states by testing $state.transistion:
angular.module("app", []).run(["$state", "$rootScope", function($state, $rootScope) => {
$rootScope.$on("$locationChangeSuccess", function() {
if (!$state.transition) {
$state.go("404");
}
});
}]);
$urlRouterProvider.otherwise('/page-not-found');
.state('error', {
url: "/page-not-found",
templateUrl: "templates/error.html",
controller: "errorController"
})
Will handle your page not found problem.
If you want to raise 404 found purposefully use the state or url. We have created a separate controller just if you want to perform any operations.