Angular: Exploring the differences between $rootScope variable and event handling

I have a dilemma with an app that handles user logins. As is common in many apps, I need to alter the header once the user logs in. The main file (index.html) utilizes ng-include to incorporate the header.html

I've come across two potential solutions (as I am relatively new to Angular, both may be flawed):

1) utilize $rootScope.broadcast()

Upon user login, I broadcast a message from the auth.js (which resides within a factory) that the controller in the header intercepts.

The auth.js

$rootScope.$broadcast('logged',user);

The controller.js

$scope.$on('logged', function(evnt, message){
      $scope.user = message;
    });

The header.html

<div class="header" ng-controller="GcUserCtrl as gcUserCtrl">
...
   <li><a ng-show="user" href="#">User: {{user.name}}</a></li>

2) establish a $rootScope variable

From my understanding, $rootScope serves as the root of all scopes and all $scope can access it.

The auth.js

$rootScope.user=user;

The heaeder.html (no controller necessary here)

<div class="header">
...
   <li><a ng-show="user" href="#">User: {{user.name}}</a></li>

So, what is the correct approach to handle this situation?

  • The first option seems somewhat resource-intensive due to the necessity for multiple checks during broadcast.
  • As for the second option, I'm not particularly fond of using global variables.

EDIT

3) employ a service

In response to alex's comment, I have included this alternative, although I haven't been able to get it functioning properly yet. (Here is the Plunker) It doesn't work without events being triggered

index.html

...
 <ng-include src="'header.html'"></ng-include>
...

header.html

Similar to solution number 1)

controller.js

.controller('GcUserCtrl', ['$scope','my.auth','$log', function ($scope, auth, $log) {
    $scope.user = auth.currentUser();
  }]);

my.auth.js

.factory('my.auth', ['$rootScope', '$log', function ($rootScope, $log, localStorageService) {
    var currentUser = undefined;

    return {

      login: function (user) {
        currentUser = user;
       ...

      },

  ...
      currentUser: function () {
        return currentUser;
      }
    };
  }]);

The issue here is that the controller is only invoked once initially, and nothing happens after subsequent logins.

Answer №1

To ensure user information is stored securely, it is recommended to utilize a dedicated Service for this purpose. This Service should be integrated with the authentication process to attach user data seamlessly. For effective authentication practices, consider implementing a Login Factory that handles the authentication and authorization tasks efficiently. By injecting the login Service into the factory, you can streamline the authentication process.

var app = angular.module('myApp', []);

app.service('SessionService', function () {
  this.attachUser = function(userId, fName){
    this.userId = userId;
    this.fName = fName;
  }
});


app.controller('MainCtrl', function($scope, SessionService){
  // Ensure proper invocation of attachUser method upon authentication
  SessionService.attachUser(1234, 'John');

  $scope.userName = SessionService.fName; 
});

This code snippet exemplifies how to create a reliable Session handling Service in AngularJS. The MainCtrl controller accesses the properties of SessionService through dependency injection. Typically, the

SessionService.attachUser(userId, fName)
operation would reside within a specialized login factory.

Adopting this approach enhances application modularity by isolating session management responsibilities. Maintaining user sessions in a designated Service ensures better organization and scalability. This eliminates the need to search for global variables scattered throughout the codebase, such as $rootScope instances.

EDIT: Check out an updated plunker utilizing rootScope broadcast/on for change capture.

Answer №2

Events serve as the primary method for indicating that some action must be taken by another entity. They signify that an action has occurred which may be of interest to another entity. Additionally, using events helps reduce scope pollution, as you've pointed out.

The statement regarding utilizing a service in this scenario is only partially correct. It is recommended that all login-related logic be encapsulated within a dedicated service specifically designed for handling logging in and out processes. This service would then trigger the event when a user logs in.

module.service('LoginHelper', function($rootScope) {
    this.loginUser = function(username, password) {
        // handle successful login
        $rootScope.broadcast('loggedIn', logginUserData)
    }
    this.logout = function() {
        // handle successful logout
        $rootScope.broadcast('loggedOut')
    }
})

The logged-in user data should be stored and managed by the service.

Alternatively, one could use $emit on $rootScope. While this limits the ability to listen for the 'loggedIn' event only on $rootScope, it does involve slightly less overhead.

Answer №3

Avoid using watches

One possible solution for this requirement would be to utilize an event mechanism, as suggested by alex. You can refer to a plunk demonstrating an example here: http://plnkr.co/edit/v6OXjOXZzF9McMvtn6hG?p=preview

However, in this specific scenario, it might not be ideal to follow the typical "angular way". Considering how $broadcast and/or $emit functions operate in AngularJS, which trigger listeners up or down the scope hierarchy, it's advisable to avoid them. You can read more about this in the documentation. Instead, other event propagation mechanisms could be considered based on the requirements.

app.controller('MainCtrl', function($scope, SessionService, $document){
  // Invoke attachUser differently (e.g., during authentication) - this is for testing purposes only
  $scope.isLoggedIn = false;
  $document.bind('$loggedin', function(){
      $scope.isLoggedIn = true;
      $scope.user = SessionService.fName;
  });
  $scope.logout = function() {
    SessionService.attachUser(null, null);
    $scope.isLoggedIn = false;
  };
});

app.controller('LoginCtrl', function($scope, SessionService, $document){
  $scope.doLogin = function() {
    console.log('doLogin');
    SessionService.attachUser(1234, $scope.username);  
    var doc = $document[0];
    var evt = new Event('$loggedin');
    doc.dispatchEvent(evt);
  };
});

For cleanup after the view is no longer needed, handle the $destroy event on the controller's scope and unbind the event handler...

$scope.$on('$destroy', function() {
    $document.unbind('$loggedin');
};

Refer to MDN for more information on triggering events using the DOM.

Update: [24 Sep]

A directive setup has been provided below, illustrating the concept:

app.directive('ngNest', function($parse, $compile, $timeout){
  // Directive code goes here
});

Further details and implementation of this directive can be found at: https://gist.github.com/deostroll/a9a2de04d3913f021f13

The results obtained from running this code are displayed below:

Live reload enabled.
$broadcast 1443074421928
$myEvt 14 1443074421929
$myEvt 19 1443074421930
DOM 1443074426405
$myEvt 14 1443074426405
$myEvt 19 1443074426405

It's worth noting the difference in timestamps when utilizing $broadcast. By broadcasting on $rootScope, Angular goes through the scope tree depth-first and triggers listeners attached to respective scopes in that order. This behavior is also validated within the source code for $emit and $broadcast methods.

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

Proper method for extracting and sending the final row of information from Google Sheets

The script I have is successfully formatting and sending out the information, but there is an issue. Instead of sending only the latest data, it is sending out each row as a separate email. I specifically want only the last row of data to be sent. functio ...

Instructions on how to implement a readmore button for texts that exceed a specific character length

I am attempting to display a "Read more" button if the length of a comment exceeds 80 characters. This is how I am checking it: <tr repeat.for="m of comments"> <td if.bind="showLess">${m.comment.length < 80 ? m.comment : ...

Maintain only specific elements in jQuery by filtering out the rest

Here is a scenario with a page layout to consider: <div id="page-container" class=""> <div id="scroller"> <!-- This page should be removed --> <div id="page_1" class="pagina"></div> <!-- These pages should be kept --&g ...

AJAX Post data transmission on the network: Enhancing data formatting

For my AJAX post call, I need to format the data differently. The server is expecting the data in a specific format when viewed in Chrome Network Headers details. My task is to update the JavaScript code below to meet this formatting requirement: percenta ...

What is the purpose of calling $timeout without specifying a delay argument?

Currently, I am reviewing a piece of AngularJS code that caught my attention. In this snippet, the $timeout function is being invoked without specifying a delay parameter. dataBinding: () => { this.$timeout(() => { this.s ...

Filling HTML5 Datepicker Using Information from a Model

Currently, I am in the process of developing a basic scheduling application for a project. To simplify the task of selecting start and end times/dates for our events, we have included a simple DateTime picker in the source code: <input type="datetime-l ...

When working with the Google Sheets API, an error occurred: "this.http.put(...).map is not a valid

Having difficulty with a straightforward request to the Google Sheets API using the PUT method. I followed the syntax for http.put, but an error keeps popping up: this.http.put(...).map is not a function. Here's my code snippet: return this.http ...

Correcting the reference to "/" (root) for assets after relocating the site to a subdirectory

I currently have a website located in the public_html directory, where all assets (images, css, js, etc) are referenced using /asset_folder/asset. The "/" at the beginning ensures that the browser starts from the root and navigates through the directories. ...

Error: The internal modules of node.js encountered an issue while loading causing an error to be thrown at

After installing Node.js (LTS version) from their website on my Windows system, I created a new directory named "mynode" and placed a text file inside it called "hellonode.js". Upon trying to run node hellonode.js in the command prompt after changing direc ...

Error alert: $.simpleWeather function not found

Currently, I am attempting to incorporate simpleWeather.js from the website simpleweatherjs.com into my own website. However, upon inserting the html code onto my website, an error message pops up: Uncaught TypeError: $.simpleWeather is not a function ...

I am looking to adjust/modulate my x-axis labels in c3 js

(I'm specifically using c3.js, but I also added d3.js tags) I have been working on creating a graph that displays data for each month based on user clicks. However, I am facing an issue where the x-axis labels show 0-X instead of 1-X. For instance, ...

Incorporating the id attribute into the FormControl element or its parent in Angular 7

I'm attempting to assign an id attribute to the first invalid form control upon form submission using Angular reactive forms. Here is my current component code: onSubmit() { if (this.form.invalid) { this.scrollToError(); } else { ...

Vue Testing Utilities - issue with data not updating upon click event activation

I recently created a basic test using Vue Test Utils: import { mount } from '@vue/test-utils' const App = { template: ` <p>Count: {{ count }}</p> <button @click="handleClick">Increment</button> `, ...

Ben Kamens has developed a new feature in jQuery Menu Aim where the sub menu will not reappear once it

While exploring Ben Kemens’ jquery-menu-aim, I came across a demonstration on codepen. The code on codepen allows smooth transition from the main menu to the sub menu, but if you move away completely, the submenu remains visible and doesn't disappe ...

Experiencing difficulties with ng select elements when performing selenium tests

Currently, I am facing a challenge in testing a specific element on a website that utilizes ionic for its front-end implementation. This particular element involves a listbox using ng-select. Despite my efforts to explore various solutions and experiment ...

How can we extract word array in Python that works like CryptoJS.enc.Hex.parse(hash)?

Is there a method in Python to convert a hash into a word array similar to how it's done in JavaScript? In JavaScript using CryptoJS, you can achieve this by using: CryptoJS.enc.Hex.parse(hash), which will provide the word array. I've searched ...

Utilizing the power of $.ajax() to parse through an XML document

I have a query about working with a large XML file containing 1000 nodes. Here is the code snippet I am using: $.ajax({ type: "GET", cache: false, url: "someFile.xml", ...

Entering a string into Angular

Within my function, I trigger the opening of a modal that is populated by a PHP file template. Within this template, there exists a div element containing the value {{msg_text}}. Inside my JavaScript file, I initialize $scope.msg_text so that when the mod ...

Can you explain the significance of "=>" in a context other than function invocation?

Working my way through the freeCodeCamp JavaScript course has left me quite puzzled about arrow functions. I know how to write a function using arrow notation, like this: const myFunc = () => "value"; However, in one of the later challenges, ...

Guide to transferring parameters from one function to another in Javascript

For my automation project using Protractor and Cucumber, I have encountered a scenario where I need to use the output of Function A in Function B. While I was able to do this without Cucumber by extending the function with "then", I am facing difficulties ...