How do I handle page refreshing with an AngularJS Single Page Application

前端 未结 3 1150
感动是毒
感动是毒 2020-12-15 11:44

Two problems have troubled me as I have learned angular:

  1. How do I restore state when the user refreshes the page or hits the back button?

相关标签:
3条回答
  • 2020-12-15 12:14

    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}}
    
    0 讨论(0)
  • 2020-12-15 12:26

    There is a simple solution when you're using Node.js to configure your server. You have to organize your routing at the client-side in a way that makes your route links as unique regular expressions. In app.js you'll have:

    (function () {
    var app = angular.module('dataCollector', ['ngRoute']);
    
    app.config(['$routeProvider', '$locationProvider',
    
        function ($routeProvider, $locationProvider) {
    
        $routeProvider
            .when('/', {
                templateUrl: 'home.html',
                controller: 'mainController'
            })
    
            .when('/about', {
                templateUrl: 'about.html',
                controller: 'aboutController'
                })
    
            .when('/login', {
                templateUrl: 'login.html',
                controller: 'loginController'
            });
    
        $locationProvider.html5Mode(true);
    }]);
    
    app.controller('mainController', ['$scope', function ($scope) {
    }]);
    
    })();
    

    In this example all the routes, except '/', can be written in a regular expression schema [A-Za-z]. Having this, the server.js file would be like this:

     var express = require('express');
     var http = require('http');
     var fs = require('fs');
     var path = require('path');
    
     var app = express();
     app.use(express.static('public'));
    
     app.get(/[A-Za-z]/, function (req, res) {
         res.sendFile(path.join(__dirname + '/index.html'));
     });
    
    
     http.createServer(app).listen(80);
    

    Now every GET request that matches the regex [A-Za-z] will make a response with index.html (that are our routes called when refreshing a page e.g. /about). Any other GET request will response with a file from the /public directory (here every file with the extension *.html). This allows to refresh the AngularJS SPA in a proper way.

    0 讨论(0)
  • 2020-12-15 12:28

    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);
                }
            });
    
    0 讨论(0)
提交回复
热议问题