Bidirectional communication between two AngularJS scopes or controllers utilizing a service

I am facing numerous situations where I require clicks or other interactions to trigger actions in a different area of the webpage (one-way communication). Now, I have encountered a need for bidirectional communication, where changes made in element A can affect specific attributes in the context behind element B and vice versa. Up until now, I have been using $rootScope.$broadcast to achieve this functionality, but it seems excessive and results in redundant code in both places:

$scope.$on('event-name', function(event, someArg) {
    if(someArg === $scope.someProperty) return;

    $scope.someProperty = someArg;
});

$scope.$watch('someProperty', function(newValue) {
    $rootScope.$broadcast('event-name', newValue);
});

Is there a more efficient solution? I would like to connect the two (or three, or N) scopes through a service, but I cannot find a way to do so without resorting to magical event names and repetitive code.

Answer №1

Although I have not personally tried this method, this answer provides a detailed explanation of how it can be implemented. You can also refer to this code snippet for a visual representation:

(function() {
    var mod = angular.module("App.services", []);

    //additional services go here...

    /* pubsub - sourced from https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js*/
    mod.factory('pubsub', function() {
        var cache = {};
        return {
            publish: function(topic, args) { 
                cache[topic] && $.each(cache[topic], function() {
                    this.apply(null, args || []);
                });
            },

            subscribe: function(topic, callback) {
                if(!cache[topic]) {
                    cache[topic] = [];
                }
                cache[topic].push(callback);
                return [topic, callback]; 
            },

            unsubscribe: function(handle) {
                var t = handle[0];
                cache[t] && d.each(cache[t], function(idx){
                    if(this == handle[1]){
                        cache[t].splice(idx, 1);
                    }
                });
            }
        }
    });


    return mod;
})();

It's important to note the potential memory leak that may occur if controllers are "deleted" without proper unsubscribing.

Answer №2

Consider giving this service a try:

'use strict';
angular.module('test')
  .service('messageBus', function($q) {
    var subs = {};
    var pendingQs = [];

    this.sub = function(name) {
      subs[name].reqDfr = $q.defer();
      return subs[name].reqDfr.promise;
    }

    this.unsub = function(name) {
      subs[name].reqDfr.resolve();
      subs[name].reqDfr = null;
    }

    function pub(name, data) {
      subs[name].reqDfr.notify(data);
    }

    this.req = function(name, code, details) {
      var dfrd = null;
      if (subs[name].reqDfr) {
        if (pendingQs[code]) {
          dfrd = pendingQuestions[code];
        } else {
          dfrd = $q.defer();
          pendingQs[code] = dfrd;
          pub(name, {
            code: code,
            details: details
          });
        }
      } else {
        dfrd = $q.defer();
        dfrd.resolve({
          code: "not subscribed"
        });
      }
      return dfrd.promise;
    }

    this.res = function(data) {
      var dfrd = pendingQs[data.code];
      if (dfrd) {
        dfrd.resolve(data);
      } else {
        handlePreemptiveNotifications(data);
      }
    }

    function handlePreemptiveNotifications() {
      switch (data.code) {
        
      }
    }
  });

This method can facilitate multi-controller communication as a message bus. It leverages the promises API in Angular for notification callbacks. All controllers involved should subscribe to the service like so:

angular.module('test')
  .controller('Controller1', function($scope, messageBus) {
    var name = "controller1";

    function load() {
      var subscr = messageBus.sub(name);
      subscr.then(null, null, function(data) {
        handleRequestFromService(data);
      });
    }

    function handleRequestFromService(data) {
      if (data.code == 1) {
        data.count = 10;
        messageBus.res(data);
      }
    }

    $scope.$on("$destroy", function(event) {
      messageBus.unsub(name);
    });

    load();
  });

angular.module('test')
  .controller('Controller2', function($scope, messageBus) {
    var name = "controller2";

    function load() {
      var subscr = messageBus.sub(name);
      subscr.then(null, null, function(data) {
        handleRequestFromService(data);
      });
    }

    function handleRequestFromService(data) {
      
    }

    $scope.getHorseCount = function() {
      var promise = messageBus.req("controller1", 1, {});
      promise.then(function(data) {
        console.log(data.count);
      });
    }

    $scope.$on("$destroy", function(event) {
      messageBus.unsub(name);
    });

    load();
  });

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

Prevent user input in Vue.js until the value has been modified

Need help handling initial input values: <input type="text" v-model="name" ref="input" /> <button type="submit" :disabled="$refs.input.defaultValue == $refs.input.value">Submit</button> Encountering an error with the disabled binding: C ...

Vue-moment displaying incorrect time despite timezone setting

Feeling a bit puzzled about my Laravel 8 application. I store time in UTC with timestamp_no_timezone in my PostgreSQL database. When I check the time in the database, it displays today's date with 13:45 as the time. However, when I use vue-moment and ...

Is your form complete?

