Angular - ui-router obtient l'état précédent

152

Existe-t-il un moyen d'obtenir l'état précédent de l'état actuel?

Par exemple, je voudrais savoir quel était l'état précédent avant l'état actuel B (où l'état précédent aurait été l'état A).

Je ne suis pas en mesure de le trouver dans les pages doc github ui-router.

Mihai H
la source
la réponse ci-dessous est correcte, vous pouvez également trouver toutes les informations dont vous avez besoin de la source si la documentation ne vous aide pas assez goo.gl/9B9bH
David Chase

Réponses:

133

ui-router ne suit pas l'état précédent une fois qu'il transite, mais l'événement $stateChangeSuccessest diffusé le $rootScopelorsque l'état change.

Vous devriez être en mesure d'attraper l'état précédent de cet événement ( fromest l'état que vous quittez):

$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
   //assign the "from" parameter to something
});
Laurelnaiad
la source
3
Quel serait un exemple utilisant "de"?
Paul
Ici, «to» et «from» signifie «toState» et «fromState». Considérez que votre URL précédente est localhost: xxxx / employee et le contrôleur est 'EmployeesController' alors l'exemple pour 'fromState' est: Object {url: "/ Employees", templateUrl: "/ Employees", controller: "EmployeesController", name: " employés "}
Ranadheer Reddy
3
J'ai fait cela dans mon résumé: `$ rootScope.previousState; $ rootScope.currentState; $ rootScope. $ on ('$ stateChangeSuccess', function (ev, to, toParams, from, fromParams) {$ rootScope.previousState = from.name; $ rootScope.currentState = to.name; console.log ('État précédent: '+ $ rootScope.previousState) console.log (' État actuel: '+ $ rootScope.currentState)}); `Il garde une trace des précédents et des actuels dans rootScope. Très pratique!
Federico
L'utilisation de la solution @ endy-tjahjono ( stackoverflow.com/a/25945003/2837519 ) est plus intégrée à ui-router 1.x.
Peter Ahlers
J'aime une façon simple avec history.back () <a href="javascript:history.back()" class="btn btn-default no-radius">
Serip88
146

J'utilise la résolution pour enregistrer les données d'état actuel avant de passer au nouvel état:

angular.module('MyModule')
.config(['$stateProvider', function ($stateProvider) {
    $stateProvider
        .state('mystate', {
            templateUrl: 'mytemplate.html',
            controller: ["PreviousState", function (PreviousState) {
                if (PreviousState.Name == "mystate") {
                    // ...
                }
            }],
            resolve: {
                PreviousState: ["$state", function ($state) {
                    var currentStateData = {
                        Name: $state.current.name,
                        Params: $state.params,
                        URL: $state.href($state.current.name, $state.params)
                    };
                    return currentStateData;
                }]
            }
        });
}]);
Endy Tjahjono
la source
8
bien mieux que de traiter$stateChangeSuccess
SET
10
À mon avis, cela devrait être la réponse acceptée. Événement bien que les $stateChangeSuccesstravaux, il le fait au niveau mondial, ce qui n'est pas vraiment nécessaire la plupart du temps.
Pierre Spring
2
Je suis d'accord. C'est beaucoup plus propre. Si vous avez affaire à de nombreux modules qui ont tous des états, $ stateChangeSuccess sera déclenché pour chaque changement d'état, pas seulement ceux de votre module. Un autre vote pour que ce soit la meilleure solution.
Jason Buchanan
1
@ Nissassin17 quelle version utilisez-vous? Dans v0.2.15 l'ouverture d' un état directement l' $state.currentest objet: {name: "", url: "^", views: null, abstract: true}.
Endy Tjahjono
3
FYI - J'avais besoin de faire ce qui suit: Paramètres: angular.copy ($ state.params) - Explication: J'utilise UI-Router v 1.0.0-beta 2 et j'avais des problèmes avec la partie Params de ce code; parce que $ state.params est un objet, il sera mis à jour dans la vue actuelle une fois la fonction résolue ... je devais le faire dans la fonction de résolution pour éviter que l'objet ne soit mis à jour dans la vue actuelle.
Joe H du
100

