AngularJS:如何在控制器之间传递变量?


326

我有两个Angular控制器:

function Ctrl1($scope) {
    $scope.prop1 = "First";
}

function Ctrl2($scope) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

我无法使用Ctrl1内部,Ctrl2因为它是未定义的。但是,如果我尝试像这样传递它…

function Ctrl2($scope, Ctrl1) {
    $scope.prop2 = "Second";
    $scope.both = Ctrl1.prop1 + $scope.prop2; //This is what I would like to do ideally
}

我得到一个错误。有谁知道如何做到这一点?

在做

Ctrl2.prototype = new Ctrl1();

也失败。

注意:这些控制器不相互嵌套。


有很多方法,但是最好的方法是有角度的手表。总是在使用框架时最好的方法是使用她自己的方法进行工作,不要忘记这一点
pejman

我发现此博客非常有帮助。博客
Black Mamba

Answers:


503

在多个控制器之间共享变量的一种方法是创建服务并将其注入到要使用它的任何控制器中。

简单服务示例:

angular.module('myApp', [])
    .service('sharedProperties', function () {
        var property = 'First';

        return {
            getProperty: function () {
                return property;
            },
            setProperty: function(value) {
                property = value;
            }
        };
    });

在控制器中使用服务:

function Ctrl2($scope, sharedProperties) {
    $scope.prop2 = "Second";
    $scope.both = sharedProperties.getProperty() + $scope.prop2;
}

这个博客对此进行了很好的描述(特别是第2课)。

我发现,如果要跨多个控制器绑定到这些属性,则如果绑定到对象的属性而不是原始类型(布尔,字符串,数字)来保留绑定的引用,则效果更好。

示例:var property = { Property1: 'First' };代替var property = 'First';


更新:为了(希望)使事情变得更清楚,这是一个小提琴,其中显示了以下示例:

  • 绑定到共享值的静态副本(在myController1中)
    • 绑定到原语(字符串)
    • 绑定到对象的属性(保存到范围变量)
  • 绑定到在更新值时更新UI的共享值(在myController2中)
    • 绑定到返回原语(字符串)的函数
    • 绑定到对象的属性
    • 双向绑定到对象的属性

5
在这种情况下-当sharedProperties.getProperty()更改值时,Ctrl2的范围将如何“知道”?
OpherV

5
如果您希望UI每次属性更改时都进行更新both,则可以将其更改为一个函数,并将在角度摘要过程中对其进行调用/重新评估。有关示例,请参见此小提琴。同样,如果您绑定到对象的属性,则可以直接在视图中使用它,并且该数据将随着此示例的更改而更新。
Gloopy

11
如果要检测控制器中的更改并对更改做出反应,可以选择将该getProperty()函数添加到范围中,并使用$ scope。$ watch,本例所示。希望这些例子对您有所帮助!
Gloopy

1
这里存在一个问题,因为服务应该是无状态的。在服务内部存储属性是错误的(但很方便)。我开始使用$ cacheFactory读取和写入数据。我使用与Gloopy几乎相同的服务,但现在不在缓存中存储状态,而是将其存储在该服务中。首先创建一个缓存服务:angular.module('CacheService',['ng']).factory('CacheService',function($ cacheFactory){return $ cacheFactory('CacheService');}); 包含在您的app.js中,将其注入服务中,按如下方式使用它:return CacheService.get(key); 或CacheService.put(key,value);
Jordan Papaleo

4
尝试了解该答案的使用方式和原因,.service而不是.factory如Angular文档中所述。当文档使用其他方法时,为什么对这个答案的评价如此之高?
pspahn 2015年

44

我喜欢通过简单的例子来说明简单的事情:)

这是一个非常简单的Service示例:


angular.module('toDo',[])

