AngularJS - Need some combination of $routeChangeStart and $locationChangeStart
Asked Answered
O

4

20

My problem is actually very similar to the one found here:

AngularJs - cancel route change event

In short, I'm using $routeChangeStart and trying to change the current route using $location. When I do, the console shows me that the original page is still loads and is quickly overwritten by the new page.

The solution provided was to use $locationChangeStart instead of $routeChangeStart, which should work for preventing the extra redirect. Unfortunately, I'm using additional data in the $routeprovider that I need to access while changing the route (I use it to track page restrictions). Here's an example...

$routeProvider.
    when('/login', { controller: 'LoginCtrl', templateUrl: '/app/partial/login.html', access: false}).
    when('/home', { controller: 'HomeCtrl', templateUrl: '/app/partial/home.html', access: true}).
    otherwise({ redirectTo: '/login' });


$rootScope.$on('$routeChangeStart', function(event, next, current) {
    if(next.access){
        //Do Stuff
    }
    else{
        $location.path("/login");
        //This will load the current route first (ie: '/home'), and then
        //redirect the user to the correct 'login' route.
    }
});

With $routeChangeStart, I can use the "next" and "current" parameters (see AngularJS - $route) as objects to retrieve my 'access' values. With $locationChangeStart, those two parameters return url strings, not objects. So there seems to be no way to retrieve my 'access' values.

Is there any way I can combine the redirect-stopping power of $locationChangeStart with the object-flexibility of $routeChangeStart to achieve what I need?

Oldfashioned answered 31/7, 2013 at 19:15 Comment(0)
B
22

One approach that comes to mind is trying to use the resolve parameter for this:

var resolver = function(access) {
  return {
    load: function($q) {
      if (access) { // fire $routeChangeSuccess
        var deferred = $q.defer();
        deferred.resolve();
        return deferred.promise;
      } else { // fire $routeChangeError
        return $q.reject("/login");
      }
    }
  }
}

$routeProvider.
  when('/login', { controller: 'LoginCtrl', templateUrl: '/app/partial/login.html', resolve: resolver(false)}).
  when('/home', { controller: 'HomeCtrl', templateUrl: '/app/partial/home.html', resolve: resolver(true)}).
  otherwise({ redirectTo: '/login' });

Please note that I haven't tested the code above but I'm doing similar stuff in my projects.

Blasius answered 31/7, 2013 at 19:40 Comment(4)
Sorry for the late reply. When something works for me, I tend to get excited and move on. This ended up being a perfect solution for what I was doing. In the load function, I was able to add things like $http and $location to further process my requests.Oldfashioned
I had to make my load function look more like the one from Nick https://mcmap.net/q/613777/-angularjs-need-some-combination-of-routechangestart-and-locationchangestart (the reject wasn't working as written here)Weigh
load function should read more like this: var deferred = $q.defer(); if (access) { deferred.resolve(); } else { deferred.reject(); $location.url('/login'); } return deferred.promise;Lykins
@marius-soutier Very huge thanx for the resolve option of when()Clubbable
D
14

I faced the same situation myself and my solution was aligned with what the OP intended to do.

I use the $locationChangeStart event and the $route service. By accessing $route.routes, I get a hold of all route objects defined with $routeProvider.

.run(function($rootScope, $route, $location) {
  $rootScope.$on('$locationChangeStart', function(ev, next, current) {
    // We need the path component of `next`. We can either process `next` and 
    // spit out its path component, or simply use $location.path(). I go with
    // the latter.
    var nextPath = $location.path();
    var nextRoute = $route.routes[nextPath]

    console.log(nextRoute.access); // There you go!
  });
})

To parse the path component out of an absolute URL:

var urlParsingNode = document.createElement('a');
urlParsingNode.href = next;  // say, next = 'http://www.abc.com/foo?name=joe
console.log(urlParsingNode.pathname)  // returns "/foo"
Dumas answered 6/12, 2013 at 2:17 Comment(5)
what if there is query parameter in the url...how do u identify which router is being calledInnermost
@Innermost - I have updated my answer for you. Take a look.Dumas
How does this work for routes with parameters? like /article/:articleId?Fosdick
@Fosdick - That would be a little trickier. I notice that each object in $route.routes has a property named regexp. As its name suggested, it's a regular expression object that you can use to match against the path component you would get from parsing the full URL (mentioned above), while looping through $route.routes. I have never tried it but I think that should work.Dumas
In order to match an arbitrary path containing a parameter against a parameterized route RegExp, you'd have to check against some subset of the routes objects, if not all. For example, you could narrow it down by the first component of the path, if it were never a parameter in any route. Still, iterating over objects to check against multiple RegExp seems inefficient, especially for a $locationChangeStart process.Berga
B
7

Since version 1.3.0 you can actually use the newly introduced preventDefault-method. With that you can cancel the current route change and then apply your own custom redirect as shown in this github-issue:


$rootScope.$on("$routeChangeStart", function (event, next, current) {
    if (next.access) {
      event.preventDefault();
      $rootScope.$evalAsync(function() {
        $location.path('/login');
      });
    }
});

I implemented this method in my own project and it works perfectly. Hope it helps anyone else who stumbles across it.

Budgerigar answered 23/12, 2014 at 11:35 Comment(0)
F
3

Nice answer Marius, put me on the right track. I'm doing something like this for access control. This works however...

  var resolver = function(route, routeEvent) {
  return {
    load: function($q) {
      deferred = $q.defer();
      if (routeEvent!=3) { // eventually will be a list of routeEvents that the logged-in user is not allowed to visit read from a db table configured by admin
        deferred = $q.defer();
        deferred.resolve();
        return deferred.promise;
      } else { // fire $routeChangeError
        alert("You don't have permissions to access this.");
        deferred.reject(route);
        return deferred.promise;
      }
    }
  }
}

  var jsonRoutes = [

    {'route' : '/logout', 'templateUrl': 'partials/login.html',   'controller' : 'LoginCtrl', 'routeEvent' : 1 },
    {'route' : '/orders', 'templateUrl': 'partials/orders.html',   'controller': 'OrderListCtrl', 'routeEvent' : 2 },
    {'route' : '/products', 'templateUrl': 'partials/products.html',   'controller': 'ProductListCtrl', 'routeEvent' : 3 },

...

];


// somewhere on successful login code add in the dynamic routes

angular.forEach(jsonRoutes, function(r) {
                $route.routes[r.route] = {templateUrl: r.templateUrl, controller: r.controller, routeEvent: r.routeEvent, resolve: resolver(r.route, r.routeEvent)};
                  });


// got some static routes too which don't have access control - user has to login right?

  config(['$routeProvider', function($routeProvider) {


  $routeProvider.
    when('/error',  {templateUrl: 'partials/error.html',   controller : ErrorCtrl,  routeEvent : 1 }).
    when('/login',  {templateUrl: 'partials/login.html',   controller : LoginCtrl, routeEvent : 1 }).
    when('/home',  {templateUrl: 'partials/home.html',   controller : HomeCtrl, routeEvent : 1 }).
    when('/logout', {templateUrl: 'partials/login.html',   controller : LoginCtrl, routeEvent : 1 }).
    otherwise( {redirectTo: '/error'} );   

When the /orders route is clicked the promise is rejected (with a pop-up, could be modal dialog) and the route isn't followed.

Hope this helps someone.

Feigned answered 20/10, 2013 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.