$ionicHistory.backView has incorrect state when go to previous state manually
There are a lot of questions about history and navigation among the issues open on github.
I guess the navigation is broken and needs to be fixed.
$ionicHistory
keeps track of the view pushing each visited view on a stack.
Actually there are 2 arrays there:
$ionicHistory.viewHistory().views
and
$ionicHistory.viewHistory().histories
I guess the first one is the history of views for the current stack while the second considers all the histories.
Different navigations can have different histories: tabs, sidemenu etc etc, and Ionic should remember each state when you switch from one history to the other.
Reading through their documentation you can find this:
Unlike a traditional browser environment, apps and webapps have parallel independent histories, such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new tab and back, the back button relates not to the previous tab, but to the previous pages visited within that tab.
You can find the currentHistoryId here: $ionicHistory.currentHistoryId()
.
I've changed your example a little bit displaying the 2 arrays when entering the view of the main controller:
.controller('MainCtrl', function($scope, $rootScope, $timeout, $ionicHistory) {
$scope.$on('$ionicView.enter', function(e) {
var history = $ionicHistory.viewHistory();
angular.forEach(history.views, function(view, index){
console.log('views: ' + view.stateName);
});
angular.forEach(history.histories[$ionicHistory.currentHistoryId()].stack, function(view, index){
console.log('history stack:' + view.stateName);
});
});
})
As you can see, the first array views keeps track of all the views you have visited.
If you go back and forth in doesn't add elements if you are displaying a view you've previously visited.
Each view has two properties: backViewId and forwardViewId. These 2 values seems to be part of the view when the elements are added to the collection. They don't change when you navigate.
So, what happens is when you follow the sequence:
Home -> Fact1 -> Fact2 -> Fact3 -> Fact2
Ionic finds the view Fact2
in the collection, gets it's backViewId
(which points to Fact1
) and that's what it will use as a view to go back to.
I didn't some debugging in the code and tried to force the back-view myself but things get messed up.
I guess they've chosen this path cause when you're back to the root - home - the back button should be hidden. Things don't work as expected when you follow the sequence:
Another thing I've noticed is the fact that sometimes views are added to this collection even if the element is already there.
You can try the sequence:
Home -> Fact1 -> Fact2 - Home (button)
As you can see now the back button (in the header) tells you the back view is Fact2 and in fact the console shows the same:
- views: tabs.home
- views: tabs.fact1
- views: tabs.fact2
- views: tabs.home
- history stack:tabs.home
- history stack:tabs.fact1
- history stack:tabs.fact2
- history stack:tabs.home
For some strange reason this time a new view has been added to the collection and the regular pattern has changed.
There's a codepen here with some tests.
HP.
Updated on June 12, 2022Comments
-
HP. almost 2 years
I did a small experiment: http://codepen.io/hawkphil/pen/NqMomm?editors=101
Here is my state flow (click on the buttons):
Home -> Fact1 -> Fact2 -> Fact3 -> Fact2
On each state change, I am showing in
console.log
for$ionicHistory.backView
However, you can see inpen.js:64
line, weird things happen. The$ionicHistory.backView
"thinks" that I got toapp.fact2
from a back button, and it showsapp.fact1
as the previous state (linepen.js:53
). This is incorrect, right? It should showapp.fact3
as the previous state because I got toapp.fact2
state MANUALLY by clicking the button. I also showed the value from$timeout
(linepen.js:59
) just in case it's slow. But it's still incorrect.pen.js:56 stateChangeSuccess pen.js:64 State change from: tabs.home to: tabs.fact1 pen.js:52 $scope.$watch $ionicHistory.backView change detect. newVal: pen.js:53 tabs.home pen.js:58 $timeout after 2 sec $ionicHistory.backView().stateName pen.js:59 tabs.home pen.js:56 stateChangeSuccess pen.js:64 State change from: tabs.fact1 to: tabs.fact2 pen.js:52 $scope.$watch $ionicHistory.backView change detect. newVal: pen.js:53 tabs.fact1 pen.js:58 $timeout after 2 sec $ionicHistory.backView().stateName pen.js:59 tabs.fact1 pen.js:56 stateChangeSuccess pen.js:64 State change from: tabs.fact2 to: tabs.fact3 pen.js:52 $scope.$watch $ionicHistory.backView change detect. newVal: pen.js:53 tabs.fact2 pen.js:58 $timeout after 2 sec $ionicHistory.backView().stateName pen.js:59 tabs.fact2 pen.js:56 stateChangeSuccess pen.js:64 State change from: tabs.fact3 to: tabs.fact2 pen.js:52 $scope.$watch $ionicHistory.backView change detect. newVal: pen.js:53 tabs.fact1 pen.js:58 $timeout after 2 sec $ionicHistory.backView().stateName pen.js:59 tabs.fact1
QUESTION
How to correct this behavior? Maybe rewrite this delegate or override it somehow?
Is there a workaround? As I rely on the correct previous state in order to show/hide something.
JS
angular.module('ionicApp', ['ionic']) .config(function($stateProvider, $urlRouterProvider) { $stateProvider .state('tabs', { url: "/tab", abstract: true, templateUrl: "templates/tabs.html", controller: "MainCtrl" }) .state('tabs.home', { url: "/home", views: { 'home-tab': { templateUrl: "templates/home.html", controller: 'HomeTabCtrl' } } }) .state('tabs.fact1', { url: "/fact1", views: { 'home-tab': { templateUrl: "templates/fact1.html", controller: 'Fact1TabCtrl' } } }) .state('tabs.fact2', { url: "/fact2", views: { 'home-tab': { templateUrl: "templates/fact2.html", controller: 'Fact2TabCtrl' } } }) .state('tabs.fact3', { url: "/fact3", views: { 'home-tab': { templateUrl: "templates/fact3.html", controller: 'Fact3TabCtrl' } } }) .state('tabs.about', { url: "/about", views: { 'about-tab': { templateUrl: "templates/about.html" } } }) .state('tabs.navstack', { url: "/navstack", views: { 'about-tab': { templateUrl: "templates/nav-stack.html" } } }); $urlRouterProvider.otherwise("/tab/home"); }) .controller('MainCtrl', function($scope, $rootScope, $timeout, $ionicHistory) { $scope.$watch(function() { return $ionicHistory.backView() ? $ionicHistory.backView().stateName : null; }, function (newVal, oldVal) { console.log('$scope.$watch $ionicHistory.backView change detect. newVal:'); console.log(newVal); }); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ console.log('stateChangeSuccess'); $timeout(function(){ console.log('$timeout after 2 sec $ionicHistory.backView().stateName'); console.log($ionicHistory.backView().stateName); }, 2000); }); }) .controller('HomeTabCtrl', function($scope, $rootScope) { // console.log('Home'); $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ console.log('State change from: ' + fromState.name + ' to: ' + toState.name); }); }) .controller('Fact1TabCtrl', function($scope) { // console.log('Fact1'); }) .controller('Fact2TabCtrl', function($scope) { // console.log('Fact2'); }) .controller('Fact3TabCtrl', function($scope) { // console.log('Fact3'); });
HTML
<html ng-app="ionicApp"> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"> <title>Navigation Example</title> <link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet"> <script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script> </head> <body> <ion-nav-bar class="bar-positive"> <ion-nav-back-button class="button-icon ion-arrow-left-c"> </ion-nav-back-button> </ion-nav-bar> <ion-nav-view></ion-nav-view> <script id="templates/tabs.html" type="text/ng-template"> <ion-tabs class="tabs-icon-top tabs-positive"> <ion-tab title="Home" icon="ion-home" href="#/tab/home"> <ion-nav-view name="home-tab"></ion-nav-view> </ion-tab> <ion-tab title="About" icon="ion-ios-football" href="#/tab/about"> <ion-nav-view name="about-tab"></ion-nav-view> </ion-tab> </ion-tab> </ion-tabs> </script> <script id="templates/home.html" type="text/ng-template"> <ion-view view-title="Home"> <ion-content class="padding"> <p> <a class="button icon ion-home" href="#/tab/home"> Home</a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact1"> Fact1 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact2"> Fact2 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact3"> Fact3 </a> </p> </ion-content> </ion-view> </script> <script id="templates/fact1.html" type="text/ng-template"> <ion-view view-title="Fact1"> <ion-content class="padding"> <p> <a class="button icon ion-home" href="#/tab/home"> Home</a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact1"> Fact1 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact2"> Fact2 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact3"> Fact3 </a> </p> </ion-content> </ion-view> </script> <script id="templates/fact2.html" type="text/ng-template"> <ion-view view-title="Fact2"> <ion-content class="padding"> <p> <a class="button icon ion-home" href="#/tab/home"> Home</a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact1"> Fact1 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact2"> Fact2 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact3"> Fact3 </a> </p> </ion-content> </ion-view> </script> <script id="templates/fact3.html" type="text/ng-template"> <ion-view view-title="Fact3"> <ion-content class="padding"> <p> <a class="button icon ion-home" href="#/tab/home"> Home</a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact1"> Fact1 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact2"> Fact2 </a> <a class="button icon icon-right ion-chevron-right" href="#/tab/fact3"> Fact3 </a> </p> </ion-content> </ion-view> </script> <script id="templates/about.html" type="text/ng-template"> <ion-view view-title="About"> <ion-content class="padding"> <h3>Create hybrid mobile apps with the web technologies you love.</h3> <p>Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components for building highly interactive apps.</p> <p>Built with Sass and optimized for AngularJS.</p> <p> <a class="button icon icon-right ion-chevron-right" href="#/tab/navstack">Tabs Nav Stack</a> </p> </ion-content> </ion-view> </script> <script id="templates/nav-stack.html" type="text/ng-template"> <ion-view view-title="Tab Nav Stack"> <ion-content class="padding"> <p><img src="http://ionicframework.com/img/diagrams/tabs-nav-stack.png" style="width:100%"></p> </ion-content> </ion-view> </script> </body> </html>