Why does AngularJS with ui-router keep firing the $stateChangeStart event?

后端 未结 6 1604
没有蜡笔的小新
没有蜡笔的小新 2020-12-04 18:29

I\'m trying to block all ui-router state changes until I\'ve authenticated the user:

$rootScope.$on(\'$stateChangeStart\', function (event, next, toParams) {         


        
相关标签:
6条回答
  • 2020-12-04 18:54

    I also had this issue. Turns out it was the code that they suggested to make a trailing slash optional at https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-make-a-trailing-slash-optional-for-all-routes

    $urlRouterProvider.rule(function ($injector, $location) {
      var path = $location.url();
    
      console.log(path);
      // check to see if the path already has a slash where it should be
      if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
        return;
      }
    
      if (path.indexOf('?') > -1) {
        return path.replace('?', '/?');
      }
    
      return path + '/';
    });
    

    changed this to

    $urlRouterProvider.rule(function ($injector, $location) {
      var path = $location.url();
      // check to see if the path already has a slash where it should be
      if (path[path.length - 1] === '/' || path.indexOf('/?') > -1) {
        return;
      }
      if (path.indexOf('?') > -1) {
        $location.replace().path(path.replace('?', '/?'));
      }
      $location.replace().path(path + '/');
    });
    

    not returning the new path and just replacing it doesn't trigger a StateChangeStart

    0 讨论(0)
  • 2020-12-04 18:56

    I also had this issue. Below is the code to workaround, which inspired by angular-permission project.

    The main concept is to add a flag($$finishAuthorize) into state manually, and break the infinite loop by this flag. Another point we need to be aware is the {notify: false} option of $state.go, and broadcast "$stateChangeSuccess" event manually.

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
        if (toState.$$finishAuthorize) {
            return;
        }
        if (!authenticated) {
            event.preventDefault();
            toState = angular.extend({'$$finishAuthorize': true}, toState);
    
            // following $timeout is emulating a backend $http.get('/auth/') request
            $timeout(function() {
                authenticated = true;
                $state.go(toState.name, toParams, {notify: false}).then(function() {
                    $rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
                });
            },1000)
        }
    );
    
    0 讨论(0)
  • 2020-12-04 18:58

    I tried the above solutions, with varying degrees of success (am building an Ionic cordova application). At one point I managed to not get infinite loops and the state would change but I was left with a blank view. I added { reload:true } and it seems to help. I tried with { notify:false } and { notify: true } and that did not help.

    I ended up using most of the answer from: https://stackoverflow.com/a/26800804/409864

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
    
      // Do not redirect if going to an error page
      if (toState.name === 'app.error') {
        return;
      }
    
      // Do not redirect if going to the login page
      if (toState.name === 'app.login') {
        return;
      }
    
      // Do not redirect if there is a token present in localstorage
      var authData = localstorage.getItem('auth');
      if (authData.token) {
        return;
      }
    
      // We do not have a token, are not going to the login or error pages, time to redirect!
      event.preventDefault();
      console.debug('No auth credentials in localstorage, redirecting to login page');
      $state.go('engineerApp.home', {}, {reload: true}); // Empty object is params
    });
    
    0 讨论(0)
  • 2020-12-04 19:04

    Looks like this is a bug with ui-router when you use the combination of $urlRouterProvider.otherwise("/foo) with the $stateChangeStart.

    Issue - https://github.com/angular-ui/ui-router/issues/600

    Frank Wallis provides a nice workaround, use the longer form of the otherwise method that takes a function as an argument:

    $urlRouterProvider.otherwise( function($injector, $location) {
                var $state = $injector.get("$state");
                $state.go("app.home");
            });
    

    Nice work Frank!

    0 讨论(0)
  • 2020-12-04 19:12

    Try changing your run block to this:

        app.run([
                 '$rootScope', '$log','$state','$interval',
        function ($rootScope,   $log,  $state,  $interval) {
          var authenticated = false;
          $rootScope.$on('$stateChangeStart', function (event, next, toParams) {
            if (!authenticated) {
              event.preventDefault()
              //following $timeout is emulating a backend $http.get('/auth/') request
            }
          })
    
    
          var intervalCanceller = $interval(function() {
            //backend call
            if(call succeeds & user authenticated) {
              authenticated = true;
              $interval.cancel(intervalCanceller);
              $state.go(next, toParams);
            }
          }, 3000);
        }
      ])
    
    0 讨论(0)
  • 2020-12-04 19:18

    Fakeout. This is an interaction issue between $urlRouterProvider and $stateProvider. I shouldn't be using $urlRouterProvider for my otherwise. I should be using something like:

    $stateProvider.state("otherwise", {
        url: "*path",
        template: "Invalid Location",
        controller: [
                  '$timeout','$state',
          function($timeout,  $state ) {
            $timeout(function() {
              $state.go('/foo')
            },2000)
          }]
    });
    

    Or even a transparent'ish redirect:

    $stateProvider.state("otherwise", {
        url: "*path",
        template: "",
        controller: [
                  '$state',
          function($state) {
            $state.go('/foo')
          }]
    });
    

    Altogether now:

    <!doctype html>
      <head>
        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.js"></script>
        <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.15/angular.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.10/angular-ui-router.min.js"></script>
      </head>
      <body ng-app="clientApp">
        <div ui-view="" ></div>
    
        <script>
          var app = angular.module('clientApp', ['ui.router'])
    
          var myRouteProvider = [
                    '$stateProvider',
            function($stateProvider) { 
    
              $stateProvider.state('/foo', {
                url: '/foo',
                template: '<div>In Foo now</div>',
                reloadOnSearch: false
              })
    
              $stateProvider.state("otherwise", {
                  url: "*path",
                  template: "",
                  controller: [
                            '$state',
                    function($state) {
                      $state.go('/foo')
                    }]
              });
            }]
          app.config(myRouteProvider)
    
          var authenticated = false
          app.run([
                     '$rootScope', '$log','$state','$timeout',
            function ($rootScope,   $log,  $state,  $timeout) {
              $rootScope.$on('$stateChangeStart', function (event, next, toParams) {
                if (!authenticated) {
                  event.preventDefault()
                  //following $timeout is emulating a backend $http.get('/auth/') request
                  $timeout(function() {
                    authenticated = true
                    $state.go(next,toParams)
                  },1000)
                }
              })
            }
          ])
        </script>
      </body>
    </html>
    
    0 讨论(0)
提交回复
热议问题