.service('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  this.dataObj = _dataObj;
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

这里的jsbin

这是一个非常简单的Factory示例:


angular.module('toDo',[])

.factory('dataService', function() {

  // private variable
  var _dataObj = {};

  // public API
  return {
    dataObj: _dataObj
  };
})

.controller('One', function($scope, dataService) {
  $scope.data = dataService.dataObj;
})

.controller('Two', function($scope, dataService) {
  $scope.data = dataService.dataObj;
});

这里的jsbin


如果这太简单,这是一个更复杂的示例

请参阅此处的答案以获取相关的最佳做法评论


1
是的,我同意你的看法。始终尝试使事情变得简单。
Evan Hu

var _dataObj = {};返回直接引用时声明的意义是什么?那不是私人的。在第一个示例中,您可以这样做this.dataObj = {};;在第二个示例中,return { dataObj: {} };它是无用的变量声明恕我直言。
TJ 2015年

@TJ关键是要在其他组件之间共享此变量。这是说明共享概念的基本示例。变量在块内是私有的,然后使用显示模式将其公开为公共变量。这样,在保存变量和使用变量之间就存在责任分离。
德米特里·扎伊采夫

@DmitriZaitsev您说的是“简单示例”,但是除非您正确地显示了如何使用私有状态,否则您只会使人们感到困惑。只要返回直接引用,示例中就没有私有状态。
TJ

@TJ我看不到任何令人困惑的地方。私有变量可以由模块公开。随时写一个更好的答案。
Dmitri Zaitsev

26

---我知道这个答案不是针对这个问题的,但是我希望那些读过这个问题并希望处理工厂等服务的人避免这样做的麻烦----

为此,您将需要使用服务或工厂。

这些服务是在非嵌套控制器之间共享数据的最佳实践

关于数据共享的这个主题的一个很好的注释是如何声明对象。我很不幸,因为在我读到它之前就陷入了AngularJS陷阱,我感到非常沮丧。因此,让我帮助您避免这种麻烦。

我从《 ng书:关于AngularJS的完整书》中读到,在控制器中作为裸数据创建的AngularJS ng模型是错误的!

$ scope元素应这样创建:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // best practice, always use a model
  $scope.someModel = {
    someValue: 'hello computer'
  });

而且不是这样的:

angular.module('myApp', [])
.controller('SomeCtrl', function($scope) {
  // anti-pattern, bare value
  $scope.someBareValue = 'hello computer';
  };
});

这是因为建议将DOM(html文档)包含为

<div ng-model="someModel.someValue"></div>  //NOTICE THE DOT.

如果希望子控制器能够从父控制器更改对象,这对于嵌套控制器非常有用。

但是在您的情况下,您不需要嵌套作用域,但是有一个相似的方面可以将对象从服务获取到控制器。

假设您有服务“工厂”,并且在返回空间中有一个objectA,其中objectB包含objectC。

如果您想从您的控制器中将objectC放入您的作用域中,那就错了:

$scope.neededObjectInController = Factory.objectA.objectB.objectC;

那行不通... 而是只使用一个点。

$scope.neededObjectInController = Factory.ObjectA;

然后,在DOM中,您可以从objectA调用objectC。这是与工厂有关的最佳实践,最重要的是,它将有助于避免意外的和不可捕获的错误。


2
我认为这是一个很好的答案,但是很难消化。
pspahn 2015年

17

不使用$ rootScope创建服务的解决方案:

要在应用程序控制器之间共享属性,可以使用Angular $ rootScope。这是共享数据的另一种选择,使人们知道它。

跨控制器共享某些功能的首选方法是服务,您可以使用$ rootscope来读取或更改全局属性。

var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = true;
}]);

app.controller('Ctrl2', ['$scope','$rootScope',
  function($scope, $rootScope) {
    $rootScope.showBanner = false;
}]);

在模板中使用$ rootScope(使用$ root访问属性):

<div ng-controller="Ctrl1">
    <div class="banner" ng-show="$root.showBanner"> </div>
</div>

5
此时,您正在使用全局范围的变量,这与AngularJS的概念不同,即在本地范围内对其各种结构中的所有内容进行范围限定。添加全局变量文件将实现相同的目的,并使查找变量最初定义的位置更加容易。无论哪种方式,都不建议。
Organiccat 2014年

4
@Organiccat-我了解您的关注,这就是为什么我已经提到首选的方式将是服务,这毫无疑问。但是ya angular也提供这种方式。您要如何管理自己的全球业务。我遇到了这种方法最适合我的情况。
桑耶夫2014年

8

上面的示例就像一个魅力。我只是做了修改,以防万一我需要管理多个值。我希望这有帮助!

app.service('sharedProperties', function () {

    var hashtable = {};

    return {
        setValue: function (key, value) {
            hashtable[key] = value;
        },
        getValue: function (key) {
            return hashtable[key];
        }
    }
});

