Pourquoi AngularJS avec ui-router continue de lancer l'événement $stateChangeStart?

j'essaie de bloquer toutes les modifications de l'état du routeur jusqu'à ce que j'ai authentifié l'utilisateur:

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

je rejette tous les changements d'état jusqu'à ce que l'utilisateur ait été authentifié, mais si je vais à une URL non valide qui utilise le otherwise() configuration, j'obtiens une boucle infinie avec un message:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: $locationWatch; newVal: 7; oldVal: 6"],["fn: $locationWatch; newVal: 8; oldVal: 7"],["fn: $locationWatch; newVal: 9; oldVal: 8"],["fn: $locationWatch; newVal: 10; oldVal: 9"],["fn: $locationWatch; newVal: 11; oldVal: 10"]]

ci-dessous est mon SSCCE. Servir avec python -m SimpleHTTPServer 7070 et accédez à localhost:7070/test.html#/bar le voir exploser dans votre visage. Tandis que la navigation directe vers les seuls angularjs valides l'emplacement ne pas exploser localhost:7070/test.html#/foo:

<!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', '$urlRouterProvider',
        function($stateProvider,   $urlRouterProvider) { 
          $urlRouterProvider.otherwise('/foo');
          $stateProvider.state('/foo', {
            url: '/foo',
            template: '<div>In Foo now</div>',
            reloadOnSearch: false
          })
        }]
      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>

Est-il une autre méthode que je devrais utiliser pour accomplir cette authentification blocage? Je ne rends cette authentification blocage est côté client seulement. Je ne montre pas le côté serveur des choses dans cet exemple.

24
demandé sur Ross Rogers 2014-07-31 21:49:23
la source

6 ответов

on dirait que c'est un bug avec ui-router quand vous utilisez la combinaison de $urlRouterProvider.autrement ("/foo) avec le $stateChangeStart.

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

Frank Wallis fournit une bonne solution, utilisez la forme plus longue de la méthode otherwise qui prend une fonction comme argument:

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

beau travail Frank!

48
répondu George S 2014-09-10 03:37:50
la source

Fakeout. C'est une interaction entre $urlRouterProvider et $stateProvider. Je ne devrais pas être à l'aide de $urlRouterProvider mon otherwise. Je devrais être en utilisant quelque chose comme:

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

ou même une redirection transparente:

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

dans l'ensemble maintenant:

<!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>
14
répondu Ross Rogers 2014-07-31 23:05:25
la source

j'ai aussi eu ce problème. Voici le code à contourner, qui s'inspire de angulaires-autorisation projet.

Le concept principal est d'ajouter un drapeau($$finishAuthorize) dans l'état manuellement, et briser la boucle infinie par ce drapeau. Un autre point que nous devons être conscients est le {notify: false} option de $state.go, et de diffusion "$stateChangeSuccess" événement manuellement.

$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)
    }
);
2
répondu Jessie 2015-06-23 12:05:42
la source

j'ai aussi eu ce problème. Il s'avère que c'était le code qu'ils ont suggéré de rendre une barre oblique de queue optionnelle à 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 + '/';
});

changé ce

$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 + '/');
});

ne pas retourner le nouveau chemin et simplement le remplacer ne déclenche pas un StateChangeStart

1
répondu Tom Wu 2015-04-01 23:28:17
la source

essayez de changer votre bloc run en ceci:

    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
répondu Neeraj Kumar Singh 2014-07-31 22:40:25
la source

j'ai essayé les solutions ci-dessus, avec plus ou moins de succès (je construis une application ionique cordova). À un moment, j'ai réussi à ne pas obtenir des boucles infinies et l'état changerait mais j'ai été laissé avec une vue blanche. J'ai ajouté { reload:true } et il semble pour aider. J'ai essayé avec { notify:false } et { notify: true } et cela n'a pas aidé.

j'ai fini par utiliser la plupart de la réponse: 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
répondu Eamonn Gahan 2017-05-23 15:09:39
la source

Autres questions sur