witoldsz / angular-http-auth

MIT License
2.38k stars 417 forks source link

How to revert to previous location after auth-loginCancelled? #55

Closed bbshopadmin closed 5 years ago

bbshopadmin commented 10 years ago

When event:auth-loginRequired event is fired on a route change promise, the location has already changed to the new route. How can i revert that location change on the auth-loginCancelled event?

witoldsz commented 10 years ago

Hi, I am sorry, but this question does not seem to address angular-http-auth, but some route-handling library of your choice, does it?

More than that, I strongly advise against any kind of redirection in response to event:auth-loginRequired, because this can destroy the user's context. Making everything invisible excluding the login form is much simpler and does not reset anything, like state of forms, selected options or what-else user was doing. See the demo app for reference.

bbshopadmin commented 10 years ago

You're right it has only indirectly to do with angular-http-auth. But i think it's a standard scenario. I have a normal route with a promise which has to be authenticated. When i change to that route i show a login form popup which is cancelable. I have no redirect in there. The problem is that the location has already changed before the route with the promise is resolved. Somehow i have to manually reject that promise when the user clicks cancel on the login popup. Is there an example how to do all that?

witoldsz commented 10 years ago

I have never used default AngularJS routes with a resolve route declaration, so I cannot tell. Maybe you could provide some example code, so we could pin the problem to something tangible?

bbshopadmin commented 10 years ago

Ok, let's say i'm on '/home' view and go to '/item', which requests server data via ItemResource in which the user must be authenticated, therefor the login form shows up.

$routeProvider
    .when('/item', {
        templateUrl: 'item.tpl.html',
        controller: 'ItemController',
        resolve: {
            data: ['ItemResource', function(ItemResource){
                return ItemResource.get().$promise;
            }]
        }
    });

The login form popup is cancelable and fires a loginCancelled event. Till here it's working fine. Now i've to reject the routes resolve/promise somehow:

$scope.$on('event:auth-loginCancelled', function() {
    // reject ItemResource.get().$promise;
});

In case of loginCancelled i've than to change the location/url back to previous location '/home', due to location is now '/item' and gets never resolved.

witoldsz commented 10 years ago

OK, so I don't know much about resolve of route providers and also never used "Resource" module, but let me guess, correct me if I am wrong.

The resolve is an object with keys as identifiers for injection point and values which may evaluate to promises, right? In your case, routing infrastructure waits for data's promise to resolve before creating ItemController? Also, while waiting for data, if the promise blows with an exception, you will actually never reach that "/item" destination... and this is what you want?

In that case let's move to part 2, this is the return ItemResource.get().$promise. I guess, that thing invokes HTTP request, that request returns with STATUS=401, then angular-http-auth comes into play. It captures the response, so the ItemResource.get().$promise does neither resolve nor reject...

OK, now I get it. There is one more thing before you reach this:

$scope.$on('event:auth-loginCancelled', function() {
    // reject ItemResource.get().$promise;
});

You had manually to invoke the authService.loginCancelled(data, reason) (then, and only then you will end up in the scope event from above snippet), see the source code. If you provide any reason, the buffered promises will be rejected. If you provide no reason, the promises will be discarded.

So, to answer your question, just provide a reason parameter and you will get your reject.

bbshopadmin commented 10 years ago

Thank you, i missed reason, i'm now getting the $routeChangeError event. I've only left that wrong $location issue and that the rejected route remains as history entry. I couldn't find something useful so far.

bbshopadmin commented 10 years ago

I can't find a solution regarding the wrong $location and history issues. How would you handle a cancelable login popup?

witoldsz commented 10 years ago

Did you try the $location.path('/').replace() thing?

bbshopadmin commented 10 years ago

Yes, but nothing happens. The history entry doesn't get removed and the location looks still wrong. Respectively replace() works on the next location change. I'm stuck on $routeChangeError.

bbshopadmin commented 10 years ago
$rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
    $location.replace();
    $location.path(previous.$$route.originalPath);
});

This would reload '/home', but i'm already/still on '/home' because '/item' was rejected and never rendered. I don't want that reload.

bbshopadmin commented 10 years ago

I'm thinking about: '/home' > '/item' (rejected, because of cancelled auth) > replace() > '/error'

The history would now contain: '/home' > '/error'

Perhaps somthing like this would reasonable: '/home' > '/item' (rejected, because of cancelled auth) > replace() > '/error' > replace() To get rid of that '/error' page too.

witoldsz commented 10 years ago

I think you can get much better support for $route and $location on AngularJS project mailing list, I really can't help you much on this subject.

bbshopadmin commented 10 years ago

Ok, thank you. But, i think it could be intresting for other users of your lib too. I can't imagine i'm the only one who ever tried that combination.

witoldsz commented 10 years ago

Let us know if you find the solution.

feliksg commented 10 years ago

Perhaps you can use

$route.reload()
bbshopadmin commented 10 years ago

@feliksg Thank you. In the docs it says reload() "Causes $route service to reload the current route even if $location hasn't changed.". I only want to change the current $location url back to the previous one without any reload. Due to $route listens to $location changes i don't think i can do that. For now i switched from the login popup to a login view.

artursmolarek commented 9 years ago

Any progress on this?

sSeewald commented 9 years ago

Hi, I was recently faced with the same problem. My solution is pretty simple:

app.run([ '$rootScope', '$location', function ($rootScope,$location, auth) {
    $rootScope.previousLocation = '/';
    $rootScope.currentLocation = '/';
    /** Simply switch the Locations and save the new to current */
    $rootScope.$on("$locationChangeStart", function (e, currentLocation, previousLocation) {
        $rootScope.previousLocation = $rootScope.currentLocation;
        $rootScope.currentLocation = $location.path();
    });
    /** Switch back */
    $rootScope.$on("event:auth-loginCancelled", function (data) {
        $location.path($rootScope.previousLocation);
    });
}]);

I hope it helps :)

wcyrek-comrise commented 9 years ago

This is useful solution and I may use it. But I might expend it to something that keeps track of entire history of routes, providing better functionality for a back/forward buttons. Yes I know browsers can do this, but on mobile I'd prefer to render my own buttons (or rather map it to drag left/right).

sSeewald commented 9 years ago

Hi @wcyrek-comrise the history remains intact.

witoldsz commented 9 years ago

I think it all goes too far. My experience is that we should never tinker with location on authentication events. This module works best when used as in demo:

The only exception to this rule is when the newly logged user is not the same as previously, so we discard all the cached requests and everything and redirect to a landing page.