如何使用AngularJS单页面应用程序处理页面刷新

时间:2014-01-28 13:56:50

标签: angularjs coffeescript

因为我学会了角色,所以有两个问题困扰我:

  1. 如何在用户刷新页面或点击后退按钮时恢复状态?

  2. 如何在属于不同控制器的作用域之间共享数据?

  3. 下面我展示一个使用客户端会话存储的简单解决方案。它允许在用户刷新页面或点击后退按钮后共享公共数据和自动恢复状态。

    注意:以下解决方案对于回答以下问题至关重要:

    How do I get the Back Button to work with an AngularJS ui-router state machine?

3 个答案:

答案 0 :(得分:5)

解决方案取决于下面显示的SessionService类。语法是coffeescript。

SessionService类

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

SessionService类定义isAuthenticated属性(简单bool)和user属性(复杂对象)。这些属性的值在使用javascript提供的客户端本地sessionStorage对象存储/检索时自动进行字符串化/解析。

您可以根据需要添加更多属性。与$rootScope一样,您可以谨慎添加属性。与$rootScope不同,页面刷新或后退按钮单击后,属性值仍然可用。

该服务允许向其注册任意数量的范围。注册范围时,sessionStorage中的所有存储值都将自动分配给该范围。这样,所有已注册的作用域始终可以访问所有会话属性。

更新属性值时,所有已注册的范围都会更新其对应的值。

当角度破坏范围时,它会自动从注册范围列表中删除,以节省浪费资源。

如果用户刷新页面或点击后退按钮,则强制角度应用程序重新启动。通常这意味着你必须重建你当前的状态。 SessionService会自动为您执行此操作,因为在应用程序初始化期间注册时,每个范围的值都将从本地存储中恢复。

现在很容易解决在作用域之间共享数据以及在用户刷新或点击后退按钮时恢复值的问题。

以下是一些示例角度代码,展示了如何使用SessionService类。

在某些Controller中使用SessionService注册范围

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')

在服务中设置会话值

 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

在UI中使用会话值(Jade模板)

div(ng-show="isAuthenticated")
    div Hello {{user.Name}}

答案 1 :(得分:0)

我遇到了同样的问题,并选择使用角饼干,因为 只有通过ng-init未被模板提取的状态才是登录用户 州。

我在收到用户后,在登录时将用户ID存储在cookie中 来自我们服务器的模型,我在注销时清除用户ID cookie。然后恢复 在页面刷新或后退按钮事件上登录用户状态,我挂钩了 $location服务$locationChangeStart事件。从我的实验来看,这个 事件在该位置即将发生变化但在此之前被触发 部分/模板已加载。这允许仅加载所需的状态 及时。

我不相信我在这里没有竞争条件 $scope.loadLoggedInUser(...)使用asynch $ http来加载所需的状态,但到目前为止 它对我来说可靠。

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

答案 2 :(得分:0)

当您使用Node.js配置服务器时,有一个简单的解决方案。您必须在客户端组织路由,使您的路由链接成为唯一的正则表达式。在app.js中你将拥有:

(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) {
}]);

})();

在此示例中,除'/'之外的所有路由都可以用正则表达式模式[A-Za-z]编写。有了这个,server.js文件将是这样的:

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

现在,与正则表达式GET匹配的每个[A-Za-z]请求都会对index.html做出回复(这是刷新页面时调用的路由,例如/about)。任何其他GET请求都将使用/public目录中的文件进行响应(此处每个文件的扩展名为*.html)。这允许以适当的方式刷新AngularJS SPA。