Angular UI router handling 404s

Problem

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 Angualr 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?

Any help much appreciated.

Problem courtesy of: Darwin Tech

Solution

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);
  }
}
Solution courtesy of: Nate Abele

Discussion

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'
})
Discussion courtesy of: ucsarge

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.

Discussion courtesy of: Betty St

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

Discussion courtesy of: Alejandro Figueroa

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");
        }
    });
}]);
Discussion courtesy of: Claudio Mezzasalma

$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.

Discussion courtesy of: BASAVANA GOUDA S PATIL

This recipe can be found in it's original form on Stack Over Flow.