【问题标题】:Share data between AngularJS controllers在 AngularJS 控制器之间共享数据
【发布时间】:2014-03-22 02:43:34
【问题描述】:

我正在尝试跨控制器共享数据。用例是一个多步骤表单,在一个输入中输入的数据稍后会在原始控制器之外的多个显示位置使用。下面和jsfiddle here中的代码。

HTML

<div ng-controller="FirstCtrl">
    <input type="text" ng-model="FirstName"><!-- Input entered here -->
    <br>Input is : <strong>{{FirstName}}</strong><!-- Successfully updates here -->
</div>

<hr>

<div ng-controller="SecondCtrl">
    Input should also be here: {{FirstName}}<!-- How do I automatically updated it here? -->
</div>

JS

// declare the app with no dependencies
var myApp = angular.module('myApp', []);

// make a factory to share data between controllers
myApp.factory('Data', function(){
    // I know this doesn't work, but what will?
    var FirstName = '';
    return FirstName;
});

// Step 1 Controller
myApp.controller('FirstCtrl', function( $scope, Data ){

});

// Step 2 Controller
myApp.controller('SecondCtrl', function( $scope, Data ){
    $scope.FirstName = Data.FirstName;
});

非常感谢任何帮助。

【问题讨论】:

标签: javascript angularjs


【解决方案1】:

正如@MaNn 在接受的答案之一中指出的那样,如果刷新页面,该解决方案将不起作用。

解决方案是使用 localStorage 或 sessionStorage 来临时持久化您想要跨控制器共享的数据。

  1. 要么创建一个 sessionService,其 GET 和 SET 方法对数据进行加密和解密,并从 localStorage 或 sessionStorage 读取数据。因此,现在您可以直接使用此服务通过您想要的任何控制器或服务来读取和写入存储中的数据。这是一种开放且简单的方法
  2. 否则,您创建一个 DataSharing 服务并在其中使用 localStorage - 这样,如果刷新页面,该服务将尝试检查存储空间并通过您在此服务文件中公开或私有的 Getter 和 Setter 进行回复。

