index-1-1.jpg
  • Aurora Solutions

AngularJS: Cross Component Communication

In this article we will discuss different techniques for communicating across different components in angular i.e directives, controllers. It’s a common problem that you need some type of communication between different controllers so that you can use some functionalities offered by one controller in another controller. Today we will look at this problem. In order to understand this article you should have basic knowledge of angular. The three techniques we will discuss today are:

  • Communicating with inherited scopes

  • Communicating with events

  • Communicating with services

We will take a simple scenario of a shopping cart, where some items will be displayed on the page with a button “add to cart” next to them, upon clicking on this button, this will just add the item in the cart. For simplicity we shall be just incrementing a variable in a controller when items are added. So we will have a separate controller “CartWidgetController” whose job is to just increment in a variable when an item is added to the cart. We will use some styling by bootstrap in order to have a little bit good look and feel of application. So here is how the app will look like:

So in the above diagram you can see the Items available for adding to cart are displayed as a list with a button “Add to cart” next to them. Upon clicking on this button item will be added to the cart. You can see number of items in cart at top right in a widget in gray color. You can also see number of items in cart below the list.

Diagram

You can see in the diagram that ‘CartWidgetController’ is so simple and small. The purpose of this controller is to use it as a widget on anywhere in the page. The purpose of this controller is to just show number of items in the cart so it has only one variable “totalItems” which will updated when a new item is added to the cart. So in next sections we can see how can we communicate between these controllers.

Communicating With Inherited Scopes

The first technique we will take a look at it is; using “inherited scopes”. As it is clear from it’s name that we will use some sort of child parent relationship of scopes. So let’s suppose we have to communicate between two controllers or we have to call some methods of one controller in another controller; what we need to do is, to make the second controller the child of first i.e. we will make the first controller scope parent of second controller so that scope of first controller can be accessible in second controller. So let’s take a look at how we can achieve this in our example. We will use plunker to show you the demo and all the links will be provided so that you can view it. So let’s start by looking in our script.js file:

// Code goes here
var app= angular.module("app",[]);

app.controller("ItemsController",function($scope){
  $scope.cartItems=0;
  $scope.items=["Laptop","Mobile","Tablet","Xbox"];
  $scope.addToCart=function (item){
    $scope.addItem();
    $scope.cartItems++;
  }
});

app.controller("CartWidgetController",function($scope){
  $scope.totalItems=0;
  $scope.addItem=function(){
    $scope.totalItems++;
  }
});

So in this file we have first created angular module and then two controllers, “ItemsController” and “CartWidgetController”. So you have seen the cart controller is simple just one method and a field. So this is pretty simple you can put this controller anywhere where you want to show number of items in cart. So now the question is how are we calling “addItem” function from ItemsController. To answer this lets first dive into index.html page.

<!DOCTYPE html>
<html>

<head>
  <script data-require="jquery@*" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" data-semver="3.3.1" data-require="bootstrap@*" />
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js" data-semver="3.3.1" data-require="bootstrap@*"></script>
  <script src="https://code.angularjs.org/1.4.0-beta.5/angular.js" data-semver="1.4.0-beta.5" data-require="angular.js@*"></script>
  <link href="style.css" rel="stylesheet" />
  <script src="script.js"></script>
</head>

<body ng-app="app">

  <div class="container">
    <div ng-controller="CartWidgetController">

      <div class="well">
        <span class="badge pull-right">Items: {{totalItems}}</span>
        Demo Shopping cart
      </div>

      <div class="well" ng-controller="ItemsController">
        <div>Items:</div>
        <ul ng-repeat="item in items">
          <li>{{item}}
            <span>
              <button class="btns btn-info" ng-click="addToCart(item)">Add to cart</button>
            </span>
          </li>
        </ul>
        <div>
          {{cartItems}} items are in cart.
        </div>
      </div>

    </div>
  </div>
</body>

</html>

As you can see in the HTML is that ItemsController is the child of CartWidgetControllerso the scope of cart controller will be inherited to ItemsController or in other words the ItemsController functionality is available in CartWidgetController. So you can use this inherited scope technique to accomplish the communication in the needed components.

Benefits & Drawbacks:

There are some drawbacks to this method though. We have to go down to add the things in parent scope. As this is the CartWidgetController it has no job to do with adding items to the cart it is just the widget to show number of items in the cart the real cart can be extra controller doing its job. So although there are some great benefits like easy to use and code but there are some drawbacks like putting things in the parent scope which are not needed there.

You can check and play with this code here

Communicating With Events:

The next technique we will take a look for communication is; communicating with events. As it’s name depicts that we will raise some sort of event from one component and handle that event in another component. Or a sort of publish subscribe method. Angular includes a global event bus that allows you raise event on one scope and let other scopes to listen this event and handle them accordingly. The listening to an event is easy you simply call $on method with parameter of the name of the event. Raising the event on the other hand is little complex and requires some planning. $broadcast method on scope let you to broadcast event but has some limitation it will broadcast the event on the child scopes not the sibling one’s if you want to raise an event from child scope and handle in parent you can use $emit method. So if you want to communicate between siblings controller you have to bring $rootScope which is the parent of all. Angular has a $rootScope any other scope will be child on it so you can raise event using $rootScope.broadcast method to forward event to all child scopes. So let’s see how we can do this in our example, here is how our script.js file looks like:

// Code goes here
var app= angular.module("app",[]);

app.controller("ItemsController",function($scope,$rootScope){
  $scope.cartItems=0;
  $scope.items=["Laptop","Mobile","Tablet","Xbox"];
  $scope.addToCart=function (item){
    $scope.cartItems++;
    $rootScope.$broadcast("item:added", item);
  }
});

