How can I utilize a service for monitoring user data and ensuring that $watch() functions properly?

I'm a beginner when it comes to AngularJS and currently working on building a website that has a navigation bar dependent on the user's login status. My approach involves using a state model design with the Nav controller as the parent state for all pages, which is functioning well.

app.config(function ($stateProvider, $httpProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('login');

$stateProvider
    .state('admin', {
        templateUrl: 'partials/Nav.html',
        controller: 'NavigationController'
    })
    .state('users', {
        url:'/users',
        templateUrl:'partials/Users.html',
        controller: 'UserController',
        parent: 'admin'
    })
    .state('devices', {
        url:'/devices',
        templateUrl:'partials/Devices.html',
        controller: 'DeviceController',
        parent: 'admin'
    }) ...

However, I now need to keep track of the user's state and I believe using a Service for this purpose would be the best approach. Therefore, I've created a service called UserStatus that monitors the user's state (authentication and username). This service is updated by other controllers as needed, and everything is functioning as expected.

(function(){

    var app = angular.module('butler-user-status', []);

    app.service( 'UserStatusService', ['$http', function($http) {
        //
        // As a service, the 'this' when the code is called will likely point to other things.
        // To avoid this issue, I save 'this' in a local variable and then the functions can 
        // refer to that variable and it will always be the right one!
        //

        console.log('User Status: Initializing service...');
        var userStatusService = this;
        userStatusService.state = {currentUsername: '', 
                                    authenticated: false};


        $http.get('/currentUser', {})
            .success(function(data, status, headers, config){
                // console.log('currentUser responded with data: '+JSON.stringify(data.username));
                if (data.username != null) {
                    console.log('UserStat: on success name='+data.username);
                    console.log('UserStat: on success authenticated='+true);
                };
            }).error(function(){
                // console.log('currentUser responded with error');
                console.log('UserStat: on error');
                userStatusService.state.currentUsername = '';
                userStatusService.state.authenticated = false;
            });

        this.login =  function(username) {
            console.log('Login: Setting username  and authenticated: username='+username);

            userStatusService.state.currentUsername = username;
            userStatusService.state.authenticated = true;

            console.log('Login: user logged in');
        };
        this.logout = function() { 
            userStatusService.state.authenticated = false; 
            console.log('Login: user logged out');
        };

    }]);
})();

Next, I wanted the Nav controller to monitor changes in the user status, so I implemented a $scope.$watch() on a variable in the service and updated the controller's variables when the service changed. This way, the HTML page could adjust its display based on the controller's variables.

(function() {

    var app = angular.module('butler-navigation', ['butler-user-status']);

    app.controller('NavigationController', ['$scope', 'UserStatusService', function($scope, UserStatusService) {

        var navigationController = this;
        var isAuthenticated = UserStatusService.state.authenticated;
        var currentUsername = UserStatusService.state.currentUsername;

        console.log('Butler-Nav: at init: username ='+UserStatusService.state.currentUsername);
        console.log('Butler-Nav: at init: auth = '+ UserStatusService.state.authenticated);

        $scope.$watch('UserStatusService.state', function (newValue, oldValue) {
            console.log('Butler-Nav: Updating isAuthenticated to:'+newValue+' from '+oldValue);
            navigationController.isAuthenticated = newValue;

        });
    }]);

})();

However, I'm facing an issue where the initial callback to the $watch listener returns undefined values for both the newValue and oldValue. Even though I've set real values in the service, not nulls. What could I be doing wrong? Any assistance would be greatly appreciated from a novice like me.

Answer №1

The issue at hand is that $watch evaluates expressions defined on the scope. Your UserStatusService is not defined on the scope, resulting in it returning undefined. There are two possible solutions for this problem.

Option 1: Create a function that retrieves the state from the scope of NavigationController:

$scope.getUserState = function () {
    return UserStatusService.state;
};

Using a getter function ensures that you always have the most up-to-date value. Then, you can watch this expression:

$scope.$watch("getUserState()", function (newValue, oldValue) {
    // Perform actions on change here
});

This is a viable option because you will probably need this information available on the scope for your UI to react accordingly.

Option 2: Alternatively, you can watch the result of a function:

$scope.$watch(function () {
    return UserStatusService.state;
}, function (newValue, oldValue) {
    // Perform actions on change here
});

During each digest cycle, the first function will be evaluated and the returned result will be compared to the previous value. This method is simpler and avoids adding anything to the scope.


Additional Explanation upon Request

There seems to be a misunderstanding between regular JavaScript variable scope and Angular's concept of "scope".

In Angular, "scope" refers to a unique object available to expressions in the view. Whenever you use "{{foo}}" in your view, Angular fills it with the value of the variable named foo attached to the scope object. These scope objects are generated by various directives and are often nested within each other. The idea of scoping here mirrors variable scoping. For more information, refer to this page.

A controller gains access to a scope object through the $scope variable injected by Angular. Anything added to the $scope variable becomes accessible to the view associated with that controller. If you do not explicitly attach something to the $scope object, it remains inaccessible.

Here's where the $watch function comes into play. When you use an expression in the view (e.g., "{{foo}}"), Angular places a "watch" on the expression ("foo" in this case). Any user interaction with your application triggers a digest loop (in various ways). Angular then evaluates all watched expressions and checks for changes since the last evaluation. Crucially, these expressions only reference variables on the scope, necessitating an attachment to the scope for the magic to work, as demonstrated in the first option.

Option two introduces a more advanced approach. Angular permits watching the outcome of a function. The digest loop process remains unchanged, except the system evaluates your function instead of an expression on the scope.

View this fiddle for a practical demonstration of how all these elements interact.

Answer №2

Just sharing my perspective on how you're approaching this issue.

Have you considered exposing the UserStatusService on the controller's scope and transferring your logic to the partials through directives?

In my opinion, it's beneficial to expose services in the scope and utilize directives, which is where Angular really shines.

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

Is there an improved method for toggling animations in CSS using jQuery compared to my current approach?

Looking to create a toggle effect for a dropdown box that appears and disappears when a button is clicked. var clickState = false; $("#show").on("click", function() { if (!clickState) { $(".animated").removeClass("off"); refreshElement($(".an ...

Reload Popup Box

I am currently developing a website using Django and I am in need of a popup window that can display logging messages and automatically refresh itself every N seconds. In order to achieve this, I am utilizing the standard Python logger, JavaScript, and Daj ...

Error encountered in Angular CLI: Attempting to access property 'value' of an undefined variable

I am encountering an issue while trying to retrieve the values of radio buttons and store them in a MySql database. The error message I receive is TypeError: Cannot read property 'value' of undefined. This project involves the use of Angular and ...

Adding colors dynamically upon page reload with javascript and jQuery

I have created an array of colors and am attempting to use colors.forEach inside the ready function to call addBox for each color in the array. My goal is to ensure that all the colors are added when the page is reloaded. Please let me know if you require ...

Instructions for extracting the href value from an anchor tag using JavaScript within a specified string

How can I retrieve the href value of the last anchor tag in the provided content string using pure JavaScript, excluding jQuery? var contents = '<div id="content"><a href="http://www.okhype.com/wp-content/uploads/2016/12/ruffcoin-made-in-aba ...

Showing dynamic content retrieved from MongoDB in a list based on the user's selected option value

Implementing a feature to display MongoDB documents conditionally on a webpage is my current goal. The idea is for the user to choose an option from a select element, which will then filter the displayed documents based on that selection. For instance, if ...

Error encountered in Webpack configuration while using html-webpack-plugin to generate index.html file

I've been experimenting with webpack to bundle project JS files. My goal is to generate an index.html file under the output dist folder using webpack. To achieve this, I followed the instructions provided in the webpack documentation and installed "h ...

Tips for retrieving the Solana unix_timestamp on the front-end using JavaScript

Solana Rust smart contracts have access to solana_program::clock::Clock::get()?.unix_timestamp which is seconds from epoch (midnight Jan 1st 1970 GMT) but has a significant drift from any real-world time-zone as a result of Solana's processing delays ...

Unable to perform filtering on a nested array object within a computed property using Vue while displaying data in a table

Lately, I've been experimenting with different methods to filter data in my project. I've tried using various approaches like methods and watchers, but haven't quite achieved the desired outcome yet. Essentially, what I'm aiming for is ...

"Notification: The marker element has been eliminated" encountered while attempting to restore text ranges using Rangy within a Vue component

I have a rather intricate Vue component that includes a contenteditable div. My goal is to highlight words within this div using the Rangy library and add extra markup while retaining this markup even after editing the text. Initially, I planned on asking ...

Conceal the second click action within the anchor tag

Is there a way to hide the second click event on all anchor tags except those that trigger popupfun? I have a sample page set up. [Check out the JS Fiddle here][1] http://jsfiddle.net/ananth3087/LgLnpvf4/15/ Link Anchor Tags: The page includes two ...

Is there an animation triggered by hovering the mouse over?

I've implemented a bounce animation that is triggered by mouseover on an image. Currently, the animation only happens once, but I want it to bounce every time the mouse hovers over it. Here is the HTML code: <div class="hair"> <img src= ...

Create a CSV document using information from a JSON dataset

My main goal is to create a CSV file from the JSON object retrieved through an Ajax request, The JSON data I receive represents all the entries from a form : https://i.sstatic.net/4fwh2.png I already have a working solution for extracting one field valu ...

Structuring React components - Incorporating a form within a modal

I am currently utilizing the react-bootstrap Modal, Form, and Button components. My goal is to have the button trigger the modal window containing a form. Once the form is filled out, clicking another button within the modal will validate the data and sen ...

Content must be concealed following the third paragraph

Dealing with an API that generates content in p tags, which can become excessively long. Considered hiding the content after 400 characters, but it poses a risk of cutting through HTML tags. Instead, looking to hide the excess content after 3 paragraphs a ...

The issue of asynchronous behavior causing malfunctioning of the PayPal button

import { PayPalButton } from 'react-paypal-button-v2' <PayPalButton amount={total} onSuccess={tranSuccess} /> const tranSuccess = async(payment) => { c ...

As I embarked on my journey into node.js, I encountered some stumbling blocks in the form of errors - specifically, "Uncaught ReferenceError: module is not defined"

Embarking on my Node.js journey, I am delving into the world of modules. After ensuring that both node and npm are correctly installed, I will share the code below to provide insight into the issue at hand. Within my project, I have two JavaScript files - ...

The Javascript calculation function fails to execute proper calculations

I have been facing immense frustration while working on a project lately. The project involves creating a unique Webpage that can calculate the total cost for users based on their selections of radio buttons and check boxes. Assuming that all other functi ...

Cannot trigger event ascn.onchange does not exist as a function

Need to trigger the onChange function and pass parameters within it. Here's what I have tried: setTimeout(function() { document.getElementById(input.key)?.onchange({}) }, 200); Encountering the following error message: cn.onchange is not a function ...

Angular: Choose the label of the currently selected option

I am working with a form that has a select menu displaying options using AngularJS: <form name="myForm" <select ng-model="myModel" ng-options="..."> </form> The output of the select menu looks like this: <select> <option valu ...