跨控制器共享和观察数据

时间:2015-10-27 22:47:19

标签: javascript angularjs

我们正在使用树型导航元素,需要允许其他指令/控制器知道:

  • 目前的选择是什么,
  • 当行选择更改时

我正在尝试确定处理此问题的最佳角度方式。

到目前为止,我们一直在触发整个应用程序可以监听的事件 - 不太理想,但确保没有任何硬编码直接与组件通信。

但是,我们现在需要在激活另一个组件时获取当前选择。事件不会飞。

所以我正在考虑一个服务,一些单例保存当前选择,可以直接由树更新,并且可以从需要它的任何人那里读取。

然而,这提出了一些其他问题:

  • 完全抛弃事件会不会更好,并且有哪些组件需要知道它何时更改$watch服务的nodeId?
  • 如果我使用$watch,似乎我应该直接暴露对象 - 所以除非我想使所需的$watch代码复杂化,否则使用getter / setter将不起作用?

我担心的一部分是,这将允许任何组件设置值,这有意不是我们允许的 - 更改不会影响树,但会使服务失效来自真实值的值,并将触发无效$watches

2 个答案:

答案 0 :(得分:0)

实现getter不应该导致复杂的$ watcher:

服务:

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

控制器:

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

请参阅此plunker:http://plnkr.co/edit/kLDwFg9BtbkdfoSeE7qa?p=preview

答案 1 :(得分:0)

我认为使用服务应该有效,而且你不需要任何观察者。

在我的演示文稿中或fiddle中,我添加了以下内容:

  1. 存储数据的一个服务/工厂sharedData - 选择和项目
  2. 使用观察者/侦听器模式事件sharedDataEvents的另一项服务。
  3. 要在component2中显示值,我使用了单向绑定,因此该组件无法更改选择。

    同样从事件中分离数据可防止组件更改选择。因此,只有MainControllerComponent1才能更改选择。

    如果您打开浏览器控制台,则可以看到正在运行的侦听器。只有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>