在AngularJS中的视图之间切换时保持范围模型


152

我正在学习AngularJS。比方说,我有/厂景使用My1Ctrl,和/视图2使用My2Ctrl ; 可以使用标签浏览,每个视图都有自己简单但不同的形式。

当用户离开然后返回到view1时,如何确保未重置以view1形式输入的值?

我的意思是,第二次访问view1如何保持与我离开模型时完全相同的状态?

Answers:


174

我花了一些时间来确定什么是最好的方法。我还想保留状态,即当用户离开页面然后按返回按钮以返回到旧页面时;而不仅仅是将我所有的数据都放到rootscope中

最终结果是为每个控制器提供服务。在控制器中,只要清除了函数和变量,它们就不会在乎。

控制器的服务是通过依赖项注入来注入的。由于服务是单例的,因此它们的数据不会像控制器中的数据那样被破坏。

在服务中,我有一个模型。该模型仅具有数据-无功能-。这样,就可以从JSON来回转换它以持久化它。我使用html5 localstorage进行持久化。

最后,我使用window.onbeforeunload$rootScope.$broadcast('saveState');让所有服务知道它们应保存其状态,并$rootScope.$broadcast('restoreState')让它们知道恢复其状态(用于当用户离开页面并分别按返回按钮返回页面时使用)。

我的userController的示例服务称为userService

app.factory('userService', ['$rootScope', function ($rootScope) {

    var service = {

        model: {
            name: '',
            email: ''
        },

        SaveState: function () {
            sessionStorage.userService = angular.toJson(service.model);
        },

        RestoreState: function () {
            service.model = angular.fromJson(sessionStorage.userService);
        }
    }

    $rootScope.$on("savestate", service.SaveState);
    $rootScope.$on("restorestate", service.RestoreState);

    return service;
}]);

userController示例

function userCtrl($scope, userService) {
    $scope.user = userService;
}

然后视图使用这样的绑定

<h1>{{user.model.name}}</h1>

而在应用模块,运行函数中我处理的广播中的saveState和restoreState

$rootScope.$on("$routeChangeStart", function (event, next, current) {
    if (sessionStorage.restorestate == "true") {
        $rootScope.$broadcast('restorestate'); //let everything know we need to restore state
        sessionStorage.restorestate = false;
    }
});

//let everthing know that we need to save state now.
window.onbeforeunload = function (event) {
    $rootScope.$broadcast('savestate');
};

正如我提到的那样,花了一段时间才达到目的。这是一种非常干净的方法,但是做一些我认为在Angular中开发时很常见的问题是相当工程的。

我希望看到更简单但更干净的方法来处理跨控制器保持状态的方法,包括用户何时离开并返回页面。


10
这说明了页面刷新时如何保留数据,而不是路由更改时如何保留数据,因此它不能回答问题。另外,它不适用于不使用路线的应用。另外,它不会显示sessionStorage.restoreState设置为的位置"true"。有关在页面刷新时保留数据的正确解决方案,请参见此处。要在路线更改时保留数据,请查看carloscarcamo的答案。您所需要做的就是将数据放入服务中。
雨果伍德

2
它确实会在视图之间持久存在,就像您建议的一样,将其存储在服务中。有趣的是,它也可以按照您对window.onbeforeunload的建议以相同的方式保留在页面更改上。谢谢你让我仔细检查。这个答案已有一年多的历史了,它似乎仍然是最简单的角度处理方式。
安东

感谢您回到这里,即使它很旧:)。确保您的解决方案涵盖了视图和页面,但是没有说明哪个代码用于什么目的,并且还不够完整,无法使用。我只是警告读者,避免进一步的问题。如果您有时间进行修改以改善答案,那就太好了。
雨果·伍德

如果在控制器中而不是该怎么办:$scope.user = userService;您将具有以下内容:$scope.name = userService.model.name; $scope.email = userService.model.email;。它会工作还是更改视图中的变量不会在Service中更改它?
体育

2
我认为angular缺乏针对此类常见问题的内置机制
obe6'Mar

22

答案有点迟了,但只是更新了一些最佳做法

jsfiddle

var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
    var userService = {};

    userService.name = "HI Atul";

    userService.ChangeName = function (value) {

       userService.name = value;
    };

    return userService;
});

function MyCtrl($scope, UserService) {
    $scope.name = UserService.name;
    $scope.updatedname="";
    $scope.changeName=function(data){
        $scope.updateServiceName(data);
    }
    $scope.updateServiceName = function(name){
        UserService.ChangeName(name);
        $scope.name = UserService.name;
    }
}

