Save state of views in AngularJS

2019-01-17 01:53发布

问题:

I develop a HTML 5 app, and have a view where user comments are loaded. It is recursive: any comment can have clickable sub-comments which are loading in the same view and use the same controller.

Everything is OK. But, when I want to go back, comments are loading again, and I lose my position and sub-comments I loaded.

Can I save the state of the view when I go back? I thought I can maybe use some kind of trick, like: append a new view any time I click on sub-comment and hide the previous view. But I don't know how to do it.

回答1:

Yes, instead of loading and keeping state of your UI inside your controllers, you should keep the state in a service. That means, if you are doing this:

app.config(function($routeProvider){
 $routeProvider.when('/', {
   controller: 'MainCtrl'
 }).when('/another', {
   controller: 'SideCtrl'
 });
});

app.controller('MainCtrl', function($scope){
  $scope.formData = {};   

  $scope./* other scope stuff that deal with with your current page*/

  $http.get(/* init some data */);
});

you should change your initialization code to your service, and the state there as well, this way you can keep state between multiple views:

app.factory('State', function(){
  $http.get(/* init once per app */);

  return {
    formData:{},
  };
});

app.config(function($routeProvider){
 $routeProvider.when('/', {
   controller: 'MainCtrl'
 }).when('/another', {
   controller: 'SideCtrl'
 });
});

app.controller('MainCtrl', function($scope, State){
  $scope.formData = State.formData;   

  $scope./* other scope stuff that deal with with your current page*/
});

app.controller('SideCtrl', function($scope, State){
  $scope.formData = State.formData; // same state from MainCtrl

});

app.directive('myDirective', function(State){
  return {
    controller: function(){
      State.formData; // same state!
    }
  };
});

when you go back and forth your views, their state will be preserved, because your service is a singleton, that was initialized when you first injected it in your controller.

There's also the new ui.router, that has a state machine, it's a low level version of $routeProvider and you can fine grain persist state using $stateProvider, but it's currently experimental (and will ship on angular 1.3)



回答2:

Use a Mediator

If you use a Mediator you'll be decreasing your Out-Degree (Fan-Out) by a factor of 2.

Benefits:

  • You're not coupling your module directly to your server ($http).
  • You're not coupling your module to an additional service (State).
  • Everything you need for state-persistence is right there in your controller ($scope / $scope.$on, $emit, $broadcast).
  • Your Mediator knows more and can direct the application more efficiently.

Downside(?):

  • Your modules need to fire interesting events ($scope.$emit('list://added/Item', $scope.list.id, item))

mediator.js

angular.module('lists.mediator', ['lists', 'list', 'item']).run(function mediate($rootScope){
    var lists = [];
    $rootScope.lists = lists;
    $rootScope.$watch('lists', yourWatcher, true);

    function itemModuleOrControllerStartedHandler(e, itemId, disclose){
        if(!lists.length){
            $http.get(...).success(function(data){
                lists.push.apply(lists, data);
                var item = getItem(lists, itemId);
                disclose(item);  // do not copy object otherwise you'll have to manage changes to stay synchronized
            });
        } else {
            var item = getItem(lists, itemId);
            disclose(item);
        }
    }

    $rootScope.$on('item://started', itemModuleOrControllerStartedHandler);
});

// angular.bootstrap(el, ['lists.mediator'])

item-controller.js

var ItemController = function ItemController($scope, $routeParams){
    var disclosure = $scope.$emit.bind($scope, 'item://received/data', (+new Date()));
    $scope.itemId = $routeParams.id;
    $scope.item = { id: -1, name: 'Nameless :(', quantity: 0 };

    function itemDataHandler(e, timestamp, item){
        $scope.item = item;
    }

    $scope.$on('item://received/data', itemDataHandler);
    $scope.$emit('item://started', $scope.id, disclosure);
};