I start with AngularJS and ui-router, and try to process 404s on resources that are not found. I would like the error to be displayed without changing the URL in the address bar.
I configured my states as such:
app.config([ "$stateProvider", function($stateProvider) { $stateProvider .state("home", { url: "/", templateUrl: "app/views/home/home.html" }) .state("listings", { abstract: true, url: "/listings", templateUrl: "app/views/listings/listings.html" }) .state("listings.list", { url: "", templateUrl: "app/views/listings/listings.list.html", }) .state("listings.details", { url: "/{id:.{36}}", templateUrl: "app/views/listings/listings.details.html", resolve: { listing: [ "$stateParams", "listingRepository", function($stateParams, repository) { return repository.get({ id: $stateParams.id }).$promise; } ] } }) .state("listings.notFound", { url: "/404", template: "Listing not found" }); } ]);
(I actually use TypeScript, but I tried changing above to be pure JavaScript)
If, say, I go to the following URL: http://localhost:12345/listings/bef8a5dc-0f9e-4541-8446-4ebb10882045 This should open the lists.details state. However, if this resource does not exist, the promise returned by the permission function will fail with 404, which I will catch here:
app.run([ "$rootScope", "$state", function($rootScope, $state) { $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) { event.preventDefault(); if (error.status === 404) { $state.go("^.notFound", null, { location: true, relative: toState }); } }); } ]);
What I'm trying to do here is go into the listings.notFound state without changing the destination URL in the address bar. I use a relative path because I would like to reuse this logic for other resources.
However, I get an exception:
Path '^ .notFound' is not valid for state 'lists.details'
This error occurs because the toState argument given by the $ stateChangeError event does not know its parent, i.e. toState.parent undefined. In the transition function to the ui-router function, I see that the object specified as an argument, to.self , which provides only a subset of the information. Using relative: $state.get(toState.name) doesn't help either, because the internal ui-router returns state.self again
I would like to avoid saving the list of absolute paths and rewrite the logic for navigation in the state hierarchy (no matter how simple it is, DRY and all that).
Am I really mistaken if there is another correct way to handle 404? If not, what is the best approach?