我什至必须在页面导航之间进行此操作(而不是页面刷新)。这样对吗?
FutuToad 2014年

我认为我应该添加部分来完成此答案,updateService控制器销毁时应调用 该部分$scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
Shivendra


8

Angular并没有真正提供您所需要的。为了完成您要执行的操作,我将使用以下附加组件

UI路由器UI路由器附加功能

这两个将为您提供基于状态的路由和粘性状态,您可以在状态之间切换,所有信息都将保存为“保持活动”范围。

检查两者的文档非常简单,ui路由器的其他功能也很好地展示了粘性状态的工作原理。


我已经阅读了文档,但是当用户单击浏览器的后退按钮时找不到如何保留表单输入。您能指出我的具体情况吗?谢谢!
达利博尔

6

我遇到了同样的问题,这就是我所做的:我在同一页面上有一个具有多个视图的SPA(没有ajax),所以这是该模块的代码:

var app = angular.module('otisApp', ['chieffancypants.loadingBar', 'ngRoute']);

app.config(['$routeProvider', function($routeProvider){
    $routeProvider.when('/:page', {
        templateUrl: function(page){return page.page + '.html';},
        controller:'otisCtrl'
    })
    .otherwise({redirectTo:'/otis'});
}]);

对于所有视图,我只有一个控制器,但是问题与问题相同,控制器总是刷新数据,为了避免这种行为,我按照人们上面的建议进行了操作,并为此目的创建了一个服务,然后将其传递到控制器,如下所示:

app.factory('otisService', function($http){
    var service = {            
        answers:[],
        ...

    }        
    return service;
});

app.controller('otisCtrl', ['$scope', '$window', 'otisService', '$routeParams',  
function($scope, $window, otisService, $routeParams){        
    $scope.message = "Hello from page: " + $routeParams.page;
    $scope.update = function(answer){
        otisService.answers.push(answers);
    };
    ...
}]);

现在,我可以从任何视图中调用update函数,传递值并更新模型,不需要使用html5 apis作为持久性数据(在我的情况下,也许在其他情况下使用html5像localstorage等其他API)。


是的,就像魅力一样。我们的数据工厂已经被编码,并且我正在使用ControllerAs语法,因此我将其重写为$ scope控制器,以便Angular可以控制它。实施非常简单。这是我编写的第一个有角度的应用程序。tks
egidiocs 16-4-22

2

服务的替代方法是使用值存储。

在我的应用程序的基础上,我添加了这个

var agentApp = angular.module('rbAgent', ['ui.router', 'rbApp.tryGoal', 'rbApp.tryGoal.service', 'ui.bootstrap']);

agentApp.value('agentMemory',
    {
        contextId: '',
        sessionId: ''
    }
);
...

然后在我的控制器中,我只是引用值存储。如果用户关闭浏览器,我认为它不起作用。