1
我还使用服务创建了一个示例,以跨不同的控制器共享数据。我希望你们喜欢。 jsfiddle.net/juazammo/du53553a/1
Juan Zamora

1
即使有效,这通常也是的语法.factory。一.service,应使用“如果你定义你的服务作为类型/类”按docs.angularjs.org/api/auto/service/$provide#service
德米特里·扎伊采夫

1
德米特里(Dmitri),你是对的,但是从我的角度来看,Angular的员工刚刚改变了我在服务(立面)和工厂之间的概念....哦....
Juan Zamora 2014年

1
如果我错了,请纠正我,服务旨在返回可以是对象或值的值。工厂旨在创建对象。我认为实际上是可以返回某些功能的功能的界面。包括从工厂调用功能。再次,我进入了基本概念,即对我来说这是什么,而不是角度角度的实际含义。(摘要工厂dofactory.com/net/abstract-factory-design-pattern)和适配器方法是我将作为服务公开的内容
Juan Zamora

1
在此处查看适配器模式dofactory.com/net/adapter-design-pattern
Juan Zamora

6

我倾向于使用价值观,让任何人高兴地讨论为什么这是一个坏主意。

var myApp = angular.module('myApp', []);

myApp.value('sharedProperties', {}); //set to empty object - 

然后根据服务注入值。

在ctrl1中设置:

myApp.controller('ctrl1', function DemoController(sharedProperties) {
  sharedProperties.carModel = "Galaxy";
  sharedProperties.carMake = "Ford";
});

并从ctrl2访问:

myApp.controller('ctrl2', function DemoController(sharedProperties) {
  this.car = sharedProperties.carModel + sharedProperties.carMake; 

});

这与使用服务有何不同?
dopatraman

5

下面的示例说明如何在同级控制器之间传递变量,以及如何在值更改时采取措施。

用例示例:边栏中有一个过滤器,可更改另一个视图的内容。

