【问题标题】:Maintain model of scope when changing between views in AngularJS在AngularJS中的视图之间更改时保持范围模型
【发布时间】:2018-07-07 18:58:49
【问题描述】:

我正在学习 AngularJS。假设我有 /view1 使用 My1Ctrl,而 /view2 使用 My2Ctrl;可以使用选项卡导航到每个视图都有自己简单但不同的形式。

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

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

【问题讨论】:

    标签: javascript angularjs


    【解决方案1】:

    我花了一些时间来找出最好的方法。我还想保持状态,当用户离开页面然后按下后退按钮时,回到旧页面;而不仅仅是将我所有的数据放入 rootscope

    最终的结果是每个控制器都有一个服务。在控制器中,您只拥有您不关心的函数和变量,如果它们被清除的话。

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

    在服务中,我有一个模型。该模型只有数据-没有功能-。这样它就可以从 JSON 来回转换以保持它。我使用 html5 本地存储进行持久化。

    最后我用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;
    }]);
    

    用户控制器示例

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

    然后视图使用这样的绑定

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

    app 模块 中,我在运行函数中处理 saveStaterestoreState广播

    $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 中开发时很常见的问题是相当多的工程。

    我希望看到更简单但更简洁的方式来处理跨控制器保持状态,包括用户离开和返回页面的时间。

    【讨论】:

    • 这里描述的是页面刷新时如何持久化数据,而不是路由变化时如何持久化,所以不回答问题。此外,它不适用于不使用路由的应用程序。另外,它没有显示sessionStorage.restoreState 设置为"true" 的位置。有关在页面刷新时保留数据的正确解决方案,请查看 here。有关路线更改时的持久数据,请查看 carloscarcamo 的答案。您只需将数据放入服务中即可。
    • 它确实会跨视图持续存在,就像您建议的那样,将其存储在服务中。有趣的是,它也会在页面更改中持续存在,就像您对 window.onbeforeunload 的建议一样。谢谢你让我仔细检查。这个答案已经有一年多了,它似乎仍然是使用 Angular 做事的最简单方法。
    • 感谢您回到这里,尽管它已经很旧了:)。当然,您的解决方案涵盖了视图和页面,但没有解释哪些代码用于什么目的,以及it is not complete enough that it can be usable。我只是警告读者避免使用further questions。如果您有时间进行编辑以改进您的答案,那就太好了。
    • 如果在你的控制器中而不是这个:$scope.user = userService; 你有这样的东西:$scope.name = userService.model.name; $scope.email = userService.model.email;。它会起作用还是改变视图中的变量不会在服务中改变它?
    • 我认为 Angular 缺少用于这种常见事物的内置机制
    【解决方案2】:

    答案有点晚了,但刚刚更新了一些最佳实践

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

    【讨论】:

    • 我什至必须在页面导航之间执行此操作(而不是页面刷新)。这是正确的吗?
    • 我想我应该添加部分来完成这个答案,updateService 应该在控制器被销毁时调用 - $scope.$on('$destroy', function() { console.log('Ctrl destroyed'); $scope.updateServiceName($scope.name); });
    【解决方案3】:

    $rootScope 是一个大的全局变量,它适用于一次性的东西或小型应用程序。 如果您想封装您的模型和/或行为(并可能在其他地方重用它),请使用服务。除了提到的 OP 的 google 组帖子,另请参阅https://groups.google.com/d/topic/angular/eegk_lB6kVs/discussion

    【讨论】:

      【解决方案4】:

      Angular 并没有真正提供开箱即用的东西。我会做的是使用以下附加组件来完成您所追求的目标

      UI Router & UI Router Extras

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

      查看两者的文档,因为它非常简单,ui router extras 也有一个很好的demonstration 粘性状态如何工作。

      【讨论】:

      • 我已阅读文档,但在用户单击浏览器后退按钮时找不到如何保留表单输入。请指点我的具体情况好吗?谢谢!
      【解决方案5】:

      我遇到了同样的问题,这就是我所做的: 我有一个 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);
          };
          ...
      }]);
      

      现在我可以从我的任何视图调用更新函数,传递值并更新我的模型,我不需要使用 html5 api 来获取持久性数据(在我的情况下,也许在其他情况下是必要的使用 html5 api,如 localstorage 和其他东西)。

      【讨论】:

      • 是的,工作就像一个魅力。我们的数据工厂已经编码,我使用的是 ControllerAs 语法,所以我将它重写为 $scope 控制器,以便 Angular 可以控制它。实施非常简单。这是我编写的第一个 Angular 应用程序。 tks
      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案7】:

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

        此服务基于 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: Cannot read property 'locals' of undefined” 我该如何解决这个问题? @brett
        • 您是否使用 Angular 来处理您的路线?我猜是“不”,因为 $route.current 似乎是未定义的。
        • 是的,Angular 正在处理我的路由。这是我在路由中的示例: JBenchApp.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { $routeProvider.when('/dashboard', { templateUrl: ' partials/dashboard.html',控制器:'JBenchCtrl'})
        • 我唯一的另一个猜测是您的控制器在配置您的应用程序之前正在运行。无论哪种方式,您都可以通过使用 $location 而不是 $route 然后从控制器传入 $scope 而不是在路由中访问它来解决这个问题。尝试使用 gist.github.com/brettpennings/ae5d34de0961eef010c6 代替您的服务,并将 $scope 作为您控制器中 restoreScope.GetOrRegisterScopeVariables 的第三个参数传入。如果这对你有用,我可能会把它作为我的新答案。祝你好运!
        【解决方案8】:

        您可以使用$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>

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-30
          • 1970-01-01
          • 2014-02-13
          相关资源
          最近更新 更多