app.controller("CartWidgetController",function($scope){
  $scope.totalItems=0;
  $scope.$on('item:added', function(event, item) {
    $scope.totalItems++;
  })
});

Look at the code and notice that we have brought in the $rootScope in the “ItemsController” and call $broadcast method with event name “item:added” and pass the item as argument. In the “CartWidgetController” we are listening to the event using $on method with event name and a function that will handle the functionality and receives the event object and item. We can use this item to do anything needed in this controller. Notice that how simple is the communication. Now lets see our index.html file:

<!DOCTYPE html>
<html>

<head>
  <script data-require="jquery@*" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" data-semver="3.3.1" data-require="bootstrap@*" />
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js" data-semver="3.3.1" data-require="bootstrap@*"></script>
  <script src="https://code.angularjs.org/1.4.0-beta.5/angular.js" data-semver="1.4.0-beta.5" data-require="angular.js@*"></script>
  <link href="style.css" rel="stylesheet" />
  <script src="script.js"></script>
</head>

<body ng-app="app">
  <div class="container">
    
    <div ng-controller="CartWidgetController">
      <div class="well">
        <span class="badge pull-right">Items: {{totalItems}}</span>
        Demo Shopping cart
      </div>
    </div>
    
    <div class="well" ng-controller="ItemsController">
      <div>Items:</div>
      <ul ng-repeat="item in items">
        <li>{{item}}
          <span>
              <button class="btns btn-info" ng-click="addToCart(item)">Add to cart</button>
            </span>
        </li>
      </ul>
      <div>
        {{cartItems}} items are in cart.
      </div>
    </div>
    
  </div>
</body>

</html>

If you review code clearly you can see that ItemsController is not now the child of “CartWidgetController”. But the scope of CartWidgetController is though the child of rootScope. So here is how the application is working we are raising “item:added” on rootScope and listening on the CartWidgetController which is the child of rootScope.

Benefits & Drawbacks:

There are also few drawbacks of this approach. The first is that we have to bring in the $rootScope and call a $broadcast on it. This means that anybody can listen to this event as it is broadcasted on the rootscope which is not safe. And any listener can cancel the this event so event will not be propagated to remaining listeners which can lead to bad bugs. And if some handler has cancel the event and some other listener still needs to listen it and process it, it will lead to some strange bugs which will be difficult to diagnose. The next problem is that this event is global so we are coupling our controllers to the event. So even we are good than using inherited scopes but it is still form of collaboration. Another drawback is the event name is string and it is little bit difficult to prevent naming conflicts. However good naming strategy could avoid this problem. Another drawback to global event bus is that some objects can hijack an inappropiate event because it happened to be called at the right time. So that totally destroys the purpose of creating event. This makes maintenance and refactoring little bit difficult.

So we have seen there are some benefits and some drawbacks of this approach.

You can view and play with the code here.

Communicating With Services:

The next technique we will take a look at is, communicating with services. In this technique we will use service for communication. So in our scenario there will be some cart service that will handle all business logic related to cart. So let’s first take a look at the code:

// Code goes here
var app = angular.module("app", []);

//cart service
app.factory("cartService", function() {
  var callbacks = [];
  var items = 0;
  var addItemToCart = function(item) {
    items++;
    //notify if there are any listeners
    var i = 0;
    for (i = 0; i < callbacks.length; i++)
      callbacks[i](items);
  }
  //register listener
  var onItemsAdded = function(callback) {
    callbacks.push(callback);
  }
  return {
    onItemsAdded: onItemsAdded,
    addItemToCart: addItemToCart
  }
});

app.controller("ItemsController", function($scope, cartService) {
  $scope.cartItems = 0;
  $scope.items = ["Laptop", "Mobile", "Tablet", "Xbox"];
  $scope.addToCart = cartService.addItemToCart;
  //subscribe items added callback
  cartService.onItemsAdded(function(items) {
    $scope.cartItems = items;
  });
});

app.controller("CartWidgetController", function($scope, cartService) {
  $scope.totalItems = 0;
  //subscribe items added callback
  cartService.onItemsAdded(function(items) {
    $scope.totalItems = items;
  });
});

Notice in the code that we have a “cartService” which is responsible for adding items to the cart and notifying about items added to the cart. So we have an array of callbacks anybody who is interested in listening to get notification when the items changed can use this service and provide a callback function. So cartService will send notification whenever the items are added to the cart. Notice that by this approach our controllers are free and are sharing data using a service. As services in angular are singletons so we can keep record of number of items in the cart. So for simplicity we are just incrementing the items when items are added but in real example they will be removing and lot more functionality this was just for the demo purpose. The index page is similar and does not contain any change.

You can use this technique to implement full publish subscribe pattern and then you can also communicate.

Benefits & Drawbacks:

So by this approach the benefits are that our controllers are clean now and business logic has been moved to service. And we can register as many callbacks as we want. You can notice that you just need to call “addItemToCart” from the “ItemsController” and you don’t need to take care of publishing or notifying other component which in our case is “CartWidgetController”, it is now cartService job to add items to cart and notify the listeners, so it is so simple. On the other hand it requires more setup work that is we may need to write some extra code. But overall this technique has lots of benefits over other techniques.

You can view and play with this code here.


Summary: In this article we had seen different techniques of communicating between different components in angular. We have also taken a look at all these techniques with examples and drawbacks. If you have any question and queries regarding this feel free to put your questions in the comments.

5 views0 comments

Recent Posts

See All

Creating Custom Directives In AngularJS

Directive is a core feature in AngularJS framework and the framework provides different directives like ng-repeat, ng-bind, ng-controller. As directives are an important part of AngularJS, we will hav

AngularJS service types with examples

In this article I will show with code examples how we can create different AngularJS service types (value, factory, service, provider, constant). We will go through all of them one by one. But one thi