angular.module('myApp', [])

  .factory('MyService', function() {

    // private
    var value = 0;

    // public
    return {
      
      getValue: function() {
        return value;
      },
      
      setValue: function(val) {
        value = val;
      }
      
    };
  })
  
  .controller('Ctrl1', function($scope, $rootScope, MyService) {

    $scope.update = function() {
      MyService.setValue($scope.value);
      $rootScope.$broadcast('increment-value-event');
    };
  })
  
  .controller('Ctrl2', function($scope, MyService) {

    $scope.value = MyService.getValue();

    $scope.$on('increment-value-event', function() {    
      $scope.value = MyService.getValue();
    });
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<div ng-app="myApp">
  
  <h3>Controller 1 Scope</h3>
  <div ng-controller="Ctrl1">
    <input type="text" ng-model="value"/>
    <button ng-click="update()">Update</button>
  </div>
  
  <hr>
  
  <h3>Controller 2 Scope</h3>
  <div ng-controller="Ctrl2">
    Value: {{ value }}
  </div>  

</div>


4

我想对这个问题做出贡献,指出在控制器甚至指令之间共享数据的推荐方法是使用服务(工厂),正如已经指出的那样,但是我也想提供一个如何做到这一点的实际例子。

这是可以使用的插件: http ://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=info

首先,创建您的服务,它将拥有您的共享数据

app.factory('SharedService', function() {
  return {
    sharedObject: {
      value: '',
      value2: ''
    }
  };
});

然后,只需将其注入您的控制器并在您的示波器上获取共享数据:

app.controller('FirstCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('SecondCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

app.controller('MainCtrl', function($scope, SharedService) {
  $scope.model = SharedService.sharedObject;
});

您也可以为您的指令执行此操作,其工作方式相同:

app.directive('myDirective',['SharedService', function(SharedService){
  return{
    restrict: 'E',
    link: function(scope){
      scope.model = SharedService.sharedObject;
    },
    template: '<div><input type="text" ng-model="model.value"/></div>'
  }
}]);

希望这个实用而干净的答案对某人有所帮助。


3

您可以通过服务或工厂来做到这一点。它们之间基本相同,只是存在一些核心差异。我发现thinkster.io上的这种解释最容易理解。简单,要点和有效。


1
“您可以通过服务或工厂来做到这一点” -如何..?OP所要执行的操作是...请在stackoverflow本身中发布完整答案,而不是链接到外部资源,否则链接可能会超时。
TJ 2015年

2

您是否也可以将该属性作为合并范围的父级?

$scope.$parent.property = somevalue;

我并不是说这是正确的,但是可以。


3
作者说NOTE: These controllers are not nested inside each other.。如果这些是嵌套控制器或共享同一父代的控制器,则可以使用,但我们不能期望如此。
克里斯·福斯特

2
$parent如果可以避免,通常依靠它是一个坏习惯。设计良好的可重用组件不应了解其父级。
Dmitri Zaitsev 2014年

2

嗯,把这些新东西作为另一种选择。它是本地存储,可在有角度的地方工作。别客气。(但真的,谢谢那个家伙)

https://github.com/gsklee/ngStorage

定义默认值:

$scope.$storage = $localStorage.$default({
    prop1: 'First',
    prop2: 'Second'
});

访问值:

$scope.prop1 = $localStorage.prop1;
$scope.prop2 = $localStorage.prop2;

存储值

$localStorage.prop1 = $scope.prop1;
$localStorage.prop2 = $scope.prop2;

请记住在应用程序中注入ngStorage,在控制器中注入$ localStorage。


1
这解决了另一个问题-持久存储。它不是针对该问题的可伸缩解决方案,因为它使您的代码具有副作用,例如,使用名称冲突等漏洞来修改本地存储对象,从而使代码泄漏。
德米特里·扎伊采夫

1

有两种方法可以做到这一点

1)使用获取/设置服务

2) $scope.$emit('key', {data: value}); //to set the value

 $rootScope.$on('key', function (event, data) {}); // to get the value

1

第二种方法:

angular.module('myApp', [])
  .controller('Ctrl1', ['$scope',
    function($scope) {

    $scope.prop1 = "First";

    $scope.clickFunction = function() {
      $scope.$broadcast('update_Ctrl2_controller', $scope.prop1);
    };
   }
])
.controller('Ctrl2', ['$scope',
    function($scope) {
      $scope.prop2 = "Second";

        $scope.$on("update_Ctrl2_controller", function(event, prop) {
        $scope.prop = prop;

        $scope.both = prop + $scope.prop2; 
    });
  }
])

HTML:

<div ng-controller="Ctrl2">
  <p>{{both}}</p>
</div>

<button ng-click="clickFunction()">Click</button>

有关更多详细信息,请参见plunker:

http://plnkr.co/edit/cKVsPcfs1A1Wwlud2jtO?p=preview


1
仅在Ctrl2(侦听器)是的子控制器时有效Ctrl1。同级控制器必须通过进行通信$rootScope
herzbube

0

如果您不想提供服务,则可以这样做。

var scope = angular.element("#another ctrl scope element id.").scope();
scope.plean_assign = some_value;

37
我毫不怀疑这个答案是否有效,但是我想指出,这违背了AngularJS的哲学,即模型/控制器代码中永远不要包含DOM对象。
JoeCool 2013年

3
-1是因为我认为通过DOM进行控制器通信是一种不好的做法。
克里斯·福斯特

3
@ChrisFoster,仅因为锤子是作为“工具”出售的,并不意味着它不能用作纸张重量。我确信对于那里的每个框架或工具,您总会找到需要“弯曲”“最佳实践”列表的开发人员。
Andrei V

5
@AndreiV-可怜的类比,使用锤子作为纸张重量没有任何缺点。这样的不良做法有明显的缺点,并且很容易导致意大利面条式代码。上面的代码很脆弱,因为它现在取决于控制器在DOM中的位置,并且很难测试。出于某种原因,使用服务是更好的做法,因为它不会将实现与模板绑定在一起。我同意开发人员经常需要调整最佳实践列表,但是当有一个清晰,通用,模块化程度更高的最佳实践时,则不需要这样做。
克里斯·福斯特

-1

除了$ rootScope和服务之外,还有一个干净,容易的替代解决方案来扩展angular以添加共享数据:

在控制器中:

angular.sharedProperties = angular.sharedProperties 
    || angular.extend(the-properties-objects);

此属性属于“角度”对象,与范围分开,并且可以在范围和服务中共享。

它的一个好处是您不必注入对象:定义后立即可以在任何地方访问它们!


2
这就像在window对象上遍布全局变量...如果要污染角度,为什么不继续污染窗口对象呢?
TJ
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.