HP. HP. - 4 months ago 13
Javascript Question

$ionicHistory.backView has incorrect state when go to previous state manually

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 in
pen.js:64
line, weird things happen. The
$ionicHistory.backView
"thinks" that I got to
app.fact2
from a back button, and it shows
app.fact1
as the previous state (line
pen.js:53
). This is incorrect, right? It should show
app.fact3
as the previous state because I got to
app.fact2
state MANUALLY by clicking the button. I also showed the value from
$timeout
(line
pen.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


  1. How to correct this behavior? Maybe rewrite this delegate or override it somehow?

  2. 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>

Answer

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.

Comments