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.
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)
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);
};