Angular - ui-router obtém estado anterior

152

Existe uma maneira de obter o estado anterior do estado atual?

Por exemplo, eu gostaria de saber qual era o estado anterior antes do estado atual B (onde o estado anterior teria sido o estado A).

Não consigo encontrá-lo nas páginas de documentos do ui-router github.

Mihai H
fonte
a resposta abaixo está correto, você também pode encontrar toda a informação que você precisa a partir da fonte, se os documentos não são suficientes ajuda goo.gl/9B9bH
David Chase

Respostas:

133

O ui-router não rastreia o estado anterior depois da transição, mas o evento $stateChangeSuccessé transmitido no$rootScope quando o estado muda.

Você deve conseguir capturar o estado anterior desse evento ( fromé o estado que você está saindo):

$rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) {
   //assign the "from" parameter to something
});
laurelnaiad
fonte
3
O que seria um exemplo usando "from"?
Paul
Aqui 'para' e 'de' significa 'toState' e 'fromState'. Considere que o seu URL anterior é localhost: xxxx / employee e controller é 'EmployeesController'; o exemplo para 'fromState' é: Objeto {url: "/ employee", templateUrl: "/ employee", controller: "EmployeesController", name: " funcionários "}
Ranadheer Reddy 17/13
3
Eu fiz isso no meu resumo: `$ rootScope.previousState; $ rootScope.currentState; $ rootScope. $ on ('$ stateChangeSuccess', function (ev, to, toParams, from, fromParams) {$ rootScope.previousState = from.name; $ rootScope.currentState = to.name; console.log ('Estado anterior: '+ $ rootScope.previousState) console.log (' Estado atual: '+ $ rootScope.currentState)}); `Ele monitora o anterior e o atual no rootScope. Muito útil!
Federico
Usar a solução @ endy-tjahjono ( stackoverflow.com/a/25945003/2837519 ) é mais inline do ui-router 1.x.
Peter Ahlers # 03
Eu amo uma maneira simples com history.back () <a href="javascript:history.back()" class="btn btn-default no-radius">
Serip88
146

Uso resolv para salvar os dados do estado atual antes de passar para o novo estado:

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
fonte
8
muito melhor do que lidar com$stateChangeSuccess
SET
10
Na minha opinião, esta deve ser a resposta aceita. Apesar das $stateChangeSuccessobras, ele é feito em nível global, o que não é realmente necessário na maioria das vezes.
Pierre Spring
2
Concordo. Isso é muito mais limpo. Se você estiver lidando com muitos módulos com estados, $ stateChangeSuccess será acionado para todas as alterações de estado, não apenas as do seu módulo. Outro voto para que esta seja a melhor solução.
Jason Buchanan
1
@ Nissassin17 qual versão você usa? Em v0.2.15 ao abrir um estado directamente o $state.currenté objecto: {name: "", url: "^", views: null, abstract: true}.
Endy Tjahjono 9/03/16
3
FYI - Eu precisava fazer o seguinte: Params: angular.copy ($ state.params) - Explicação: Estou usando o UI-Router v 1.0.0-beta 2 e estava tendo problemas com a parte Params deste código; como $ state.params é um objeto, ele será atualizado para a exibição atual após a função ser resolvida ... eu precisava fazer isso na função de resolução para impedir que o objeto fosse atualizado na exibição atual.
Joe H
100

Para facilitar a leitura, colocarei minha solução (baseada na resposta de stu.salsbury) aqui.

Adicione esse código ao modelo abstrato do seu aplicativo para que ele seja executado em todas as páginas.

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

Mantém o controle das alterações no rootScope. É muito útil.

Federico
fonte
19
Vale a pena armazenar os fromParams também se você realmente deseja redirecionar alguém de volta para onde eles vieram.
Yaron
Eu amo isso e implemento-o em meus aplicativos. Obrigado
Fallenreaper 4/16
Realmente útil ... Ainda mais usando o localStorage, você pode obter o previousState em qualquer lugar.
Georgeos
14

No exemplo a seguir, criei um decorator (é executado apenas uma vez por aplicativo na fase de configuração) e adiciona uma propriedade extra ao $stateserviço, portanto, essa abordagem não adiciona variáveis globais$rootscope e não exige a adição de nenhuma dependência extra a outros serviços $state.

No meu exemplo, eu precisava redirecionar um usuário para a página de índice quando ele já estava conectado e quando ele não deveria redirecioná-lo para a página "protegida" anterior após o login.

Os únicos serviços desconhecidos (para você) que eu uso são authenticationFactory e appSettings:

  • authenticationFactoryapenas administra o login do usuário. Nesse caso, utilizo apenas um método para identificar se o usuário está logado ou não.
  • appSettings são constantes apenas para não usar seqüências de caracteres em todos os lugares. appSettings.states.logine appSettings.states.registercontenha o nome do estado para o URL de login e registro.

Então, em qualquer controller/ serviceetc, você precisa injetar$state serviço e pode acessar os URLs atual e anterior como este:

  • Atual: $state.current.name
  • Anterior: $state.previous.route.name

No console do Chrome:

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

Implementação:

(Eu estou usando angular-ui-router v0.2.17e 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);
CodeArtist
fonte
12

Adicione uma nova propriedade chamada {previous} a $ state em $ 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;
    ...
}

Agora, em qualquer lugar que você precisar, pode usar $ state, você terá disponível anteriormente

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

E use-o assim:

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

Estou com o mesmo problema e encontro a maneira mais fácil de fazer isso ...

//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();
};

(Se você quiser que seja mais testável, injete o serviço $ window no seu controlador e use $ window.history.back ()).

NiRmaL
fonte
é mais complicado quando você tem um menu no seu aplicativo #
417
não obter o seu problema, por favor fornecer mais detalhes
Nirmal
Você perderá $ stateParams se não o inserir no URL, a melhor solução é salvar seus parâmetros anteriores no $ rootScope, você pode obtê-lo e enviá-lo para o $ state para voltar.
precisa saber é o seguinte
Esta resposta é mais fácil para eu implementar.
Lalnuntluanga Chhakchhuak
8

Eu uso uma abordagem semelhante ao que Endy Tjahjono faz.

O que faço é salvar o valor do estado atual antes de fazer uma transição. Vamos ver em um exemplo; imagine isso dentro de uma função executada ao clicar no que quer que desencadeie a transição:

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

A chave aqui é o objeto params (um mapa dos parâmetros que serão enviados ao estado) -> { previousState : { name : $state.current.name } }

Nota: observe que estou apenas "salvando" o atributo name do objeto $ state, porque é a única coisa que preciso para salvar o estado. Mas poderíamos ter todo o objeto de estado.

Em seguida, indique "o que quer que" tenha sido definido assim:

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

Aqui, o ponto principal é o objeto params.

params : {
  previousState: null,
}

Então, dentro desse estado, podemos obter o estado anterior assim:

$state.params.previousState.name
avcajaraville
fonte
6

Aqui está uma solução realmente elegante de Chris Thielen ui-router-extras: $ previousState

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

previousé um objeto que se parece com: { state: fromState, params: fromParams }where fromState é o estado anterior e fromParams são os parâmetros de estado anteriores.

Efraim
fonte
1
Em 16 de maio de 2017, Chris Thielen adicionou um aviso de fim da vida útil ao projeto: ui-router-extras.
Michael R
4

Ok, eu sei que estou atrasado para a festa aqui, mas sou novo em angular. Estou tentando fazer isso se encaixar no guia de estilo de John Papa aqui. Eu queria tornar isso reutilizável, então criei em um bloco. Aqui está o que eu vim com:

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
}
})();

Qualquer crítica a este código só me ajudará; portanto, deixe-me saber onde posso melhorar esse código.

coçar
fonte
2

Se você só precisa dessa funcionalidade e deseja usá-la em mais de um controlador, este é um serviço simples para rastrear o histórico de rotas:

  (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;
  }
})();

onde o 'core' no .module ('core') seria o nome do seu aplicativo / módulo. Exija o serviço como uma dependência do seu controlador e, em seu controlador, você poderá:$scope.routeHistory = RouterTracker.getRouteHistory()

user1970565
fonte
4
Se eu tivesse um botão de navegação na minha página que vá para o estado anterior no histórico, depois de navegar para esse estado anterior, o stateChangeSuccess será acionado, o que o adiciona ao histórico. Portanto, esse código não acabaria resultando em um ciclo interminável de alternância entre duas páginas?
whatsTheDiff
1

Eu acompanho os estados anteriores em $ rootScope , portanto, sempre que precisar, chamarei a linha de código abaixo.

$state.go($rootScope.previousState);

No App.js :

$rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams) {
  $rootScope.previousState = from.name;
});
Naveen Kumar V
fonte
1
Eu acho que é melhor se você salvar não apenas from.name. Dessa forma, você também terá os parâmetros, caso precise fazer algo como $ state.go ($ tootScope.previousState.name, $ tootScope.previousState.params);
abelabbesnabi
0

Para o roteador de interface do usuário (> = 1.0), os eventos StateChange foram preteridos. Para um guia completo de migração, clique em aqui

Para obter o estado anterior do estado atual no 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
fonte
-1

Uma solução realmente simples é apenas editar a string $ state.current.name e cortar tudo, incluindo e após o último '.' - você obtém o nome do estado pai. Isso não funciona se você pular muito entre os estados, porque apenas analisa o caminho atual. Mas se seus estados correspondem a onde você realmente está, então isso funciona.

var previousState = $state.current.name.substring(0, $state.current.name.lastIndexOf('.'))
$state.go(previousState)
Juhana Pietari Lehtiniemi
fonte
1
$state.go('^')vai conseguir isso
james
E os parâmetros do estado?
Tushar Shukla
-2

Você pode retornar o estado desta maneira:

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

Um exemplo:

(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
fonte
2
Essa não é uma boa resposta se você estiver acessando o estado de um estado não pai. Se você deseja apenas o pai do estado atual, ele faz o trabalho.
Maxime Lafarie
1
Não é o mesmo que usar $ state.go ("^")?
Nathan Moinvaziri 11/11