Angular Singleton Service for Asynchronous UI-Router Operations

I've been using UI-Router for my Angular application. The data I fetch through an asynchronous $http call helps in creating a connection object, which I want to make available as a singleton. My aim is to prevent potential timing issues that may arise when the socket property in the Service is assigned asynchronously. I want other states in the router to access or create another connection object through the singleton, ensuring reusability instead of generating new connections each time.

Below is a basic structure of the code I'm trying to make functional. I'm uncertain if something can be achieved through the state's resolve method.

Any ideas or recommendations on how this can be accomplished?

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', function($http) {
    var socket;

    // Call and store $http result in a singleton
    // Should the $http.get be encapsulated in a function? If so, how should it be invoked 
    // and returned for synchronous access by a service user?
        $http.get("something/that/returns/data")
            .success(function (result) {
                this.socket= createConnection(result.data);
            })
            .error(function (err) {
                //handle error
            })

    var getSocket= function() {
        if (!this.socket) {
            return createNewSocket();
        } else {
            return this.socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: // ??? Can something be done here ???
            },
            controller: function(socket) {
                // Accessing socket here
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: // Should reference the same socket object from home state
            },
            controller: function(socket) {
                // Accessing same socket here
            }
        })
})

Update

In my attempt to clarify the issue further, I've made some changes to the original code a few times. Previous comments helped correct certain errors I encountered. Hence, I reverted the above code back to its initial state and added new code below. However, I'm still facing a challenge where the resolve within the ui-router state isn't resolving before entering the controller. Consequently, calling an emit() on the supposed resolved socket object results in an error stating "Cannot read property 'emit' of undefined." Additionally, I seek to maintain the same socket object across states. Any suggestions or guidance would be highly appreciated.

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', function($http) {
    var socket;

    // Fetch and store $http result in a singleton
    $http.get("something/that/returns/data")
        .then(function (result) {
            socket = createConnection(result.data);
        }, function (err) {
            // handle error
        });

    var getSocket = function() {
        if (!socket) {
            // handle error
        } else {
            return socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some msg");
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some other msg");
            }
        })
})

Update 2

The following solution works, but it lacks the elegance found in the answer provided by Muli Yulzari, which has been accepted as the correct one.

After exploring the creation of a custom promise and utilizing it as a resolution for ui-router, I have confirmed that only one connection is created across states, with entry into the controller being deferred until the connection is resolved. Therefore, I believe this solution effectively addresses the problem at hand. I omitted a version of the code that deals with an onConnect event on the connection prior to resolving the promise for brevity.

var myapp = angular.module('me', ['ui.router'])
.service('connectionService', ['$http', '$q', function($http, $q) {
    var socket;

    this.getSocket = function() {
        if (!socket) {
            var deferred = $q.defer();

            $http.get("something/that/returns/data")
                .then(function (result) {
                    socket = createConnection(result.data);
                    deferred.resolve(socket);
                }, function (err) {
                    // handle error
                    deferred.reject(socket);
                });

            return deferred.promise;
        } else {
            return socket;
        }
    }
}])

myapp.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise("home");

    $stateProvider
        .state('home', {
            url: "/home",
            templateUrl: "home.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some msg");
            }
        })
        .state('nextState', {
            url: "/next",
            templateUrl: "next.html",
            resolve: {
                socket: function(connectionService) {
                    return connectionService.getSocket();
                }
            },
            controller: function(socket) {
                socket.emit("some other msg");
            }
        })
})

Answer №1

It seems that you are looking to set up the socket based on data retrieved from $http.get every time the socket instance is not valid, and you want to do this asynchronously while the application loads.

.service('connectionService', ['$http', '$q', function($http, $q) {
    //For caching the socket
    var socket;

    //Initiate the first get request
    tryGetData();

    //tryGetData sends a $http.get request and returns a promise that resolves -
    //with the new socket instance.
    function tryGetData(){
     return $http.get("something/that/returns/data")
        .then(function (result) {
            return socket = createConnection(result.data);
        }, function (err) {
            // handle error
        });
    }

    //When getSocket is called, it checks if the socket is already instantiated. If not, 
    //it assumes the $http.get failed and tries again, creating a new socket.
    //This function returns a promise for controllers to use.
    this.getSocket = function(){
     if(socket) return $q.resolve(socket);
     return tryGetData();
    }
}]);

The remainder of your code appears to be in good shape.

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

Encountering the error message "ws02 script tag does not define Map"

When I try to create a map using the following code: let myMap = new Map(); I encounter the error shown below. Script Error: The script engine encountered an error while executing the inlined Javascript function 'mediate'. com.sun.phobos.script. ...

Receiving an unknown value from the input field

I can't seem to retrieve the value of my input, as quantityElement.Value consistently returns undefined. Here is the section of my HTML and JS that I am struggling with. In my JavaScript function, the quantityElement always gives me an undefined whe ...

