在AngularJS控制器之间共享数据


362

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

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

任何帮助是极大的赞赏。


Answers:


481

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

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和setters提供有限的访问权限:

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/


2
第一个解决方案对我来说最有效-让服务维护对象并且控制器仅处理引用。Mucho谢谢。
johnkeese 2014年

5
$ scope。$ watch方法可以很好地工作,可以从一个作用域进行调用,然后将结果应用于另一个作用域
aclave1 2014年

与其返回类似的对象,return { FirstName: '' };也不返回包含与该对象交互的方法的对象,不应该返回class(function)的实例,这样当您使用该对象时,它具有特定的类型并且不仅一个东西{}” ?
RPDeshaies 2015年

8
请注意,侦听器功能不需要newValue !== oldValue检查,因为如果oldValue和newValue相同,则Angular不会执行该功能。唯一的例外是您是否关心初始值。请参阅:docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
Gautham C.

@tasseKATT,如果我不刷新页面,则效果很好。如果我刷新页面,您能建议我任何解决方案吗?
2015年

71

我宁愿不使用$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变量中的新值来更新视图。在性能方面是否相同?
Mart Dominguez,2015年

4
我不确定这两种方法的性能。据我了解,Angular已经在示波器上运行了一个摘要循环,因此设置其他监视表达式可能会增加Angular的工作量。您必须运行性能测试才能真正找到答案。我只喜欢通过上述答案进行数据传输和共享,而不是通过$ watch表达式进行更新。
尼克2015年

2
这不是$ watch的替代方案。这是在两个Angular对象(在本例中为控制器)之间共享数据的另一种方式,而不必使用$ watch。
Bennick

1
IMO这个解决方案是最好的解决方案,因为它更多地是出于Angular声明性方法的精神,而不是添加$ watch语句,感觉更像jQuery。
JamesG

8
我想知道为什么这不是最投票的答案。毕竟$ watch使代码更复杂
Shyamal Parikh 2015年

11

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

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

每种方法的说明:

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

  2. 使用 $state.go

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

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

6

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

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

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

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


$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })如果您有ui.router,请使用
Nuno Silva

6

有多种方法可以在控制器之间共享数据

  • 角度服务
  • $ broadcast,$ emit方法
  • 家长与孩子之间的沟通
  • $ rootscope

众所周知$rootscope,数据传输或通信不是首选方法,因为它是一个全局范围,可用于整个应用程序

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

在从父数据传输到子控制器的情况下,你可以通过孩子控制器直接访问父数据$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
    });
});

请参阅给定的链接以了解更多信息$broadcast


4

最简单的解决方案:

我已经使用了AngularJS服务

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

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

    };
    return Person;
});

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

//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>

1
对于使用angularJs的新手来说,这是一个非常明显的例子。您将如何共享/传递父子关系中的数据? FirstCtrl是父母并且得到SecondCtrl(孩子)也需要的承诺?我不想打两个API。谢谢。
克里斯22年

1

还有一种不使用$ 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;
});

1

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

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

0

不知道我在哪里选择了这种模式,但是要在控制器之间共享数据并减少$ 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));

正在获取新数据的Controller(即发布者)将执行以下操作。

var someData = yourDataService.getSomeData();

sharedDataEventHub.changeData(someData);

同样使用此新数据的Controller(称为订阅服务器)将执行以下操作...

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

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


使用在控制器之间共享状态的“模型”似乎是最常用的选项。不幸的是,这并不涵盖您将刷新子控制器的情况。如何从服务器中“提取”数据并重新创建模型?您将如何处理缓存失效?
Andrei 2015年

我认为父级和子级控制器的想法有点危险,并且会导致彼此之间过多的依赖关系。但是,当与UI-Router结合使用时,您可以轻松刷新“子”上的数据,尤其是当这些数据是通过状态配置注入时。缓存失效发生在发布者上,由此他们将负责检索数据并进行调用sharedDataEventHub.changeData(newData)
ewahner 2015年

我认为有误会。刷新是指用户从字面上按键盘上的F5键,并使浏览器进行回发。如果发生这种情况时您处于子视图/详细信息视图中,则没有人可以调用“ var someData = yourDataService.getSomeData()”。问题是,您将如何处理这种情况?谁应该更新模型?
安德烈(Andrei)

根据您激活控制器的方式,这可能非常简单。如果您有一个激活方法,则负责最初加载数据的控制器将加载它,然后使用,sharedDataEventHub.changeData(newData)然后依赖于该数据的任何子代或控制器将在其控制器主体中具有以下位置:sharedDataEventHub.onChangeData($scope, function(obj) { vm.myObject = obj.data; });如果使用UI -路由器这可以从状态配置中注入。
ewahner 2015年

这是一个说明我要描述的问题的示例:plnkr.co/edit/OcI30YX5Jx4oHyxHEkPx?p=preview。如果弹出插件,请导航到编辑页面并刷新浏览器,它将崩溃。在这种情况下,谁应该重新创建模型?
安德烈(Andrei)2015年

-3

做到简单(已通过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>


5
我认为这是不赞成的,因为它不是模块化的。dummy对两个控制器都是全局的,而不是通过与$ scope或controllerA的继承或使用服务以更模块化的方式共享。
乍得·约翰逊
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.