ionic and AngularJS: How to retain state between navigations?

I'm developing my first hybrid mobile app with ionic and AngularJS, and there's this one thing I'm trying to figure out: How do we keep the state (as in GUI) between navigations? Let's say my app has a side menu with these item links:

  1. Search form
  2. All (#/all)
  3. Cats (#/cats)
  4. Dogs (#/dogs)
  5. Err... Cows? (#/cows)
  6. Contact (#/contact)

Items from 2 to 5 trigger requesting server data in an inifite loading manner.

Now let's say from Cats, I go to Dogs and then back to Cats. Since (from my understanding), a brand new Controller instance (and scope) is created with every route change, the app will reload the list of cats from the server. I want the Cats state to be kept, and re-displayed instead.

I've been searching for a solution for this (which I believe should be common enough, just I'm not using the correct search terms). All of the results I've found so far suggest listening to state change or route change kinds of events, and store the object array into localStorage. Though, to some extends, this work, I feel it to be clumsy and just not the way to do - for example, HTML has to be compiled, which can be very slow (notice the "inifinite loading" feature I mentioned earlier, which can increase the number of objects to hundreds), and the viewport will revert back to the top of the app.

So my question is, how do you guys approach this? Is there any sort of navigation that acts like a browser's back and forward buttons?

If I understand correctly what you want, you would need to use an AngularJS Service. For a good explanation, see this article from the Ionic docs:

http://learn.ionicframework.com/formulas/sharing-data-between-views/

In the section "Sharing Data with Services" the answer to your question is right there.

I had the opposite problem, where I wanted it to refresh if the user went back to the same page, but it wasn't. At some point during the last 10 months (between this question being asked and the point I'm writing this), I believe Ionic implemented a page cache to make things like their iOS slide-to-go-back effect work. So your problem may have been fixed by that, but just in case ...

My problem was fixed by adding "cache: false" to the options for each state in the module's .config() section. However, on some pages (or states, I guess) I didn't want them to reload, but I couldn't be sure the page/state would still be in the cache when the user came back to it. So I decided to set up a flag in $rootScope... or, rather, set up an object in $rootScope that contains a flag variable for each controller, and just check the flag to see if that page has already been loaded, and if so, don't run any of the controller code. So for the setup you described, you'd have something like this:

app.js (or whatever .js file defines your module):

angular.module('MyApp', ['ionic'])
.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('Search'  , { cache: true  , url:'/Search'  , templateUrl:'templates/Search.html'  , controller: 'SearchController'  })
    .state('All'     , { cache: true  , url:'/All'     , templateUrl:'templates/All.html'     , controller: 'AllController'     })
    .state('Dogs'    , { cache: true  , url:'/Dogs'    , templateUrl:'templates/Dogs.html'    , controller: 'CatsController'    })
    .state('Cows'    , { cache: true  , url:'/Cows'    , templateUrl:'templates/Cows.html'    , controller: 'DogsController'    })
    .state('Cats'    , { cache: true  , url:'/Cats'    , templateUrl:'templates/Cats.html'    , controller: 'CowsController'    })
    .state('Contact' , { cache: true  , url:'/Contact' , templateUrl:'templates/Contact.html' , controller: 'ContactController' });
  $urlRouterProvider.otherwise('/');
});

And then for the controllers, you could do:

controllers.js

.controller('SearchController', function($scope, $rootScope, $state) {
    // If $rootScope.flags does not already exist, create it as an empty object
    $rootScope.flags = $rootScope.flags || {};

    if($rootScope.flags.SearchPageHasBeenOpenedAlready) {
      console.log("Search page was already opened, not doing anything.");
      return;
    } else {
      // Assuming this is the first controller, I guess you could initialize the flags here...
      // We will initialize them all to false and not set them to true until the code for
      // each controller has run through successfully
      $rootScope.flags.SearchPageHasBeenOpenedAlready  = false;
      $rootScope.flags.AllPageHasBeenOpenedAlready     = false;
      $rootScope.flags.CatsPageHasBeenOpenedAlready    = false;
      $rootScope.flags.DogsPageHasBeenOpenedAlready    = false;
      $rootScope.flags.CowsPageHasBeenOpenedAlready    = false;
      $rootScope.flags.ContactPageHasBeenOpenedAlready = false;
    }

    /* Whatever you normally do in SearchController goes here */

    $rootScope.flags.SearchPageHasBeenOpenedAlready = true;
  }
)

.controller('AllController', function($scope, $rootScope, $state) {
    // If $rootScope.flags does not already exist, create it as an empty object
    $rootScope.flags = $rootScope.flags || {};

    if($rootScope.flags.AllPageHasBeenOpenedAlready) {
      console.log("All page was already opened, not doing anything.");
      return;
    }

    /* Whatever you normally do in AllController goes here */

    $rootScope.flags.AllPageHasBeenOpenedAlready = true;
  }
)

/* 
And so forth until the last one...
*/

.controller('ContactController', function($scope, $rootScope, $state) {
    // If $rootScope.flags does not already exist, create it as an empty object
    $rootScope.flags = $rootScope.flags || {};

    if($rootScope.flags.ContactPageHasBeenOpenedAlready) {
      console.log("Contact page was already opened, not doing anything.");
      return;
    }

    /* Whatever you normally do in ContactController goes here */

    $rootScope.flags.ContactPageHasBeenOpenedAlready = true;
  }
);