4

How do I save URL parameters state throughout lifecycle of application using pushState?

  1. Page load.
  2. Go to "/search" via href
  3. submitSearch() through filter fields where $location.search(fields)
  4. Go to "/anotherPage" via href
  5. Go back to "/search" via href
  6. Search paramters are set back to what they last were.

Is this a built in feature somewhere?

If not what's the best way to go about this?

Dan Kanze
  • 18,485
  • 28
  • 81
  • 134

3 Answers3

4

If you're planning on a mostly single page website through pushState, you might want to get an intimate understanding of $routeProvider (http://docs.angularjs.org/api/ngRoute.%24routeProvider).

To go further down the rabbit hole, I would recommend looking at the ui-router module: (https://github.com/angular-ui/ui-router). $stateProvider (from ui-router) and $routeProvider work very similar, so sometimes the ui-router docs can give insights that you can't find in the poor documentation of the $routeProvider.

I reccomend going through the five page ui-router documentation (https://github.com/angular-ui/ui-router/wiki) page by page.

After all that preamble, here's the practical: you would set up a factory that holds history data and use the controller defined in your $routeProvider/$stateProvider to access and manipulate that data.

Note: the factory is a service. A service is not always a factory. The namespace goes:

angular.module.<servicetype[factory|provider|service]>. 

This post explains the service types: https://stackoverflow.com/a/15666049/2297328. It's important to remember that they're all singletons.

Ex:

var myApp = angular.module("myApp",[]);
myApp.factory("Name", function(){
  return factoryObject
});

The code would look something like:

// Warning: pseudo-code
// Defining states
$stateProvider
  .state("root", {
    url: "/",
    // Any service can be injected into this controller.
    // You can also define the controller separately and use
    // "controller: "<NameOfController>" to reference it.
    controller: function(History){
      // History.header factory
      History.pages.push(History.currentPage);
      History.currentPage = "/";
    }
  })
  .state("search", {
    url: "/search",
    controller: function(History, $routeParams) {
      History.lastSearch = $routeParams
    }
  });

app.factory('<FactoryName>',function(){
  var serviceObjectSingleton = {
    pages: []
    currentPage: ""
    lastSearch: {}
  }
  return serviceObjectSingleton
})

If you're wondering what the difference between $routeProvider and $stateProvider is, it's just that $stateProvider has more features, mainly nested states and views... I think.

Community
  • 1
  • 1
  • Could you provide a plunker for this example? :) – Dan Kanze Jul 19 '13 at 17:01
  • This will not persist when user closes browser right? Not that it's a requirment --- I'm just trying to understand flexibility. Are there options to configure persistence with cookies? – Dan Kanze Jul 19 '13 at 17:04
  • No, these services don't persist. To make the data persist, I'd imagine it's just as easy as assigning the data in the History service to the cookie, and pulling it out on page load... but I haven't opened that can of worms myself, and I haven't figured out the "Angular Way" of it yet. Also: I'd like to plnkr this, but I just don't have the time. – Kirk Werklund Jul 19 '13 at 17:11
2

The easiest way is using cookies, angularjs provides a wrapping service for that. Simply when you go to "/search" save your current URL parameters with "$cookieStore.put()" and once you've back you've got what you need with "$cookieStore.get()".

See the documentation at angularjs cookie store

Akallabeth
  • 343
  • 3
  • 9
  • Do you know if $cookies persist now in 1.1.5? I remember hearing that they didn't have full support for this yet. – Dan Kanze Jul 19 '13 at 15:49
  • Consider that 1.1.x versions are unstable, but cookies are currently supported, I'm using them on my angularjs project (1.1.5). Anyway you can use javascript native "cookies", or javascript native "local storage". If you want to store informations through different pages (without passing information with URL) local storing is the only way to do it (as far as I konw) – Akallabeth Jul 19 '13 at 15:58
1

I made a locationState service, you simply give it the values you want to persist and it stores them in the URL. So you can store all the state you want across all routes in your app.

Use it like this:

angular.module('yourapp')
.controller('YourCtrl', function ($scope, locationState) {
  var size = locationState.get('size');
  ;
  // ... init your scope here
  if (size) {
      $scope.size = size;
  }
  // ...and watch for changes
  $scope.$watch('size', locationState.setter('size'));
}

Here's the code:

// Store state in the url search string, JSON encoded per var
// This usurps the search string so don't use it for anything else
// Simple get()/set() semantics
// Also provides a setter that you can feed to $watch
angular.module('yourapp')
.service('locationState', function ($location, $rootScope) {
    var searchVars = $location.search()
    , state = {}
    , key
    , value
    , dateVal
    ;

    // Parse search string
    for (var k in searchVars) {
        key = decodeURIComponent(k);
        try {
            value = JSON.parse(decodeURIComponent(searchVars[k]));
        } catch (e) {
            // ignore this key+value
            continue;
        }
        // If it smells like a date, parse it
        if (/[0-9T:.-]{23}Z/.test(value)) {
            dateVal = new Date(value);
            // Annoying way to test for valid date
            if (!isNaN(dateVal.getTime())) {
                value = dateVal;
            }
        }
        state[key] = value;
    }

    $rootScope.$on('$routeChangeSuccess', function() {
        $location.search(searchVars);
    });

    this.get = function (key) {
        return state[key];
    };
    this.set = function (key, value) {
        state[key] = value;
        searchVars[encodeURIComponent(key)] = JSON.stringify(value);
        // TODO verify that all the URI encoding etc works. Is there a mock $location?
        $location.search(searchVars);
    };
    this.setter = function (key) {
        var _this = this;
        return function (value) {
            _this.set(key, value);
        };
    };
});
w00t
  • 17,944
  • 8
  • 54
  • 62