【讨论】:

    【解决方案2】:

    最简单的解决方案:

    我使用了 AngularJS 服务

    第 1 步:我创建了一个名为 SharedDataService 的 AngularJS 服务。

    myApp.service('SharedDataService', function () {
         var Person = {
            name: ''
    
        };
        return Person;
    });
    

    第二步:创建两个控制器并使用上面创建的服务。

    //First Controller
    myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
       function ($scope, SharedDataService) {
       $scope.Person = SharedDataService;
       }]);
    
    //Second Controller
    myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
       function ($scope, SharedDataService) {
       $scope.Person = SharedDataService;
       }]);
    

    第 3 步:只需在视图中使用创建的控制器即可。

    <body ng-app="myApp">
    
    <div ng-controller="FirstCtrl">
    <input type="text" ng-model="Person.name">
    <br>Input is : <strong>{{Person.name}}</strong>
    </div>
    
    <hr>
    
    <div ng-controller="SecondCtrl">
    Input should also be here: {{Person.name}}
    </div>
    
    </body>
    

    要查看此问题的有效解决方案,请点击下面的链接

    https://codepen.io/wins/pen/bmoYLr

    .html 文件:

    <!DOCTYPE html>
    <html>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
    
    <body ng-app="myApp">
    
      <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Person.name">
        <br>Input is : <strong>{{Person.name}}</strong>
       </div>
    
    <hr>
    
      <div ng-controller="SecondCtrl">
        Input should also be here: {{Person.name}}
      </div>
    
    //Script starts from here
    
    <script>
    
    var myApp = angular.module("myApp",[]);
    //create SharedDataService
    myApp.service('SharedDataService', function () {
         var Person = {
            name: ''
    
        };
        return Person;
    });
    
    //First Controller
    myApp.controller("FirstCtrl", ['$scope', 'SharedDataService',
        function ($scope, SharedDataService) {
        $scope.Person = SharedDataService;
        }]);
    
    //Second Controller
    myApp.controller("SecondCtrl", ['$scope', 'SharedDataService',
        function ($scope, SharedDataService) {
        $scope.Person = SharedDataService;
    }]);
    
    </script>
    
    
    </body>
    </html>
    

    【讨论】:

    • 对于我们这些使用 angularJs 的新手来说,这是一个非常明显的例子。您将如何共享/传递来自父/子关系的数据? FirstCtrl 是父母并得到SecondCtrl(孩子)也需要的承诺?我不想进行两次 api 调用。谢谢。
    【解决方案3】:

    一个简单的解决方案是让您的工厂返回一个对象并让您的控制器使用对同一对象的引用:

    JS:

    // declare the app with no dependencies
    var myApp = angular.module('myApp', []);
    
    // Create the factory that share the Fact
    myApp.factory('Fact', function(){
      return { Field: '' };
    });
    
    // Two controllers sharing an object that has a string in it
    myApp.controller('FirstCtrl', function( $scope, Fact ){
      $scope.Alpha = Fact;
    });
    
    myApp.controller('SecondCtrl', function( $scope, Fact ){
      $scope.Beta = Fact;
    });
    

    HTML:

    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="Alpha.Field">
        First {{Alpha.Field}}
    </div>
    
    <div ng-controller="SecondCtrl">
    <input type="text" ng-model="Beta.Field">
        Second {{Beta.Field}}
    </div>
    

    演示: http://jsfiddle.net/HEdJF/

    当应用程序变得更大、更复杂且更难测试时,您可能不想以这种方式从工厂公开整个对象,而是提供有限的访问权限,例如通过 getter 和 setter:

    myApp.factory('Data', function () {
    
        var data = {
            FirstName: ''
        };
    
        return {
            getFirstName: function () {
                return data.FirstName;
            },
            setFirstName: function (firstName) {
                data.FirstName = firstName;
            }
        };
    });
    

    使用这种方法,由消费控制器使用新值更新工厂,并观察更改以获取它们:

    myApp.controller('FirstCtrl', function ($scope, Data) {
    
        $scope.firstName = '';
    
        $scope.$watch('firstName', function (newValue, oldValue) {
            if (newValue !== oldValue) Data.setFirstName(newValue);
        });
    });
    
    myApp.controller('SecondCtrl', function ($scope, Data) {
    
        $scope.$watch(function () { return Data.getFirstName(); }, function (newValue, oldValue) {
            if (newValue !== oldValue) $scope.firstName = newValue;
        });
    });
    

    HTML:

    <div ng-controller="FirstCtrl">
      <input type="text" ng-model="firstName">
      <br>Input is : <strong>{{firstName}}</strong>
    </div>
    <hr>
    <div ng-controller="SecondCtrl">
      Input should also be here: {{firstName}}
    </div>
    

    演示: http://jsfiddle.net/27mk1n1o/

    【讨论】:

    • 第一个解决方案对我来说效果最好——让服务维护对象,而控制器只处理引用。非常感谢。
    • $scope.$watch 方法非常适合从一个范围进行休息调用并将结果应用到另一个范围
    • 与其返回return { FirstName: '' }; 之类的对象或返回包含与对象交互的方法的对象,不如返回一个类的实例(function),这样当你使用你的对象,它有一个特定的类型,它不仅仅是一个“对象{}”?
    • 请注意,监听函数不需要newValue !== oldValue 检查,因为如果 oldValue 和 newValue 相同,Angular 不会执行该函数。唯一的例外是如果您关心初始值。见:docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
    • @tasseKATT,如果我不刷新页面,效果很好。如果我刷新页面,你能建议我任何解决方案吗???
    【解决方案4】:

    在控制器之间共享数据有多种方式

    • 角度服务
    • $broadcast, $emit 方法
    • 父子控制器通信
    • $rootscope

    我们知道$rootscope 不是数据传输或通信的首选方式,因为它是一个可用于整个应用程序的全局范围

    对于 Angular Js 控制器之间的数据共享,Angular 服务是最佳实践,例如。 .factory, .service
    对于reference

    如果数据从父控制器传输到子控制器,您可以通过$scope直接访问子控制器中的父数据
    如果您使用的是ui-router,那么您可以使用$stateParmas 传递url 参数,例如idnamekey

    $broadcast 也是在控制器之间从父控制器传输数据到子控制器的好方法,$emit 也是从子控制器向父控制器传输数据的好方法

    HTML

    <div ng-controller="FirstCtrl">
       <input type="text" ng-model="FirstName">
       <br>Input is : <strong>{{FirstName}}</strong>
    </div>
    
    <hr>
    
    <div ng-controller="SecondCtrl">
       Input should also be here: {{FirstName}}
    </div>
    

    JS

    myApp.controller('FirstCtrl', function( $rootScope, Data ){
        $rootScope.$broadcast('myData', {'FirstName': 'Peter'})
    });
    
    myApp.controller('SecondCtrl', function( $rootScope, Data ){
        $rootScope.$on('myData', function(event, data) {
           $scope.FirstName = data;
           console.log(data); // Check in console how data is coming
        });
    });
    

    参考link 了解更多关于$broadcast的信息

    【讨论】:

      【解决方案5】:

      有多种方法可以做到这一点。

      1. 事件 - 已经解释清楚了。

      2. ui 路由器 - 如上所述。

      3. 服务 - 上面显示更新方法
      4. 糟糕 - 观察变化。
      5. 另一个父子方法而不是 emitbrodcast -

      *

      <superhero flight speed strength> Superman is here! </superhero>
      <superhero speed> Flash is here! </superhero>
      

      *

      app.directive('superhero', function(){
          return {
              restrict: 'E',
              scope:{}, // IMPORTANT - to make the scope isolated else we will pollute it in case of a multiple components.
              controller: function($scope){
                  $scope.abilities = [];
                  this.addStrength = function(){
                      $scope.abilities.push("strength");
                  }
                  this.addSpeed = function(){
                      $scope.abilities.push("speed");
                  }
                  this.addFlight = function(){
                      $scope.abilities.push("flight");
                  }
              },
              link: function(scope, element, attrs){
                  element.addClass('button');
                  element.on('mouseenter', function(){
                     console.log(scope.abilities);
                  })
              }
          }
      });
      app.directive('strength', function(){
          return{
              require:'superhero',
              link: function(scope, element, attrs, superHeroCtrl){
                  superHeroCtrl.addStrength();
              }
          }
      });
      app.directive('speed', function(){
          return{
              require:'superhero',
              link: function(scope, element, attrs, superHeroCtrl){
                  superHeroCtrl.addSpeed();
              }
          }
      });
      app.directive('flight', function(){
          return{
              require:'superhero',
              link: function(scope, element, attrs, superHeroCtrl){
                  superHeroCtrl.addFlight();
              }
          }
      });
      

      【讨论】:

        【解决方案6】:

        您可以通过多种方式在控制器之间共享数据

        1. 使用服务
        2. 使用 $state.go 服务
        3. 使用状态参数
        4. 使用根范围

        各方法说明:

        1. 我不打算解释,因为有人已经解释过了

        2. 使用$state.go

            $state.go('book.name', {Name: 'XYZ'}); 
          
            // then get parameter out of URL
            $state.params.Name;
          
        3. $stateparam 的工作方式与$state.go 类似,您将其作为对象从发送者控制器传递并使用状态参数收集到接收者控制器中

        4. 使用$rootscope

          (a) 从子控制器向父控制器发送数据

            $scope.Save(Obj,function(data) {
                $scope.$emit('savedata',data); 
                //pass the data as the second parameter
            });
          
            $scope.$on('savedata',function(event,data) {
                //receive the data as second parameter
            }); 
          

          (b) 从父控制器向子控制器发送数据

            $scope.SaveDB(Obj,function(data){
                $scope.$broadcast('savedata',data);
            });
          
            $scope.SaveDB(Obj,function(data){`enter code here`
                $rootScope.$broadcast('saveCallback',data);
            });
          

        【讨论】:

          【解决方案7】:

          还有另一种不使用$watch的方法,使用angular.copy:

          var myApp = angular.module('myApp', []);
          
          myApp.factory('Data', function(){
          
              var service = {
                  FirstName: '',
                  setFirstName: function(name) {
                      // this is the trick to sync the data
                      // so no need for a $watch function
                      // call this from anywhere when you need to update FirstName
                      angular.copy(name, service.FirstName); 
                  }
              };
              return service;
          });
          
          
          // Step 1 Controller
          myApp.controller('FirstCtrl', function( $scope, Data ){
          
          });
          
          // Step 2 Controller
          myApp.controller('SecondCtrl', function( $scope, Data ){
              $scope.FirstName = Data.FirstName;
          });
          

          【讨论】:

            【解决方案8】:

            我不想为此使用$watch。您可以只分配数据,而不是将整个服务分配给控制器的范围。

            JS:

            var myApp = angular.module('myApp', []);
            
            myApp.factory('MyService', function(){
              return {
                data: {
                  firstName: '',
                  lastName: ''
                }
                // Other methods or objects can go here
              };
            });
            
            myApp.controller('FirstCtrl', function($scope, MyService){
              $scope.data = MyService.data;
            });
            
            myApp.controller('SecondCtrl', function($scope, MyService){
               $scope.data = MyService.data;
            });
            

            HTML:

            <div ng-controller="FirstCtrl">
              <input type="text" ng-model="data.firstName">
              <br>Input is : <strong>{{data.firstName}}</strong>
            </div>
            <hr>
            <div ng-controller="SecondCtrl">
              Input should also be here: {{data.firstName}}
            </div>
            

            您也可以使用直接方法更新服务数据。

            JS:

            // A new factory with an update method
            myApp.factory('MyService', function(){
              return {
                data: {
                  firstName: '',
                  lastName: ''
                },
                update: function(first, last) {
                  // Improve this method as needed
                  this.data.firstName = first;
                  this.data.lastName = last;
                }
              };
            });
            
            // Your controller can use the service's update method
            myApp.controller('SecondCtrl', function($scope, MyService){
               $scope.data = MyService.data;
            
               $scope.updateData = function(first, last) {
                 MyService.update(first, last);
               }
            });
            

            【讨论】:

            • 您不使用显式 watch() 但在内部 AngularJS 会使用 $scope 变量中的新值更新视图。在性能方面是否相同?
            • 我不确定这两种方法的性能。据我了解,Angular 已经在作用域上运行摘要循环,因此设置额外的监视表达式可能会为 Angular 添加更多工作。您必须进行性能测试才能真正找出答案。我只是更喜欢通过上面的答案进行数据传输和共享,而不是通过 $watch 表达式进行更新。
            • 这不是 $watch 的替代品。这是在两个 Angular 对象(在本例中为控制器)之间共享数据的不同方式,而无需使用 $watch。
            • IMO 这个解决方案是最好的解决方案,因为它更符合 Angular 的声明性方法的精神,而不是添加 $watch 语句,这感觉更像 jQuery。
            • 为什么这不是我想知道的投票最多的答案。毕竟 $watch 让代码更复杂
            【解决方案9】:

            不确定我在哪里采用了这种模式,但对于跨控制器共享数据并减少 $rootScope 和 $scope 来说,这非常有用。这让人想起拥有发布者和订阅者的数据复制。希望对您有所帮助。

            服务:

            (function(app) {
                "use strict";
                app.factory("sharedDataEventHub", sharedDataEventHub);
            
                sharedDataEventHub.$inject = ["$rootScope"];
            
                function sharedDataEventHub($rootScope) {
                    var DATA_CHANGE = "DATA_CHANGE_EVENT";
                    var service = {
                        changeData: changeData,
                        onChangeData: onChangeData
                    };
                    return service;
            
                    function changeData(obj) {
                        $rootScope.$broadcast(DATA_CHANGE, obj);
                    }
            
                    function onChangeData($scope, handler) {
                        $scope.$on(DATA_CHANGE, function(event, obj) {
                            handler(obj);
                        });
                    }
                }
            }(app));
            

            获取新数据的控制器,即发布者会做这样的事情..

            var someData = yourDataService.getSomeData();
            
            sharedDataEventHub.changeData(someData);
            

            同样使用这些新数据的控制器,称为订阅者,会做这样的事情......

            sharedDataEventHub.onChangeData($scope, function(data) {
                vm.localData.Property1 = data.Property1;
                vm.localData.Property2 = data.Property2;
            });
            

            这适用于任何情况。因此,当主控制器初始化并获取数据时,它将调用 changeData 方法,然后将其广播给该数据的所有订阅者。这减少了我们的控制器之间的耦合。

            【讨论】:

            • 使用在控制器之间共享状态的“模型”似乎是最常用的选项。不幸的是,这不包括刷新子控制器的场景。您如何从服务器“重新获取”数据并重新创建模型?你将如何处理缓存失效?
            • 我认为父子控制器的想法有点危险,会导致相互依赖太多。但是,当与 UI-Router 结合使用时,您可以轻松地刷新“孩子”上的数据,特别是如果该数据是通过状态配置注入的。缓存失效发生在发布者身上,他们将负责检索数据并调用sharedDataEventHub.changeData(newData)
            • 我觉得有误会。通过刷新,我的意思是用户按键盘上的 F5 键并导致浏览器回发。如果发生这种情况时您处于子/详细信息视图中,则没有人可以调用 "var someData = yourDataService.getSomeData()" 。问题是,您将如何处理这种情况?谁应该更新模型?
            • 根据您激活控制器的方式,这可能非常简单。如果您有一个激活方法,那么负责最初加载数据的控制器将加载它,然后使用sharedDataEventHub.changeData(newData),那么任何依赖该数据的子或控制器将在其控制器主体中的某个位置具有以下内容:@ 987654327@ 如果您使用 UI-Router,则可以从状态配置中注入。
            • 这是一个说明我试图描述的问题的示例:plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview。如果弹出 plunk,导航到编辑页面并刷新浏览器,它将崩溃。在这种情况下,谁应该重新创建模型?
            【解决方案10】:

            做简单的事(用v1.3.15测试):

            <article ng-controller="ctrl1 as c1">
                <label>Change name here:</label>
                <input ng-model="c1.sData.name" />
                <h1>Control 1: {{c1.sData.name}}, {{c1.sData.age}}</h1>
            </article>
            <article ng-controller="ctrl2 as c2">
                <label>Change age here:</label>
                <input ng-model="c2.sData.age" />
                <h1>Control 2: {{c2.sData.name}}, {{c2.sData.age}}</h1>
            </article>
            
            <script>
                var app = angular.module("MyApp", []);
            
                var dummy = {name: "Joe", age: 25};
            
                app.controller("ctrl1", function () {
                    this.sData = dummy;
                });
            
                app.controller("ctrl2", function () {
                    this.sData = dummy;
                });
            </script>
            

            【讨论】:

            • 我认为这被否决了,因为它不是模块化的。 dummy 对两个控制器都是全局的,而不是通过使用 $scope 或 controllerAs 继承或使用服务以更模块化的方式共享。
            【解决方案11】:

            我创建了一个工厂来控制路由路径模式之间的共享范围,因此您可以在用户在同一路由父路径中导航时维护共享数据。

            .controller('CadastroController', ['$scope', 'RouteSharedScope',
                function($scope, routeSharedScope) {
                  var customerScope = routeSharedScope.scopeFor('/Customer');
                  //var indexScope = routeSharedScope.scopeFor('/');
                }
             ])
            

            因此,如果用户转到另一个路由路径,例如“/Support”,路径“/Customer”的共享数据将被自动销毁。但是,如果不是这个用户转到“子”路径,例如“/Customer/1”或“/Customer/list”,则范围不会被破坏。

            您可以在此处查看示例:http://plnkr.co/edit/OL8of9

            【讨论】:

            • 如果你有 ui.router,请使用 $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })
            猜你喜欢
            • 2015-12-15
            • 2013-05-28
            • 2014-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多