Par souci de lisibilité, je vais placer ma solution (basée sur la réponse de stu.salsbury) ici.

Ajoutez ce code au modèle abstrait de votre application pour qu'il s'exécute sur chaque page.

$rootScope.previousState;
$rootScope.currentState;
$rootScope.$on('$stateChangeSuccess', function(ev, to, toParams, from, fromParams) {
    $rootScope.previousState = from.name;
    $rootScope.currentState = to.name;
    console.log('Previous state:'+$rootScope.previousState)
    console.log('Current state:'+$rootScope.currentState)
});

Garde une trace des changements dans rootScope. C'est assez pratique.

Federico
la source
19
Cela vaut la peine de stocker les fromParams également si vous voulez vraiment rediriger quelqu'un vers son pays d'origine.
Yaron
J'adore cela et je l'implémente dans mes applications. Merci
Fallenreaper
Vraiment utile ... Encore plus en utilisant localStorage, vous pouvez obtenir le previousState n'importe où.
georgeos
14

Dans l'exemple suivant, j'ai créé un decorator(ne s'exécute qu'une seule fois par application lors de la phase de configuration) et ajoute une propriété supplémentaire au $stateservice, de sorte que cette approche n'ajoute pas de variables globales$rootscope et ne nécessite pas d'ajouter de dépendance supplémentaire à d'autres services que $state.

Dans mon exemple, j'avais besoin de rediriger un utilisateur vers la page d'index quand il était déjà connecté et quand il ne devait pas le rediriger vers la page "protégée" précédente après la connexion.

Les seuls services inconnus (pour vous) que j'utilise sont authenticationFactoryet appSettings:

  • authenticationFactorygère simplement la connexion de l'utilisateur. Dans ce cas, j'utilise uniquement une méthode pour identifier si l'utilisateur est connecté ou non.
  • appSettingssont des constantes juste pour ne pas utiliser de chaînes partout. appSettings.states.loginet appSettings.states.registercontient le nom de l'état pour l'URL de connexion et d'enregistrement.

Ensuite, dans n'importe quel controller/ serviceetc, vous devez injecter le $stateservice et vous pouvez accéder à l'url actuelle et précédente comme ceci:

  • Actuel: $state.current.name
  • Précédent: $state.previous.route.name

Depuis la console Chrome:

var injector = angular.element(document.body).injector();
var $state = injector.get("$state");
$state.current.name;
$state.previous.route.name;

La mise en oeuvre:

(J'utilise angular-ui-router v0.2.17et angularjs v1.4.9)

(function(angular) {
    "use strict";

    function $stateDecorator($delegate, $injector, $rootScope, appSettings) {
        function decorated$State() {
            var $state = $delegate;
            $state.previous = undefined;
            $rootScope.$on("$stateChangeSuccess", function (ev, to, toParams, from, fromParams) {
                $state.previous = { route: from, routeParams: fromParams }
            });

            $rootScope.$on("$stateChangeStart", function (event, toState/*, toParams, fromState, fromParams*/) {
                var authenticationFactory = $injector.get("authenticationFactory");
                if ((toState.name === appSettings.states.login || toState.name === appSettings.states.register) && authenticationFactory.isUserLoggedIn()) {
                    event.preventDefault();
                    $state.go(appSettings.states.index);
                }
            });

            return $state;
        }

        return decorated$State();
    }

    $stateDecorator.$inject = ["$delegate", "$injector", "$rootScope", "appSettings"];

    angular
        .module("app.core")
        .decorator("$state", $stateDecorator);
})(angular);
CodeArtiste
la source
12

Ajouter une nouvelle propriété appelée {previous} à $ state sur $ stateChangeStart

$rootScope.$on( '$stateChangeStart', ( event, to, toParams, from, fromParams ) => {
    // Add {fromParams} to {from}
    from.params = fromParams;

    // Assign {from} to {previous} in $state
    $state.previous = from;
    ...
}

Maintenant, partout où vous avez besoin, vous pouvez utiliser $ state, vous aurez le précédent disponible

previous:Object
    name:"route name"
    params:Object
        someParam:"someValue"
    resolve:Object
    template:"route template"
    url:"/route path/:someParam"

Et utilisez-le comme ceci:

$state.go( $state.previous.name, $state.previous.params );
Luis
la source
9

Je suis coincé avec le même problème et je trouve le moyen le plus simple de le faire ...

//Html
<button type="button" onclick="history.back()">Back</button>

OU

//Html
<button type="button" ng-click="goBack()">Back</button>

//JS
$scope.goBack = function() {
  window.history.back();
};

(Si vous voulez qu'il soit plus testable, injectez le service $ window dans votre contrôleur et utilisez $ window.history.back ()).

NiRmaL
la source
c'est plus compliqué quand vous avez un menu dans votre application
Swanand
ne pas avoir votre problème, veuillez fournir plus de détails
NiRmaL
Vous perdrez $ stateParams si vous ne le poussez pas dans l'url, la meilleure solution est de sauvegarder vos paramètres précédents dans $ rootScope, vous pouvez l'obtenir à partir de cela et pousser dans l'état $ pour revenir en arrière.
dangquang1020
Cette réponse est pour moi la plus simple à mettre en œuvre.
Lalnuntluanga Chhakchhuak
8

J'utilise une approche similaire à ce que fait Endy Tjahjono.

Ce que je fais, c'est enregistrer la valeur de l'état actuel avant d'effectuer une transition. Voyons sur un exemple; imaginez ceci dans une fonction exécutée en cliquant sur ce qui déclenche la transition:

$state.go( 'state-whatever', { previousState : { name : $state.current.name } }, {} );

La clé ici est l'objet params (une carte des paramètres qui seront envoyés à l'état) -> { previousState : { name : $state.current.name } }

Remarque: notez que je ne "sauvegarde" que l'attribut name de l'objet $ state, car c'est la seule chose dont j'ai besoin pour sauvegarder l'état. Mais nous pourrions avoir tout l'objet d'état.

Ensuite, indiquez «tout ce qui a été défini comme suit:

.state( 'user-edit', {
  url : 'whatever'
  templateUrl : 'whatever',
  controller: 'whateverController as whateverController',
  params : {
    previousState: null,
  }
});

Ici, le point clé est l'objet params.

params : {
  previousState: null,
}

Ensuite, à l'intérieur de cet état, nous pouvons obtenir l'état précédent comme ceci:

$state.params.previousState.name
avcajaraville
la source
6

Voici une solution vraiment élégante de Chris Thielen ui-router-extras: $ previousState

var previous = $previousState.get(); //Gets a reference to the previous state.

previousest un objet qui ressemble à: { state: fromState, params: fromParams }où fromState est l'état précédent et fromParams est les paramètres d'état précédent.

Éphraïm
la source
1
Le 16 mai 2017, Chris Thielen a ajouté un avis de fin de vie pour le projet: ui-router-extras.
Michael R
4

Ok, je sais que je suis en retard à la fête ici, mais je suis nouveau à angular. J'essaie d'intégrer cela dans le guide de style John Papa ici. Je voulais le rendre réutilisable alors j'ai créé dans un bloc. Voici ce que j'ai trouvé:

previousStateProvider

(function () {
'use strict';

angular.module('blocks.previousState')
       .provider('previousState', previousStateProvider);

previousStateProvider.$inject = ['$rootScopeProvider'];

function previousStateProvider($rootScopeProvider) {
    this.$get = PreviousState;

    PreviousState.$inject = ['$rootScope'];

    /* @ngInject */
    function PreviousState($rootScope) {
        $rootScope.previousParms;
        $rootScope.previousState;
        $rootScope.currentState;

        $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
            $rootScope.previousParms = fromParams;
            $rootScope.previousState = from.name;
            $rootScope.currentState = to.name;
        });
    }
}
})();

core.module

(function () {
'use strict';

angular.module('myApp.Core', [
    // Angular modules 
    'ngMessages',
    'ngResource',

    // Custom modules 
    'blocks.previousState',
    'blocks.router'

    // 3rd Party Modules
]);
})();