Incorporating PruneCluster into an AngularJS Leaflet Directive for Enhanced Mapping

I am currently facing an issue with loading clustered markers for geojson data using PruneCluster. The clusters are not appearing on the map and there are no errors showing up in the console to assist with troubleshooting. Below is the snippet of my curr ...

Make sure to confirm the input length and utilize bootstrap validation for error checking

I have a rather straightforward question. I am currently utilizing Bootstrap 4.6 and am looking to implement form validation for my input field. The requirement is to validate whether the input has a length of 5 characters or less, in which case it should ...

Is there a way to retrieve the password of the currently logged in user using Keycloak in JavaScript?

Does anyone know how to access the password of a logged-in user in a React application using Keycloak? I have successfully retrieved most of the necessary information from the idToken, like this: keycloak.init({ onLoad: 'login-required'}).then(fu ...

Incorporating an external library into a Node.js virtual machine

I'm currently working on a nodejs library that enables users to write and execute their own JS code. Here is an example: var MyJournal = Yurnell.newJournal(); module.exports = function(deployer) { MyJournal.description = "my first description& ...

Exploring the possibilities of combining DOM and Express in web development

app.post('/result.html',function(req,res){ var num1 = req.body.Num1 ; var num2 = req.body.Num2 ; var operator = req.body.Operator ; var result =0 ; switch (operator) { case '+': result = Number(num1)+Number(num2) ; ...

Implementing an Angular theme in a project using Node.js, MySQL, and Express

I'm a beginner with node, angular, and express. I've managed to create a REST API using node+express+mysql, but now I need help integrating the blur-admin theme into my existing project. Despite getting the theme to run separately with gulp, I&ap ...

summing 3 numbers to a total of 100 percent

I am currently trying to calculate the percentages of different statuses based on 3 count values. Let's assume I have 3 statuses: 1) Passed 2) Failed 3) Skipped When dealing with only two cases, I was able to use a combination of the Floor and Ceil ...

The data display in MUI-Datatable is experiencing an issue where it is unable to read properties of undefined, specifically when trying to split the data

Whenever I include data within the MuiDatatable, it triggers this error: Cannot read properties of undefined (reading 'split') Is there a solution to this problem so that the data can be properly displayed? To demonstrate the issue, I have se ...

Steps to ensure a dropdown menu remains expanded when its nested menu is selected

Check out this dropdown list here, but the issue arises when clicking on a nested menu item such as "Books Registration". Once that page loads, the dropdown menu collapses like shown here. Here's a snippet of the dropdown code: <ul class="nav nav- ...

Disabling animations in Reactjs with CSSTransition and Group Transition

Currently, I am experimenting with REACTJS to build a basic app featuring Transitions. In my project file, I have imported CSSTransitions and Group Transition. However, when attempting to implement CSSTransition for specific news items, the animations are ...

The error "ReferenceError: Component is not defined in Meteor" indicates that the Component

I'm facing an issue while trying to display my Deal component and encountering the error mentioned below. I am currently utilizing Meteor along with ReactJS. Uncaught ReferenceError: Deal is not defined at meteorInstall.imports.routes.routes ...

Retrieving individual data elements from an array with the help of the .find() method

I am struggling to display specific details of an object within an array by using .find() method, but I keep receiving undefined as the output. Below is the code snippet where I attempted this, when I log the ID, I can see a value, however, when I try to ...

What are the steps to reset the Firebase server in order to allow the deployment of functions

After using firebase deploy --only functions, an error appeared in my React code and I had to use control-C to stop the deployment on my Mac. Now, when attempting to redeploy to Google servers, the following error is encountered: firebase.js -> build/f ...

Use JavaScript to overlay drawings onto an existing image

Within this particular image, I possess a compilation of pixel coordinates outlining the polygon segments that encompass all the objects contained within it (refer to the image provided below). For example, in relation to the individual, there exists a li ...

Activate the script upon the left-click of the arrow icon

Looking for help with this basic javascript code snippet. $('#about_us').on('click',function() { $('#slider').toggleClass('open'); }); I'm trying to find a way to activate this function by pressing the lef ...

Updating the style sheet of a selected menu item on an ASP.NET master page

I created an asp.net master page with a menu setup like this: <menu id="menu"> <nav id="main_nav"> <ul id="menu-primary"> <li ><a href="./">Home</a></li> <li><a href="staff.aspx"& ...

What is the process for sorting a filtered array of strings alphabetically?

Currently, I am navigating through an array in my VUE application that holds the following structured data: [ { "id": 1, "brands": [ { "name": "Mall", "id": 1 }, { "na ...

When using Asp.Net TextBox, the focus is lost after inputting the first character

I have created an Asp.Net web form (aspx) that includes a TextBox: <div id="field"> <asp:TextBox Id="Name" runat="server" MaxLength="50"/> </div> Whenever I type characters into the TextBox, I t ...