web-dev-qa-db-fra.com

Angular JS ui-router comment rediriger vers un état enfant à partir d'un parent?

Lors de l'utilisation de onEnter pour rediriger vers un état, si le nouvel état est un enfant de l'état actuel, une boucle infinie se produit.

Exemple:

$stateProvider
  .state 'inventory',
    url: '/inventory'
    templateUrl: 'views/inventory.html'
    controller: 'InventoryCtrl'
    onEnter: () ->
      $state.go 'inventory.low'
  .state 'inventory.low',
    url: '/low'
    templateUrl: 'views/inventory-table.html'
    controller: 'LowInventoryCtrl'

Quand:

$state.go 'inventory.low'

Est appelé, l'état inventory est réinitialisé, ce qui a pour effet de le rappeler = boucle infinie.

Cependant, si l'état de redirection est:

$state.go 'otherStateThatIsNotAChild'

Ce problème ne se produit pas. Je suppose que l'état parent est en cours de réinitialisation, mais pourquoi?

  1. Pourquoi l’état parent est-il réinitialisé lorsque .go est appelé dans un état enfant?
  2. Comment alors géreriez-vous la redirection vers un état enfant?
14
TaylorMac

1) Pourquoi l’état parent est-il réinitialisé lorsque .go est appelé sur un état de l'enfant?

Lors du processus de transition, tout $ state.go/transitionTo entraînera le remplacement de la transition actuellement en cours. Une transition en cours qui est remplacée est immédiatement annulée. Étant donné que votre transition d'origine vers inventory n'est pas terminée au moment où tous les états 'onEnters sont appelés, la transition d'origine est annulée et une transition new vers inventory.low est lancée à partir de l'état précédemment actif.

Voir ui-router src https://github.com/angular-ui/ui-router/blob/master/src/state.js#L897 ... 

2) Comment alors géreriez-vous la redirection vers un état enfant?

Vous pourriez... 

  • Enveloppez $state.go dans une $timeout() pour permettre à la transition d'origine de se terminer avant la redirection.
  • Appelez $ state.go depuis votre contrôleur à la place. UI-router appelle le contrôleur à partir de la directive ui-view APRES la fin de la transition.

Dans tous les cas, assurez-vous que votre application redirige de la sorte. Si un utilisateur accédait directement à un autre état d'inventaire de l'inventaire (tel que inventory.high), la redirection se produirait toujours, le forçant à passer à inventory.low, ce qui ne correspondrait pas à son intention.

20
Chris T

J'ai eu le même problème. La solution simple que j'ai trouvée consiste à écouter les changements d'état et à les rediriger vers votre état enfant à partir de là.

C'est une sorte de hack qui fait que les routes redirigent vers des états enfants par défaut. Il n'est pas nécessaire de faireurl: '' ou $state.go(), ils ne fonctionnent pas correctement.

Donc, dans le fichier config:

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) {
  if (toState.redirectTo) {
    event.preventDefault();
    $state.go(toState.redirectTo, toParams);
  }
});

Dans le fichier state:

.state('home', {
  url: '',
  redirectTo: 'home.list'
});

J'ai fait un exemple sur Gist: https://Gist.github.com/nikoloza/4ab3a94a3c6511bb1dac

9
nikoloza

Vous devez prendre du recul et réfléchir à ce que vous essayez d'accomplir. Quel est l'intérêt d'avoir un état alors qu'il ne fait que rediriger vers un état enfant?

En ce qui concerne votre première question, les états parents sont toujours activés lorsque vous arrivez à un état enfant. Ce comportement est extrêmement utile pour le partage de données entre états. Sans cela, le routage imbriqué serait impossible (ou plutôt n'aurait aucun sens).

En ce qui concerne la deuxième question, j'ai travaillé sur quelques grosses applications angulaires et je n'ai pas encore eu besoin de le faire.

OK, croyez-le ou non, autant que je déteste dire ça, je suis tombé sur un scénario dans lequel je devais le faire. J'ai un itinéraire profile/userName (techniquement, il devrait s'agir de profile/userName/details) et d'un itinéraire profile/userName/products, je voulais avoir une vue principale pour les deux états, mais je voulais en même temps que l'itinéraire profile/userName contienne une URL propre, comme: profile/mike62, NOT profile/mike62/details. Alors j'ai fini par faire ceci:

.state('publicProfile', { url: '/profile/{username}'})//this is the base/Shell state
.state('publicProfile.details',{url:null})//I want this to be my de-facto state, with a clean URL, hence the null url
.state('publicProfile.products', {url:'/products/{exceptProductId:/?.*}'})

En fin de compte, il y a plusieurs façons:

dans mon contrôleur d'état publicProfile (il s'agit de l'état de base):

$scope.state = $state;
$scope.$watch('state.current', function(v) {
       if(v.name=='publicProfile') {//want to navigate to the 'preferred' state if not going to publicProfile.products
            $state.go('publicProfile.details');
       };
}, true);

Oui, cela me semble hacky, mais je sais maintenant que nous voulons faire cela dans certains cas Edge, même si nous pourrions repenser notre conception de l'état. Une autre façon, plus sale, serait de vérifier l'état actuel dans un $ timeout avec un léger retard dans l'état de base. Si nous ne sommes pas dans l'état publicProfile.products, nous passons à l'état préféré/de facto de publicProfile.details.

4
Mohammad Sepahvand

On dirait que vous essayez de définir un état enfant par défaut.

Voici une question fréquemment posée à propos de ui-router: Comment: configurer un état enfant par défaut/index

Le tl; dr doit utiliser états abstraits selon les paramètres abstract: true dans l’état parent. Si vous ajoutez plusieurs états enfants, il passera par défaut au premier état enfant.

$stateProvider

  .state('inventory', {
    abstract: true,
    url: '/inventory',
    templateUrl: 'views/inventory.html',
    controller: 'InventoryCtrl'
  }).

    .state('inventory.low', {
      url: '/low',
      templateUrl: 'views/inventory-table.html',
      controller: 'LowInventoryCtrl'
    });
3
Atav32

Selon la réponse de pdvorchik sur le fil de discussion GitHub associé , il existe un correctif simple qui a parfaitement fonctionné pour moi, consistant à encapsuler l'appel $state.go dans un appel .finally() pour s'assurer que la première transition se termine avant la nouvelle commencé.

onEnter: ['$state', function($state){
    if ($state.transition) {
        $state.transition.finally(function() {
            $state.go('home.my.other.state', {})
        });
    }
}]
2
temporary_user_name

Après l'explication de @Chris T, il semble que la solution la plus propre consiste à écouter l'événement $stateChangeSuccess:

redirects =
  inventory: 'inventory.low'

$rootScope.$on '$stateChangeSuccess', (e, toState) ->
  redirect = redirects[toState.name]
  $state.go redirect if redirect

Où les redirections contiendront toutes les redirections de route pouvant survenir. Merci a tous!

1
TaylorMac

Ne faites pas de l'état d'inventaire faible un état enfant.

$stateProvider
    .state 'inventory',
        url: '/inventory'
        templateUrl: 'views/inventory.html'
        controller: 'InventoryCtrl'
        onEnter: () ->
            $state.go 'lowinventory'
    .state 'lowinventory',
        url: '/inventory/low'
        templateUrl: 'views/inventory-table.html'
        controller: 'LowInventoryCtrl'
0
craigb