Ionic-framework: bug: $ioniocHistory.clearCache does always work

Created on 11 Feb 2016  路  14Comments  路  Source: ionic-team/ionic-framework

Type: bug

Ionic Version: 1.x

Platform: all

This is the same issue that was discussed in #2939, but it is still present in 1.2.4. There is a workaround in that ticket, but this is clearly a bug, and at the very least deserves a documentation update.

The root of the problem is that if $ionicHistory.clearCache() is called between views, it doesn't clear the cache. For example this code will not clear the cache, because the call happens before the new ion-view is loaded.

``` $state.go('login', {}, {reload: true}) .then(function(){ $ionicHistory.clearCache(); })


``````

Most helpful comment

Well this is an old issue, I will explain what really happens and how to solve it:

P.S: If you want to go straight to SOLUTION just scroll down right away.

The code of $ionicHistory.clearCache():
clearCache: function(stateIds) { return $timeout(function() { $ionicNavViewDelegate._instances.forEach(function(instance) { instance.clearCache(stateIds); }); }); },
So, as you can see, it takes 1 parameter cllaed stateIds which is an array of stateId. Indeed i struggled to find out that stateId is nothing more than stateName.

So, let's go deeper. The code of $ionicNavView.clearCache which is used in the line above "instance.clearCache(stateIds)" is:

`self.clearCache = function(stateIds) {
var viewElements = $element.children();
var viewElement, viewScope, x, l, y, eleIdentifier;

for (x = 0, l = viewElements.length; x < l; x++) {
  viewElement = viewElements.eq(x);

  if (stateIds) {
    eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);

    for (y = 0; y < stateIds.length; y++) {
      if (eleIdentifier === stateIds[y]) {
        $ionicViewSwitcher.destroyViewEle(viewElement);
      }
    }
    continue;
  }

  if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
    $ionicViewSwitcher.destroyViewEle(viewElement);

  } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
    viewScope = viewElement.scope();
    viewScope && viewScope.$broadcast('$ionicView.clearCache');
  }

}

};
`

And as you can see in the code, this clearCache DOES NOT CLEAR ALL CACHES, instead, it destroy all cached views that matches a value in the stateIds array. If there's no parameter IT JUST DESTROY THE ACTUAL VIEW.

So the solution for this, using just the Ionic way is to call $ionicHistory.clearCache() with all your state names in an array as parameter.

E.g: SOLUTION
$ionicHistory.clearCache(['login', 'map', 'home']);
I cannot belive any Ionic developer didnt dug into the code before, or missed this simple datail. There's a lot of people who's aching for this.

Just to make it crystal clear, i want to point out where the bug itself is (if we can call it bug), maybe can be handy for devs:

self.clearCache = function(stateIds){

[...]

     var viewElements = $element.children();

}

What the whole function does is basically:

  • Get all elements using JQLite
  • Loop the elements
  • Check if an element equals one in the StateIds array and destroy it; go to next element.
  • Check if element in the loop is cached or active, and in both true cases destroy it

I wont dig deeper into this but debugging it i could see that the elements gotten from var viewElements = $element.children(); is not an array of all your views content, not even the cached ones, intentionally or not it does not loop through out all your states to clear all those that matches 'ACTIVE' or 'CACHED'. If you want it to loop through ALL your states and destroy all cached views and data you need to explicity pass the stateIds array parameter.

Besides that there's another strange behavior, because when i was debugging it i saw when the var viewElements array was filled up with 2 elements, and these 2 elements were from the same state, one resolved to 'CACHED' another resolver to 'ACTIVE', even resolving to the 2 types used in the if conditions the cache was not cleared at all.

I personally think that this is somekind wrong implemented or is wide used wrongly. The fact is that there's a lot of people cracking their heads on this and devs don't even give this simple explanation.

All 14 comments

I'm using ionic 1.2.4 from bower (1.2.4-nightly-1917)

I agree with OP, the cache-related things are pretty confusing and not very reliable.

There's been a thread here and one guy says that doing your things inside a promise resolution callback of clearCache() works for him

The documentation says:

clearCache()

Removes all cached views within every ionNavView.
This both removes the view element from the DOM, and destroy it鈥檚 scope.

Returns:
promise

We have a standard "logout-user-and-clear-everything" scenario, and the ionic caching hit us there.

I don't want to disable caching for the views, but in logout action, I want to clear all the data from memory, localstorage etc. obviously, for privacy reasons.

My code goes like this:

    logout: function() {
      var deferred = $q.defer();

      function logoutCb() { .. }

      $http.post(LOGOUT_SERVER_ACTION).finally(logoutCb);
      return deferred.promise;
    }

I tried following variations of logoutCb:

function logoutCb1() {
    $ionicHistory.clearHistory();
    $ionicHistory.clearCache();
    deferred.resolve();
}

function logoutCb2() {
    $ionicHistory.clearHistory();
    $ionicHistory.clearCache().then(function() {
      deferred.resolve();
    });
}

function logoutCb3() {
    $timeout(function() {
        $ionicHistory.clearHistory();
        $ionicHistory.clearCache();
    }, 1500);
    deferred.resolve(); // resolve logout immediately, can't wait 1.5s...
}

Then on login page another user logs in, and in AfterLoginController I have

$scope.$on("$ionicView.enter", function(scopes, states) {
    console.log(states.fromCache);
});

Only with logoutCb3 I get states.fromCache === false, in other cases I see it being true and I can see the data of the previously logged out user.

Hello, I am trying to resolve this issue in our code. I have created this codepen to recreate it.

I am not able to recreate the issue.

Can you help me recreate your issue in the codepen?

The part of the code that does the logout does looks like this:

.controller("loggedInController", function($ionicHistory, $scope, $state, $stateParams){

  $scope.username = $stateParams.username;
  $scope.password = $stateParams.password;

  $scope.logout = function(){
    // first, clear the history
    $ionicHistory.clearHistory();
    // clear the cache before you navigate to a page
    $ionicHistory.clearCache().then(function(){
      $state.go("app.login");
    });
  }
})

Hello! As it has been a while since there was any activity on this issue i will be closing it for now, but feel free to comment and i will happily reopen!

This is still an outstanding bug. I think calling 6 days "a while" is a bit of a stretch.

Hello @AgDude ! Ill be happy to reopen this issue! Do you think you could give @danbucholtz some help with recreating your issue in the codepen he made? We would love to help you with your issue. Thanks!

Hi @AgDude, can you provide a code pen to recreate your issue? Does my explanation not work?

If you clear the history, then clear the cache, then execute your logout, you should be good to go.

Please let me know if there is anything else I can help you with. I am going to close this but will re-open if needed.

Thanks,
Dan

@danbucholtz, I have slightly modified your codepen here: http://codepen.io/AgDude/pen/YqavWa

I have reordered state.go and clearCache.

  1. Login in with a username and password.
  2. Log out
  3. Note that the username and password are still in the inputs, indicating the scope was not destroyed.

I have the same problem I believe. I have implemented a mechanism that refreshes the entire application data (from rest service) if the SignalR connections has been terminated and re-established.
When this occurs, future changes in the $scope variables are not visible on the screen, until I go back to main screen, to another controller, back to main, and then back to the first one (this time controller obviously gets recreated).
$ionicHistory methods clearCache and clearHistory have not helped.

some further notices: although clearHistory was called, the back button did not dissappear
using cache:false in state definition helps, but obviously I lose caching of the view entirely

additionally, i noticed that only beforeLeave event is called, while
$scope.$on("$ionicView.afterLeave",...) and $scope.$on("$ionicView.leave",...) do not get invoked!? :O

I was using Ionic 1.3.0, upgrade to 1.3.1 solved the issue with view events not being fired.
Additionally it seems that by executing clearCache method in $timeout after 1000 ms of afterLeave event, does clear the previous view (300 is too little in my experience).
My problem is that I want to remain on the same view, only rebind the $scope variable (updated in the background) to the view, but that does not happen.

Is it possible to clear the cache of the current view and reload it from scratch?

My problem is that I want to remain on the same view, only rebind the $scope variable (updated in the background) to the view, but that does not happen.

@gregor-srdic I'm having this exact same issue. Were you able to come up with any workaround?

@JPatricio I have disabled caching for the particular view in the routeConfig file:

.state('my-state-config, {
      url: ...,
      templateUrl: ...,
      controller: ...,
      cache: false
}

Im clearing cache in my login controller but when a user logs in again the forward views are not calling the controllers again

Well this is an old issue, I will explain what really happens and how to solve it:

P.S: If you want to go straight to SOLUTION just scroll down right away.

The code of $ionicHistory.clearCache():
clearCache: function(stateIds) { return $timeout(function() { $ionicNavViewDelegate._instances.forEach(function(instance) { instance.clearCache(stateIds); }); }); },
So, as you can see, it takes 1 parameter cllaed stateIds which is an array of stateId. Indeed i struggled to find out that stateId is nothing more than stateName.

So, let's go deeper. The code of $ionicNavView.clearCache which is used in the line above "instance.clearCache(stateIds)" is:

`self.clearCache = function(stateIds) {
var viewElements = $element.children();
var viewElement, viewScope, x, l, y, eleIdentifier;

for (x = 0, l = viewElements.length; x < l; x++) {
  viewElement = viewElements.eq(x);

  if (stateIds) {
    eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);

    for (y = 0; y < stateIds.length; y++) {
      if (eleIdentifier === stateIds[y]) {
        $ionicViewSwitcher.destroyViewEle(viewElement);
      }
    }
    continue;
  }

  if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
    $ionicViewSwitcher.destroyViewEle(viewElement);

  } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
    viewScope = viewElement.scope();
    viewScope && viewScope.$broadcast('$ionicView.clearCache');
  }

}

};
`

And as you can see in the code, this clearCache DOES NOT CLEAR ALL CACHES, instead, it destroy all cached views that matches a value in the stateIds array. If there's no parameter IT JUST DESTROY THE ACTUAL VIEW.

So the solution for this, using just the Ionic way is to call $ionicHistory.clearCache() with all your state names in an array as parameter.

E.g: SOLUTION
$ionicHistory.clearCache(['login', 'map', 'home']);
I cannot belive any Ionic developer didnt dug into the code before, or missed this simple datail. There's a lot of people who's aching for this.

Just to make it crystal clear, i want to point out where the bug itself is (if we can call it bug), maybe can be handy for devs:

self.clearCache = function(stateIds){

[...]

     var viewElements = $element.children();

}

What the whole function does is basically:

  • Get all elements using JQLite
  • Loop the elements
  • Check if an element equals one in the StateIds array and destroy it; go to next element.
  • Check if element in the loop is cached or active, and in both true cases destroy it

I wont dig deeper into this but debugging it i could see that the elements gotten from var viewElements = $element.children(); is not an array of all your views content, not even the cached ones, intentionally or not it does not loop through out all your states to clear all those that matches 'ACTIVE' or 'CACHED'. If you want it to loop through ALL your states and destroy all cached views and data you need to explicity pass the stateIds array parameter.

Besides that there's another strange behavior, because when i was debugging it i saw when the var viewElements array was filled up with 2 elements, and these 2 elements were from the same state, one resolved to 'CACHED' another resolver to 'ACTIVE', even resolving to the 2 types used in the if conditions the cache was not cleared at all.

I personally think that this is somekind wrong implemented or is wide used wrongly. The fact is that there's a lot of people cracking their heads on this and devs don't even give this simple explanation.

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

GeorgeAnanthSoosai picture GeorgeAnanthSoosai  路  3Comments

BilelKrichen picture BilelKrichen  路  3Comments

manucorporat picture manucorporat  路  3Comments

brandyscarney picture brandyscarney  路  3Comments

SebastianGiro picture SebastianGiro  路  3Comments