Is there a way to determine if all required fields in the current form are correctly filled in order to disable/enable navigation? Are there any specific properties or JQuery functions that can be used to check for form completion status? ...

Change the default values for grid column configurations in Ext JS globally

The Ext.grid.column.Column class contains the following configurations: draggable (Default: true) sortable (Default: true) menuDisabled (Default: false) Is there a way to globally change the default values of these configurations for all grid columns i ...

Conceal and reveal buttons at the same location on an HTML webpage

There are 3 buttons on my custom page called page.php: <input type="submit" value="Btn 1" id="btn1"> <input type="submit" value="Btn 2" id="btn2" onClick="action();> <input type="submit" value="Btn 3" id="btn3" onClick="action();> ...

What is the solution to fixing the Vetur/Vuelidate issue where the error message "'validate' does not exist in type 'ComponentOptions<Vue [etc.]" is displayed?

QUERY: I'm facing an issue with error 'validations' does not exist in type 'ComponentOptions<Vue [etc.] when using Vetur with TypeScript installed in VSCode. How can I resolve this? CONTEXT: I integrated Vuelidate into a single-file ...

Guide for creating a CORS proxy server that can handle HTTPS requests with HTTP basic authentication

For my http requests, I've been utilizing a CORS-Proxy which works well for me. However, I recently stumbled upon an API for sending emails which requires http basic authentication for https requests. I'm uncertain of how to go about implementing ...

Learn how to trigger an HTTP exception after a failed command in a saga with NestJS CQRS

Currently utilizing the NestJS CQRS pattern to handle interactions between User and UserProfile entities within my system. The setup consists of an API Gateway NestJS server along with dedicated NestJS servers for each microservice (User, UserProfile, etc. ...

Retrieve JSON object by matching another JSON property

I am working with an array of id's and their respective contents in a JSON response. My goal is to retrieve the content based on the received id. For instance, if the ID is 1 (returned from the JSON), I aim to access the JSON data using "data.id" (wh ...

What is the reason for the malfunction in the login dialog?

I'm having trouble implementing AJAX login functionality on my website. When I click the submit button, nothing seems to happen. Can someone take a look at the code and help me out? $(document).ready(function() { var user, pass; function login(us ...

Learn the process of uploading and converting multiple files into base64 using AngularJS

Initially, I was able to successfully upload a single file and convert it to base 64. However, when attempting to upload multiple files and convert each one to base 64 using the directive below, all images end up being converted into a single base 64 strin ...

Prevent the reloading of the page by utilizing Ajax technology when submitting a form in Laravel

I'm facing a challenge with processing a form submit using ajax instead of Laravel to prevent page reloads. Unfortunately, it's not working as expected and I'm struggling to figure out the issue. Despite researching numerous examples online, ...

What is the best approach for invoking two services using a single provider factory?

Welcome! I'm having a little trouble setting up a discussion platform where users can post and comment. I've created two REST services for this purpose, but it seems like the commenting functionality is not working as expected. Could anyone help ...

Having trouble in React.js when trying to run `npm start` with an

Upon initially building a todo app in react.js by using the command: npx create-react-app app_name When I proceeded to run the command npm start, it resulted in displaying errors: In further investigation, I discovered a log file with various lines that ...

Refreshing jQuery via Ajax Response

In our JSF2 application, we encounter situations where we need to re-invoke the JavaScript (specifically jQuery for UI styling) when making Ajax calls. However, it seems that the JavaScript functions are not being called upon receiving the Ajax response fr ...

Upgrading to Angular 2: Utilizing ElementRef in ES5

Currently, I am facing a challenge in creating an Attribute directive for Angular 2 that would allow me to set multiple default HTML attributes using a single custom attribute. My intention is to apply this directive specifically to the input element. Howe ...

Compare and contrast the functions of scrollToIndex and manual scrolling in a React Native FlatList

Currently, my FlatList is set up to automatically scroll item by item based on a time series using the scrollToIndex function. However, I also want to allow users to manually scroll through the list and temporarily pause the automatic scrolling when this ...

Discovering XMLHttpRequest Issues within a Chrome Application

Is there a way to identify XMLHttpRequest errors specifically in Chrome App? For instance, I need to be able to recognize when net::ERR_NAME_NOT_RESOLVED occurs in order to display an error message to the user. While XMLHttpRequest.onerror is activated, ...

The emission from Socket.io is originating from the recipient's browser without being transmitted to the sender's browser

Hey there, I'm fairly new to socket.io and not completely sure if what I'm doing is the standard way, but everything seems to be working fine so I guess it's okay. I've been working on a chat application where messages are stored in a d ...

Activating a function that requires locating a dynamically loaded element on the webpage using AJAX

I have a unique page on my website that allows users to make edits. Whenever they click on an item, the edit form is seamlessly loaded into a specialized section of the page using AJAX. Depending on the chosen item, the form fields are already prefilled w ...