Two problems have troubled me as I have learned angular:
How do I restore state when the user refreshes the page or hits the back button?
How do I share data between scopes belonging to different controllers?
Below I show a simple solution that makes use of client-side session storage. It allows for both the sharing of common data and the automatic restoration of state after a user refreshes the page or hits the back button.
Note: The solution below proved essential to answer the following question:
How do I get the Back Button to work with an AngularJS ui-router state machine?
From then on, the Angular framework kick in and loads the appropriate modules and pages as route changes. When you refresh the page you lose all the state of the application since you are reloading the index. html with all the required dependencies again.
AngularJS is a JavaScript-based front-end web framework based on bidirectional UI data binding and is used to design Single Page Applications. Single Page Applications are web applications that load a single HTML page and only a part of the page instead of the entire page gets updated with every click of the mouse.
Single Page Applications are super easy to deploy in Production, and even to version over time! A single page application is super-simple to deploy if compared to more traditional server-side rendered applications: it's really just one index. html file, with a CSS bundle and a Javascript bundle.
The solution depends on the SessionService
class shown below. The syntax is coffeescript.
SessionService Class
class SessionService
scopes:[]
setStorage:(key, value) ->
scope[key] = value for scope in @scopes
value = if value is undefined then null else JSON.stringify value
sessionStorage.setItem key, value
getStorage:(key)->
sessionValue = sessionStorage.getItem key
if sessionValue == "undefined"
return null
JSON.parse sessionValue
register:(scope)->
for key, value of sessionStorage
scope[key] = if value? and value != "undefined" then JSON.parse(value) else null
@scopes.push scope
scope.$on '$destroy', =>
@scopes = @scopes.filter (s) -> s.$id != scope.$id
clear: ->
@setStorage(key, null) for key of sessionStorage
isAuthenticated: ->
@accessor 'isAuthenticated', value
user:(value=null) ->
@accessor 'user', value
# other storage items go here
accessor:(name, value)->
return @getStorage name unless value?
@setStorage name, value
angular
.module 'app.Services'
.service 'sessionService', SessionService
The SessionService
class defines the isAuthenticated
property (simple bool) and the user
property (a complex object) . The values of these properties are automatically stringified / parsed as they are stored / retrieved using the client-side local sessionStorage
object supplied by javascript.
You add more properties as required. Like $rootScope
you add properties sparingly. Unlike $rootScope
the property values are still available after a page refresh or back button click.
The service allows any number of scopes to be registered with it. When a scope is registered all the stored values in sessionStorage
are automatically assigned to that scope. In this way all the registered scopes always have access to all the session properties.
When a property value is updated, all the registered scopes have their corresponding values updated.
When angular destroys a scope it is automatically removed from the list of registered scopes to save wasting resources.
If a user refreshes the page or hits the back button then the angular application is forced to restart. Normally this would mean you would have to reconstruct your current state. The SessionService
does this for you automatically as each scope will have its values restored from local storage when they are registered during the app initialisation.
So now it is easy to solve the problem of both sharing data between scopes as well as restoring values when the user refreshes or hits the back button.
Here is some sample angular code that shows how to use the SessionService
class.
Register a scope with SessionService in some Controller
angular
.module 'app'
.controller 'mainCtrl', ($scope, $state, session, security) ->
#register the scope with the session service
session.register $scope
#hook up the 'login' method (see security service)
$scope.login = security.login
# check the value of a session property
# it may well be true if the page has been refreshed
if session.isAuthenticated
$state.go('home')
else
$state.go('login')
Set Session values in a service
class SecurityService
@$inject:['$http','sessionService', 'api']
constructor:(@http, @session, @api) ->
login:(username, password) =>
@http.get "#{@api.base}/security/login/credentials/#{username}/#{password}"
.success (user)=>
@session.isAuthenticated = true
@session.user = user
.error (ex)=>
# process error
angular
.module 'app'
.service 'securityService', SecurityService
Use Session values in UI (Jade template)
div(ng-show="isAuthenticated")
div Hello {{user.Name}}
I was faced with the same issue and chose to use angular cookies, since the only state that is not pulled by the template via ng-init is the logged in user state.
I store the user ID in a cookie on login after I've received the user
model from our server and I clear the user ID cookie on logout. Then to recover
the logged in user state on a page refresh or back button event, I hook the
$location
service's $locationChangeStart
event. From my experimentation, this
event is triggered at the point the location is about to change but before the
partial/template has been loaded. This allows the needed state to be loaded just
in time.
I'm not convinced that I don't have a race condition here as
$scope.loadLoggedInUser(...)
uses asynch $http to load the needed state but so far
it has worked reliably for me.
$scope.$on('$locationChangeStart', function() {
$log.debug("locationChangeStart");
if (!$scope.appCtx.models.loggedInUser) {
var userID = $cookies.get("userID");
if (!userID) {
$scope.doLogout();
return;
}
$scope.loadLoggedInUser(userID, true);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With