core.config

(function () {
'use strict';

var core = angular.module('myApp.Core');

core.run(appRun);

function appRun(previousState) {
    // do nothing. just instantiating the state handler
}
})();

Toute critique sur ce code ne fera que m'aider, alors faites-moi savoir où je peux améliorer ce code.

fizch
la source
2

Si vous avez juste besoin de cette fonctionnalité et que vous souhaitez l'utiliser dans plus d'un contrôleur, il s'agit d'un service simple pour suivre l'historique des itinéraires:

  (function () {
  'use strict';

  angular
    .module('core')
    .factory('RouterTracker', RouterTracker);

  function RouterTracker($rootScope) {

    var routeHistory = [];
    var service = {
      getRouteHistory: getRouteHistory
    };

    $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
      routeHistory.push({route: from, routeParams: fromParams});
    });

    function getRouteHistory() {
      return routeHistory;
    }

    return service;
  }
})();

où le 'core' dans .module ('core') serait le nom de votre application / module. Exigez le service en tant que dépendance de votre contrôleur, puis dans votre contrôleur, vous pouvez faire:$scope.routeHistory = RouterTracker.getRouteHistory()

user1970565
la source
4
Si j'avais un bouton de navigation sur ma page qui passe à l'état précédent de l'historique, après avoir navigué vers cet état précédent, stateChangeSuccess serait déclenché, ce qui l'ajoute à l'historique. Donc, ce code ne finirait-il pas par une boucle sans fin de va-et-vient entre 2 pages?
whatsTheDiff
1

Je garde une trace des états précédents dans $ rootScope , donc chaque fois que j'en aurai besoin, j'appellerai simplement la ligne de code ci-dessous.

$state.go($rootScope.previousState);

Dans App.js :

$rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
  $rootScope.previousState = from.name;
});
Naveen Kumar V
la source
1
Je pense que c'est mieux si vous enregistrez non seulement from.name. De cette façon, vous aurez également les paramètres, au cas où vous auriez besoin de faire quelque chose comme $ state.go ($ tootScope.previousState.name, $ tootScope.previousState.params);
abelabbesnabi
0

Pour UI-Router (> = 1.0), les événements StateChange sont obsolètes. Pour un guide de migration complet, cliquez ici

Pour obtenir l'état précédent de l'état actuel dans UI-Router 1.0+:

app.run(function ($transitions) {
    $transitions.onSuccess({}, function (trans) {
         // previous state and paramaters
         var previousState = trans.from().name;
         var previousStateParameters = trans.params('from');
    });
});
NagarajuRaghava
la source
-1

Une solution très simple consiste simplement à modifier la chaîne $ state.current.name et à tout supprimer, y compris et après le dernier '.' - vous obtenez le nom de l'état parent. Cela ne fonctionne pas si vous sautez beaucoup entre les états, car il analyse simplement le chemin actuel. Mais si vos états correspondent à l'endroit où vous vous trouvez réellement, cela fonctionne.

var previousState = $state.current.name.substring(0, $state.current.name.lastIndexOf('.'))
$state.go(previousState)
Juhana Pietari Lehtiniemi
la source
1
$state.go('^')va accomplir cela
james
Et qu'en est-il des paramètres d'état?
Tushar Shukla
-2

Vous pouvez renvoyer l'état de cette façon:

$state.go($state.$current.parent.self.name, $state.params);

Un exemple:

(function() {
    'use strict'

    angular.module('app')
        .run(Run);

    /* @ngInject */
    function Run($rootScope, $state) {

        $rootScope.back = function() {
            $state.go($state.$current.parent.self.name, $state.params);
        };

    };

})();
otaviodecampos
la source
2
Ce n'est pas une bonne réponse si vous accédez à l'état depuis un état non parent. Si vous voulez juste le parent de l'état actuel, il fait le travail.
Maxime Lafarie
1
N'est-ce pas la même chose que d'utiliser $ state.go ("^")?
Nathan Moinvaziri du