angular.module('rbAgent')
.controller('AgentGoalListController', ['agentMemory', '$scope', '$rootScope', 'config', '$state', function(agentMemory, $scope, $rootScope, config, $state){

$scope.config = config;
$scope.contextId = agentMemory.contextId;
...

1

适用于多个范围以及这些范围内的多个变量的解决方案

该服务基于Anton的回答,但具有更强的可扩展性,可以跨多个作用域工作,并允许在同一作用域中选择多个作用域变量。它使用路由路径来索引每个范围,然后使用范围变量名称来更深一级的索引。

使用以下代码创建服务:

angular.module('restoreScope', []).factory('restoreScope', ['$rootScope', '$route', function ($rootScope, $route) {

    var getOrRegisterScopeVariable = function (scope, name, defaultValue, storedScope) {
        if (storedScope[name] == null) {
            storedScope[name] = defaultValue;
        }
        scope[name] = storedScope[name];
    }

    var service = {

        GetOrRegisterScopeVariables: function (names, defaultValues) {
            var scope = $route.current.locals.$scope;
            var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);
            if (storedBaseScope == null) {
                storedBaseScope = {};
            }
            // stored scope is indexed by route name
            var storedScope = storedBaseScope[$route.current.$$route.originalPath];
            if (storedScope == null) {
                storedScope = {};
            }
            if (typeof names === "string") {
                getOrRegisterScopeVariable(scope, names, defaultValues, storedScope);
            } else if (Array.isArray(names)) {
                angular.forEach(names, function (name, i) {
                    getOrRegisterScopeVariable(scope, name, defaultValues[i], storedScope);
                });
            } else {
                console.error("First argument to GetOrRegisterScopeVariables is not a string or array");
            }
            // save stored scope back off
            storedBaseScope[$route.current.$$route.originalPath] = storedScope;
            sessionStorage.restoreScope = angular.toJson(storedBaseScope);
        },

        SaveState: function () {
            // get current scope
            var scope = $route.current.locals.$scope;
            var storedBaseScope = angular.fromJson(sessionStorage.restoreScope);

            // save off scope based on registered indexes
            angular.forEach(storedBaseScope[$route.current.$$route.originalPath], function (item, i) {
                storedBaseScope[$route.current.$$route.originalPath][i] = scope[i];
            });

            sessionStorage.restoreScope = angular.toJson(storedBaseScope);
        }
    }

    $rootScope.$on("savestate", service.SaveState);

    return service;
}]);

将此代码添加到您的应用模块中的运行函数中:

$rootScope.$on('$locationChangeStart', function (event, next, current) {
    $rootScope.$broadcast('savestate');
});

window.onbeforeunload = function (event) {
    $rootScope.$broadcast('savestate');
};

将restoreScope服务注入到控制器中(以下示例):

function My1Ctrl($scope, restoreScope) {
    restoreScope.GetOrRegisterScopeVariables([
         // scope variable name(s)
        'user',
        'anotherUser'
    ],[
        // default value(s)
        { name: 'user name', email: 'user@website.com' },
        { name: 'another user name', email: 'anotherUser@website.com' }
    ]);
}

上面的示例将$ scope.user初始化为存储的值,否则默认为提供的值并将其保存。如果页面已关闭,刷新或更改了路径,则所有已注册作用域变量的当前值将被保存,并在下次访问该路径/页面时恢复。


当我将其添加到代码中时,出现错误“ TypeError:无法读取未定义的属性'locals'”我该如何解决?@brett
Michael Mahony

您是否正在使用Angular处理路线?我猜是“否”,因为$ route.current似乎是未定义的。
Brett Pennings

是的,angular正在处理我的路由。这是我在路由中的样本:JBenchApp.config(['$ routeProvider','$ locationProvider',函数($ routeProvider,$ locationProvider){$ routeProvider。when('/ dashboard',{templateUrl:' partials / dashboard.html”,控制器:“ JBenchCtrl”})
Michael Mahony

我唯一的其他猜测是,在配置应用程序之前,您的控制器正在运行。无论哪种方式,您都可以通过使用$ location而不是$ route来解决此问题,然后从控制器传入$ scope而不是在路由中访问它。尝试改用gist.github.com/brettpennings/ae5d34de0961eef010c6为您的服务,并在控制器中传入$ scope作为restoreScope.GetOrRegisterScopeVariables的第三个参数。如果这对您有用,我可能只是做出我的新答案。祝好运!
Brett Pennings

1

您可以使用$locationChangeStart事件将先前的值存储$rootScope在服务中或服务中。当您回来时,只需初始化所有先前存储的值即可。这是使用的快速演示$rootScope

在此处输入图片说明

var app = angular.module("myApp", ["ngRoute"]);
app.controller("tab1Ctrl", function($scope, $rootScope) {
    if ($rootScope.savedScopes) {
        for (key in $rootScope.savedScopes) {
            $scope[key] = $rootScope.savedScopes[key];
        }
    }
    $scope.$on('$locationChangeStart', function(event, next, current) {
        $rootScope.savedScopes = {
            name: $scope.name,
            age: $scope.age
        };
    });
});
app.controller("tab2Ctrl", function($scope) {
    $scope.language = "English";
});
app.config(function($routeProvider) {
    $routeProvider
        .when("/", {
            template: "<h2>Tab1 content</h2>Name: <input ng-model='name'/><br/><br/>Age: <input type='number' ng-model='age' /><h4 style='color: red'>Fill the details and click on Tab2</h4>",
            controller: "tab1Ctrl"
        })
        .when("/tab2", {
            template: "<h2>Tab2 content</h2> My language: {{language}}<h4 style='color: red'>Now go back to Tab1</h4>",
            controller: "tab2Ctrl"
        });
});
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular-route.js"></script>
<body ng-app="myApp">
    <a href="#/!">Tab1</a>
    <a href="#!tab2">Tab2</a>
    <div ng-view></div>
</body>
</html>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.