13

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?

6 Answers 6

22

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);
  }
}
Sign up to request clarification or add additional context in comments.

9 Comments

Right, dealing with the 404 like that totally makes sense. With the $http, though, if I implement it like you have if won't work as objId comes from the $stateParams and isn't yet resolved. Isn't this what the defer method is for?
I'm not really sure what you mean, but when you inject $stateParams into a resolve or onEnter function, you get a local copy representing the parameters for the state being transitioned to.
@NateAbele May I ask how you got $state injected onto the event-handler: $stateChangeError? As I've got error: 'undefined $state' from your example.
@Roylee You inject it into the same function you inject $rootScope. For example: yourModuleName.run(function($rootScope, $state) { /* ... */ });
@XelharK Depends on your use case. Perhaps a user followed a link from which a parameter is extracted and injected into an API call, which returns a 404. In this case, a "not found" error would be totally appropriate to show (however, it's kind of a moot point, since HTTP error codes/messages are totally orthogonal to SPAs).
|
8

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.

1 Comment

You should abort current transition $state.transition.abort(). Otherwise, you'll see Transition Rejection The transition has been superseded by a different transition
3

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'
})

2 Comments

I don't see why you need that abstract route, you can accomplish that with one state?
It worked for me without the abstract route. I just have .state('notFound' { url: '*path', templateUrl: 'views/404.html' })
2

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

2 Comments

In my code status doesn't seem to have a rejected property, it just equates to '404'.
Oh sorry for the confusion, I just put it like that as an example, let me update the code.
1

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");
        }
    });
}]);

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.