Efficiently transmitting WebSockets events/messages to multiple controllers in AngularJS

Incorporating AngularJs, I created a factory to manage a WebSocket connection effectively.
Here is the code:

.factory('WebSocketConnection', function () {
        var service = {};
        service.callbacks = [];
        service.connect = function() {
            if(service.ws) 
              return;
            var ws = new WebSocket("ws://localhost:9000/ws");
            ws.onmessage = function (message) {
                angular.forEach(service.callbacks, function(callback){
                    callback(message);
                });
            };
            service.ws = ws;
        };

        service.send = function(message) {
            service.ws.send(message);
        };

        service.subscribe = function(callback) {
          service.callbacks.push(callback);
        };
        return service;
});

Essentially, this setup enables Angular components like controller or factory/service to register specific callbacks to handle messages; hence, the callbacks array.
Below is an insightful snippet from a listening controller:

WebSocketConnection.subscribe(function (message) {  
      $scope.$apply(function () {
         var data = angular.fromJson(message.data);
         $scope.notifications.push(data);
      });
});

Therefore, this function is added to the callbacks array.

However, what if the controller is no longer needed at some point? (e.g., when navigating to another page based on a different controller)
It becomes necessary to remove the callback item (function) from the array each time the controller's usage is terminated to prevent unnecessary and potentially conflicting processing of the callback for subsequent messages.
This is not very convenient...

I contemplated broadcasting an event from the $rootScope within the factory to enable a specific controller to handle its listeners/subscriptions automatically.
Yet, I prefer not to involve the entire scope tree, encompassing scopes that are not relevant.

What would be the ideal approach?

Note: I aim to establish a 1-N relationship where 1 represents the WebSocket handler (factory) and N denotes any active parallel Angular components, each needing to listen to messages.

Answer №1

If you want to ensure seamless interaction with your model, it is advisable to store it within a service or factory object. This approach will allow you to manipulate the data independently of the application's state and controller configurations. It also promotes the principle of:

$scope has model instead of $scope as model

To implement this, you can structure your code as follows:

