简体   繁体   English

跨控制器共享和观察数据

[英]Sharing and observing data across controllers

We're using a tree-style navigation element which needs to allow other directives/controllers to know: 我们正在使用树型导航元素,该元素需要允许其他指令/控制器知道:

  • What the current selection is, and 当前选择是什么,以及
  • When the row selection changes 当行选择更改时

I'm trying to determine the best angular-way to handle this. 我正在尝试确定处理此问题的最佳方法。

Until now, we've been firing an event the entire app can listen to - less than ideal but ensures nothing is hard-coded to communicate directly with the component. 到目前为止,我们一直在引发整个应用程序可以收听的事件-不够理想,但要确保没有硬编码直接与组件进行通信。

However, we now have a need to obtain the current selection when another component is activated. 但是,我们现在需要在激活另一个组件时获得当前选择。 An event won't fly. 活动不会进行。

So I'm considering a service, some singleton which holds the current selection and can be updated directly by the tree, and read from by anyone who needs it. 因此,我正在考虑一种服务,该服务可以保存当前选择,并且可以由树直接更新,并可由需要它的任何人读取。

However, this present some other issues: 但是,这带来了其他一些问题:

  • Would it be better to ditch the event entirely, and have components which need to know when it changes $watch the service's nodeId? 最好完全放弃该事件,并让组件知道何时更改$watch服务的nodeId?
  • If I use $watch , it seems like I should expose the object directly - so using getters/setters won't work unless I want to complicate the needed $watch code? 如果我使用$watch ,似乎应该直接暴露该对象-因此,除非我想使所需的$watch代码复杂化,否则使用getters / setter方法将无法工作?

Part of my concern is that this would allow any component to set the value and this is intentionally not something we'll allow - the change will not impact the tree but will de-sync the service values from the true values, and will fire invalid $watches . 我担心的一部分是,这将允许任何组件设置该值,而我们故意不允许这样做-更改不会影响树,但会使服务值与真实值不同步,并且会触发无效值$watches

Implementing a getter should not lead to a complicated $watcher: 实现吸气剂不应导致复杂的$ watcher:

Service: 服务:

angular.service('myService', function() {
  var privateVar = 'private';
  return {
    getter: function() {
      return privateVar;
  };
});

Controller: 控制器:

angular.controller('myController', function(myService){
  $scope.watch(myService.getter, function(){
    //do stuff
  };
});

See this plunker: http://plnkr.co/edit/kLDwFg9BtbkdfoSeE7qa?p=preview 看到这个矮人: http ://plnkr.co/edit/kLDwFg9BtbkdfoSeE7qa?p=preview

I think using a service should work and you don't need any watchers for it. 我认为使用服务应该可以,并且您不需要任何观察者。

In my demo below or in this fiddle I've added the following: 在下面的演示中或此小提琴中,我添加了以下内容:

  1. One service/factory sharedData that's storing the data - selection and items 一种服务/工厂sharedData数据,用于存储数据-选择和项目
  2. Another service for eventing sharedDataEvents with observer/listener pattern. 使用观察者/侦听器模式来事件sharedDataEvents另一项服务。

To display the value in component2 I've used one-way binding, so that component can't change the selection. 为了显示component2的值,我使用了单向绑定,因此该组件无法更改选择。

Also separating data from events prevents a component from changing the selection. 同样,将数据与事件分开也可以防止组件更改选择。 So only MainController and Component1 can change the selection. 因此,只有MainControllerComponent1可以更改选择。

If you're opening the browser console you can see the listeners in action. 如果要打开浏览器控制台,则可以看到监听器正在运行。 Only listener of component3 is doing something (after 3 selection changes it will do an alert) the others are just logging the new selection to console. 只有component3侦听器正在做某事(更改3个选择后,它将发出警报),其他监听器仅将新选择记录到控制台。

 angular.module('demoApp', []) .controller('MainController', MainController) .directive('component1', Component1) .directive('component2', Component2) .directive('component3', Component3) .factory('sharedData', SharedData) .factory('sharedDataEvents', SharedDataEvents); function MainController(sharedData) { sharedData.setItems([{ id: 0, test: 'hello 0' }, { id: 1, test: 'hello 1' }, { id: 2, test: 'hello 2' }]); this.items = sharedData.getItems(); this.selection = this.items[0]; } function Component1() { return { restrict: 'E', scope: {}, bindToController: { selection: '=' }, template: 'Comp1 selection: {{comp1Ctrl.selection}}'+ '<ul><li ng-repeat="item in comp1Ctrl.items" ng-click="comp1Ctrl.select(item)">{{item}}</li></ul>', controller: function($scope, sharedData, sharedDataEvents) { this.items = sharedData.getItems(); this.select = function(item) { //console.log(item); this.selection = item sharedData.setSelection(item); }; sharedDataEvents.addListener('onSelect', function(selected) { console.log('selection changed comp. 1 listener callback', selected); }); }, controllerAs: 'comp1Ctrl' }; } function Component2() { return { restrict: 'E', scope: {}, bindToController: { selection: '@' }, template: 'Comp2 selection: {{comp2Ctrl.selection}}', controller: function(sharedDataEvents) { sharedDataEvents.addListener('onSelect', function(selected) { console.log('selection changed comp. 2 listener callback', selected); }); }, controllerAs: 'comp2Ctrl' }; } function Component3() { //only listening and alert on every third change return { restrict: 'E', controller: function($window, sharedDataEvents) { var count = 0; sharedDataEvents.addListener('onSelect', function(selected, old) { console.log('selection changed comp. 3 listener callback', selected, old); if (++count === 3) { count = 0; $window.alert('changed selection 3 times!!! Detected by Component 3'); } }); } } } function SharedData(sharedDataEvents) { return { selection: {}, items: [], setItems: function(items) { this.items = items }, setSelection: function(item) { this.selection = item; sharedDataEvents.onSelectionChange(item); }, getItems: function() { return this.items; } }; } function SharedDataEvents() { return { changeListeners: { onSelect: [] }, addListener: function(type, cb) { this.changeListeners[type].push({ cb: cb }); }, onSelectionChange: function(selection) { console.log(selection); var changeEvents = this.changeListeners['onSelect']; console.log(changeEvents); if ( ! changeEvents.length ) return; angular.forEach(changeEvents, function(cbObj) { console.log(typeof cbObj.cb); if (typeof cbObj.cb == 'function') { // callback is a function if ( selection !== cbObj.previous ) { // only trigger if changed cbObj.cb.call(null, selection, cbObj.previous); cbObj.previous = selection; // new to old for next run } } }); } }; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.js"></script> <div ng-app="demoApp" ng-controller="MainController as ctrl"> <p>Click on a list item to change selection:</p> <component1 selection="ctrl.selection"></component1> <!-- can change the selection --> <component2 selection="{{ctrl.selection}}"></component2> <component3></component3> </div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM