Best practice for using $rootscope in an Angularjs application?

10.1k views Asked by At

We have a large Angularjs 1.6 application that has $rootscope scattered throughout the app in over 200 places in filters, services, routes, etc.. so it needs to be refactored, but I'm not sure how to know when to remove it. When is it a best practice to use $rootscope in the application?

I've read everything from never, to using it for storing variables, which I assumed was for sharing data between controllers. I've since read that it's better to use factories/services for this use case instead and I also read that one valid use case is to use $rootscope as a global event bus.

I didn't really see this explained in the Angularjs docs.

3

There are 3 answers

3
AndreaM16 On BEST ANSWER

From ng-book:

When Angular starts to run and generate the view, it will create a binding from the root ng-app element to the $rootScope. This $rootScope is the eventual parent of all $scope objects. The $rootScope object is the closest object we have to the global context in an Angular app. It’s a bad idea to attach too much logic to this global context, in the same way that it’s not a good idea to dirty the JavaScript global scope.

You are right, you should definitely use Services to share data and logic between your modules.

Putting a lot of logic in your $rootScope means having bad maintainability and modularity in your application, it is also very difficult to test issues.

I highly suggest you to take a look at:

I know it may be easy to attach everything to $rootScope, but It is just difficult to work on it, make little changes, reusing your code for other applications or modules and test your application in general.

EDIT

Recently I had to fetch some items from API and catch these items in order to show them in a certain view. The item fetching mechanism was in a certain Factory, while the mechanism to format and show the items was in a Controller.

So, I had to emit an event in the Factory when items got fetched and catch this event in the Controller.

$rootScope way

//Factory
$rootScope.$broadcast('refreshItems', items);
//Controller
$scope.$on('refreshItems', doSomething());

It clearly worked but I didn't really like to use $rootScope and I've also noticed that the performance of that task were pretty miserable.

Then I tried giving a shot to Postal.js:

Postal.js is an in-memory message bus - very loosely inspired by AMQP - written in JavaScript. Postal.js runs in the browser, or on the server using node.js. It takes the familiar "eventing-style" paradigm (of which most JavaScript developers are familiar) and extends it by providing "broker" and subscriber implementations which are more sophisticated than what you typically find in simple event emitting/aggregation.

I tried using Postal.js for this kind of needs and I found out that it is really faster than using $rootScope for this purpose.

//Factory
$scope.$bus.publish({
                  channel : 'reloadItems',
                  topic   : 'reloadItems'
                  data    : items
);

//Controller
$scope.$bus.subscribe({
  channel  : 'reloadItems',
  topic    : 'reloadItems',
  callback : function () {
    resetAndLoadItems();
  }
});

I hope I've been helpful.

1
Wilmer SH On

From Angluar docs: Every application has a single root scope. All other scopes are descendant scopes of the root scope. Scopes provide separation between the model and the view, via a mechanism for watching the model for changes.

Of course this is going to come down to a matter of opinion and style. I tend to follow a style very close to John Papa's Angular Style Guide.

In keeping with the two, and following a good separation of concerns strategy my architecture contains factory models that are shared across the application. My controllers in turn are all bound to the services that hold the shared data.

Using $rootScope as the global event bus is exactly how Angular uses it. Should you tag along and do the same? I don't see why not. But if you are, make sure that the purpose is clearly defined and maybe even use your own service to register events to the global event bus. That way you are decoupling your app from Angular, and if you ever decide that you want to change the framework in which your global event bus lives then you can change it in one place.

This is what I'm suggesting:

Global event bus

// Angular specific: add service to module
angular.module('app').factory('globalEventBus', GlobalEventBus);

// Angular specific: inject dependencies
GlobalEventBus.$inject(['$rootScope']);

// Non framework specific. 
// param: fameworkEventBus will be $rootScope once injected 
function GlobalEventBus(fameworkEventBus) {

  var globalEventBus = this;

  globalEventBus.registerEvent(params...){
    fameworkEventBus.
  }

  return globalEventBus;
}

Global data models

My data models are smart and tend to contain functions that provide information about themselves or retrieve/return specific data.

// Angular specific: add service to module
angular.module('app').factory('dataModel', DataModel);

function DataModel() {

  var dataModel= this;

  dataModel.myData = {};

  dataModel.GetSpecificData = funtion(param){
    return ...
  }

  return dataModel;
}

The controller

// Angular specific
angular.module('app').controller('MyController', MyController);

// Angular specific: inject dependencies to controller
MyController.$inject = ['dataModel'];

// By convention I use the same parameter name as the service.
// It helps me see quickly if my order of injection is correct
function MyController(dataModel) {

  var myController = this;

  // Bind to the service itself, and NOT to the service data property
  myController.myData = dataModel;

  myController.doStuff = function(){
  }
}

Here is a fun post about binding to services and not to service properties.

All in all you have to be the judge of what works best for you. A good system architecture and good style have saved me countless hours of solving completely avoidable problems.

0
James-Jesse Drinkard On

After doing some more work with Angular and more reading I found this basic rule of thumb for using $rootscope that I wanted to add to the other answers:

Only add properties that are static or constant. Anything else that represents a changing state or a mutable value should have a corresponding directive or controller to handle it.