ws.onmessage = function(event) {
    $rootScope.$apply(function(){
        Service.notifications.push(event.data);
    }
}

Additionally, you can define your controller like this:

angular.module('MyApp').controller('MyCtrl', ['$scope', 'Service', 
function($scope, Service) {
    $scope.notifications = Service.notifications; //references are ===
}])

To enhance adaptability, you can analyze the message data to determine the required injectables/methods for updating and utilize the $injector accordingly.

Answer №2

Shoutout to @calebboyd for reminding me about the $scope's destroy event, which helped me figure out a great solution for my task.

To automatically unsubscribe the controller, you can simply include this code snippet:

$scope.$on("$destroy",function() {
  WebSocketConnection.unsubscribe($scope.$id);      
});

The process for subscribing would then appear as follows:

WebSocketConnection.subscribe($scope.$id, function (message) {   //note the $scope.$id parameter
      $scope.$apply(function () {
         var data = angular.fromJson(message.data);
         $scope.notifications.push(data);
      });
});

As a result, the complete factory code is:

.factory('WebSocketConnection', function () {
        var service = {};
        service.callbacks = {}; //note the object declaration, not Array
        service.connect = function() {
            if(service.ws) 
              return;
            var ws = new WebSocket("ws://localhost:9000/ws");
            ws.onmessage = function (message) {
                angular.forEach(service.callbacks, function(callback){
                    callback(message);
                });
            };
            service.ws = ws;
        };

        service.send = function(message) {
            service.ws.send(message);
        };

        service.subscribe = function(concernedScopeId, callback) {
          service.callbacks[concernedScopeId] = callback;
        };

        service.unsubscribe = function(concernedScopeId) {
          delete service.callbacks[concernedScopeId];
        };

        return service;
});

By implementing this solution, any unnecessary listener callbacks can be identified and removed efficiently.

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

Problem with Anular5 - "this" not functioning correctly inside of ready()

I am encountering an issue in graph.component.ts this.cContainer = cytoscape ( { ready: function(e) { this._dataService.setResultData(); } }); However, I am getting the following error message: ERROR TypeError: Cannot read property &ap ...

Implementing Jquery to Identify the Matching Indices of Two Arrays

I need to find the indices of similar values in array1 and array2, and then save them in a variable named stored_index. array1 = ["50","51","52","53","54","55","56","57","58","59"]; array2 = ["59","55","51"]; The desired result for stored_index is: sto ...

What is a unique way to rename the 'yo' command in Yeoman to something of your choosing?

Is there a way to change the alias for creating a directive from $ yo directive myDirective To something like this: $ awesomeness directive myDirective If so, how can it be done? ...

Creating a Three-Dimensional Bounding Box in THREE.js

After successfully utilizing the OBB.js in three.js examples to fetch the center, halfSize, and rotation values, the next step is to determine how to calculate the 8 corners of the bounding box based on this information. Additionally, we need to understa ...

Tips for extracting a specific attribute from an array of objects using both react and JavaScript while considering various conditions

I have a set of objects structured like this: const obj_arr = [ { id: '1', jobs: [ { completed: false, id: '11', run: { id: '6&apos ...

Executing a series of HTTP requests sequentially using Angular 5

I need some guidance on sending an array of HTTP requests in sequential order within my application. Here are the details: Application Entities : Location - an entity with attributes: FanZone fanZone, and List<LocationAdministrator> locationAdmins ...

How to activate the menu in AngularJS

Within my application, I have a header that contains various menu items. These menu items are fetched from a service and displayed in the header. When hovering over the main list, the submenus appear. My goal is to highlight the parent item as active when ...

What are the steps to use the vue-text-highlight feature?

I am attempting to implement an example from the basic usage section of https://github.com/AlbertLucianto/vue-text-highlight. However, upon opening index.html in Firefox, I am only greeted with a blank page. You can find the code I am using at https://git ...

adjust variable increment in JavaScript

As I work on developing a Wordpress theme, I have encountered an issue with incrementing a load more button using Javascript. This is my first time facing this problem with a javascript variable, as I don't always use Wordpress. The variable pull_page ...

What could be causing the Uncaught ReferenceError message that says scrollToM is not defined in my code?

Can someone help me with this code for creating a single page website? $(window).load(function() { function filterPath(string) { return string .replace(/^\//,'') .replace(/(index|default).[a-zA-Z]{3,4}$/,'') ...

Tips for successfully including a forward slash in a URL query string

My query involves passing a URL in the following format: URL = canada/ontario/shop6 However, when I access this parameter from the query string, it only displays "canada" and discards the rest of the data after the first forward slash. Is there a way to ...

Efficiently handle user authentication for various user types in express.js with the help of passport.js

Struggling to effectively manage user states using Passport.js in Express.js 4.x. I currently have three different user collections stored in my mongodb database: 1. Member (with a profile page) 2. Operator (access to a dashboard) 3. Admin (backend privi ...

The perplexity caused by unescaped HTML and line breaks

I have created a platform similar to a forum, where users can input various strings such as: <<<<< >.< and so on. It is essential for me to maintain the formatting of new lines. Additionally, I need to condense multiple new lines in ...

Guide on moving the parent <div> at an angle within an AngularJS custom directive

The code snippet below demonstrates the functionality of dynamically generated ellipse elements as they change color based on an array. I have been experimenting with updating the style property of the parent div element within a custom directive. This in ...

CSS - tackling the issue of menu items overlapping

My menu has two items, and when the user clicks on each item, a sub-menu is displayed. The issue is that both menus are displaying at the same spot - under the first item. I've tried tweaking it for a while but can't seem to fix the problem. Ad ...

Having issues extracting information from easports.com due to difficulties with the <ea-elements-loader> element

Recently, I've been developing a Python WebScraper to extract data (such as wins and losses) from our FIFA ProClub by crawling various websites. While I successfully implemented it on a third-party website using BeautifulSoup and requests, I encounter ...

Removing data using axios in a React project

I'm currently working on a small app using the Json server package to help me keep track of movies I want to watch in my free time. I am looking to learn React and Axios, so I decided to build this app with these technologies. The concept is simple - ...

Sharing details of html elements using ng-click (AngularJS)

I am currently exploring options to enable users to click on a "open in new tab" link, which would essentially transfer that HTML element into a fresh window for their convenience. I am seeking advice on how to achieve this. At the moment, I am able to la ...

Since switching to PHP 5.5 from version 3.x, I have noticed that it is attempting to interpret my JavaScript comment within a script tag in a PHP include file

After a long break from working with PHP, I recently encountered an issue with an older website I built using PHP and the include function. The site was functioning perfectly until the web host updated PHP to version 5.5, causing a strange bug where it see ...

Building a Sharepoint application with Angular 2 using the CLI and Webpack

Trying to deploy an Angular 2 CLI App to a SharePoint 2013 site has been quite challenging. The app works fine when embedded via a Content Editor Webpart, but the console throws an exception: zone.js:158 Uncaught Error: Sys.ParameterCountException: Parame ...