简体   繁体   中英

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?
  • 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?

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 .

Implementing a getter should not lead to a complicated $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

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
  2. Another service for eventing sharedDataEvents with observer/listener pattern.

To display the value in component2 I've used one-way binding, so that component can't change the selection.

Also separating data from events prevents a component from changing the selection. So only MainController and Component1 can change the selection.

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.

 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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