Collaborating and monitoring data between controllers

A unique challenge has arisen as we implement a tree-style navigation element that must communicate with other directives/controllers. The main objectives are:

  • Keep track of the current selection,
  • Detect when the row selection changes.

The task at hand is figuring out the best angular-way to address this issue.

Previously, we relied on firing an event that the entire app could listen to – not ideal but prevented any hard-coded communication with the component.

However, a new requirement has emerged where we need to access the current selection when another component is activated. Using events may not be the most efficient solution in this case.

Thus, I am contemplating using a service, a singleton entity, to store and update the current selection directly from the tree, which can then be accessed by other components as needed.

Nevertheless, there are some challenges to consider:

  • Is it wise to eliminate events completely and have relevant components $watch the service's nodeId for changes?
  • If utilizing $watch, should the object be exposed directly? This approach might complicate the required $watch code if getters/setters are used.

One concern is that allowing any component to manipulate the value could lead to discrepancies between the service values and the actual values in the tree, resulting in erroneous $watches.

Answer №1

Creating a getter function should not complicate the $watcher implementation:

Service:

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

Controller:

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

Check out this plunker example: http://example.com

Answer №2

If you want to utilize a service, there's no need for any watchers.

In the demonstration below or in this JSFiddle, I have implemented the following:

  1. A service/factory named sharedData that stores the data - selection and items
  2. Another service for event handling called sharedDataEvents, utilizing an observer/listener pattern.

To showcase the value in component2, I've employed one-way binding to ensure that the component cannot modify the selection.

Furthermore, segregating data from events prevents a component from altering the selection. Only MainController and Component1 have the ability to change the selection.

If you check the browser console, you'll witness the listeners in action. The listener of component3 is actively responding (it will trigger an alert after 3 selection changes), while the others are logging the new selection.

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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to prevent uncaught errors when checking for undefined in if statements and dealing with undefined items

It appears that there are not many oboe tags being used on SO, but any assistance with this general JavaScript question regarding handling uncaught errors for undefined would be greatly appreciated!~ I am currently utilizing Oboe.js to stream data to a we ...

Strategies for preserving context throughout an Ajax request

In my project, I am looking to implement an Ajax call that will update a specific child element within the DOM based on the element clicked. Here is an example of the HTML structure: <div class="divClass"> <p class="pClass1">1</p> &l ...

After converting from php/json, JavaScript produces a singular outcome

After running a PHP query and converting the result to JSON using json_encode, I noticed that when I try to print the results using echo, only one entry from the query is output in JSON format. My objective is to make this information usable in JavaScript ...

ReactJs: difficulty in resetting input field to empty string

I have an application using React v 0.13.1 (Old version). I am struggling to update my input field to "" after retrieving the updated value from the database. Scenario: I am updating the input fields by clicking on the button named "Pull&qu ...

What is the best way to retrieve a particular variable from an ng-repeat loop?

I'm currently working on a task where I have an ng-repeat loop that generates multiple dropdowns. Each dropdown is associated with a unique ID generated by the controller for reference purposes. The issue I am facing is that when a user selects an op ...

Having trouble implementing min and max date validation in Angular UI-Bootstrap datepicker with UI-Bootstrap version 1.3.3

My goal is to implement validation in my datepicker, preventing the user from selecting a date within 30 days before or after the current date. Here's the code snippet I'm currently using for the datepicker: <div class="form-group" ng-class=" ...

What is the method for transmitting a concealed attribute "dragable" to my component?

Currently, I have successfully integrated a here map into my project, but I am now tackling the challenge of adding draggable markers to this map. To achieve this, I am utilizing a custom package/module developed by my company. This package is designed to ...

What is the sequence in which services, factories, and providers are executed in AngularJS?

Is the execution order of all the mentioned tasks in AngularJS framework predefined, or is it left up to the programmer to determine? ...

Is it possible to access a class with protected/private fields written in TypeScript from outside the class in JavaScript?

Currently, I am delving into TypeScript classes (though my experience with OOP is limited). The following code snippet is extracted from the chapter on classes in https://www.typescriptlang.org/docs/handbook/classes.html Here's the issue at hand: I ...

Guide to automatically closing the calendar once a date has been chosen using owl-date-time

Utilizing Angular Date Time Picker to invoke owl-date-time has been functioning flawlessly. However, one issue I have encountered is that the calendar does not automatically close after selecting a date. Instead, I am required to click outside of the cal ...

Sublime Text 3 for React.js: Unveiling the Syntax Files

Currently, my code editor of choice is Sublime Text 3. I recently wrote a simple "hello world" example in React, but the syntax highlighting appears to be off. I attempted to resolve this issue by installing the Babel plugin, however, the coloring still re ...

Struggling with Responsiveness: Challenges with Detailed Information and Image Grid Design

Encountering challenges in achieving the desired responsiveness for a grid layout consisting of details and an image. The layout displays correctly on desktop screens, with details on the left and the image on the right. However, on mobile screens, the ima ...

Ways to effectively store continuous scale data in a database

I am currently running a straightforward Node.js server (using Express) on my local machine on port 8000. By utilizing the npm package serialport, I have successfully connected to a scale and am able to retrieve its weight in a continuous manner as shown ...

One common issue is being unable to target input[type=file] when multiple forms are present on a page using JavaScript

Question: I have multiple dynamic forms on a web page, each with a file input field. How can I specifically target the correct file input using $(this) in JavaScript? Here is an example of one of my forms: <form enctype="multipart/form-data" action="c ...

Use Javascript to display an image based on the date, otherwise hide the div

I'm looking to implement an image change on specific dates (not days of the week, but actual calendar dates like August 18th, August 25th, September 3rd, etc). Here's the div I'm working with: <div id="matchday"> <img id="home ...

Tips to detect a specific animation completion on an element?

How can I ensure that a specific animation ends when multiple animations are triggered on an element? My scenario involves an overlay song list that appears when a list icon is clicked. The challenge lies in closing the menu smoothly. I have implemented a ...

Adjust words to fit any screen size as needed

Looking for a small program that can dynamically change a word within an SVG? The goal is to create an effect where regardless of the word or group of words, they always stretch along the entire height (due to a 90-degree rotation) by adjusting the font si ...

The term 'Buffer' is not recognized in the context of react-native

Having trouble using buffer in my react-native app (developed with the expo tool). I have a hex value representing a geography Point like this for example -> 0101000020E61000003868AF3E1E0A494046B3B27DC8F73640 and I'm attempting to decode it into l ...

Tips for retrieving the option text value following an onchange event in AngularJS

Whenever I change the selection in my dropdown menu for 'Cities', the alert is displaying the value of the previous selection instead of the current one. For example, if I select a state and then switch to Cities, the alert shows the text related ...

Discovering ways to align specific attributes of objects or target specific components within arrays

I am trying to compare objects with specific properties or arrays with certain elements using the following code snippet: However, I encountered a compilation error. Can anyone help me troubleshoot this issue? type Pos = [number, number] type STAR = &quo ...