AngularJS - Using a Service to Communicate Between Two Different Controllers

Monday, January 6, 2014

Introduction

AngularJS, being an MVC type of framework allows you to have a controller that is responsible for marshaling data to and from a view on your web page.

Well, what if you have a multiple controllers on a page that are completely independent from one another, but in certain cases you want those controllers to be able to communicate back and forth. I say completely independent, because you could nest the controllers, making a parent-child relationship, and then you could use the $rootScope object to communicate with each other.

How $rootScope Works

Every AngularJS application has exactly one $rootScope. All other scopes inside the application are children of the $rootScope. So in the case of nested controllers, the child controller's scope object is a descendent of the parent scope which is ultimately a descendent of the application $rootScope. So, for example, if you try to evaluate {{message.text}} on a child scope. and If that property does not exist on the child scope, AngularJS will then evaluate the parent scope for that property, and then ultimately it will evaluate the $rootScope.

So, in the case of controllers that have a parent-child relationship, you can use the existing inheritance between the controller to facilitate the communication.

Using Factories and Services to Facilitate Communication Across Independent Controllers

In the case your controllers are independent from each other, then you will need a service that will link the two controllers. In my example, I am using a factory which essentially a service that returns an object.

My example is just a web page with two text boxes that chat with each other. Each text box is in a different controller, but each controller has the list of message from both text boxes.

Here is the HTML.

    <div ng-controller="Controller1">
      <h1>Controller 1</h1>
      <ul ng-repeat="msg1 in messages1 track by msg1.id">
        <li>{{msg1.text}}</li>
      </ul>
      <p>
        Controller 1 Message: <input type="text" ng-model="text">
        <button ng-click="postMessage()">Post</button>
      </p>
    </div>
    <p></p>
    <div ng-controller="Controller2">
      <h1>Controller 2</h1>
      <ul ng-repeat="msg2 in messages2 track by msg2.id">
        <li>{{msg2.text}}</li>
      </ul>
      <p>
        Controller 1 Message: <input type="text" ng-model="text">
        <button ng-click="postMessage()">Post</button>
      </p>
    </div>

Each ng-controller section above has a copy of the same messages that is sent out by the service that manages the message list.

So to link the two controllers above, I have added a Angular Factory that will be injected into each controller. Here is the code.

app.factory('messageService', function ($rootScope) {
    var messenger = {
        messages: [],
        identity: 0,
        addMessage: function (text, caller) {
 
            this.identity += 1;
            var id = this.identity,
                message = {
                    text: caller + text,
                    id: id
                };
 
            this.messages.push(message);
            $rootScope.$broadcast('messageAdded');
        }
    };
 
    return messenger;
});

In this service, I am injecting the $rootScope, which as available throughout the Angular application. I have a function called addMessage which, when called by a controller, adds a message to the collection of messages and then broadcasts that the message was added using the $broadcast function. Also, keep in mind that a factory in Angular is a singleton object, so there is only one occurrence of its state in the current Angular application, and any other object that access its state will get what ever values it hold at that current time.

Looking at the code for the controllers, you will see that each of the controllers injects the service and calls the addMessage function when a user enters a text message and clicks the Post button.

app.controller('Controller1', function ($scope, messageService) {
    $scope.messages1 = messageService.messages;
    $scope.post = {
        text: ''
    };
 
    $scope.postMessage = function () {
        console.log($scope.post);
        messageService.addMessage($scope.text, "controller 1");
    };
 
    $scope.$on('messageAdded', function () {
        $scope.messages1 = messageService.messages;
    });
});

Each controller also has $on listener which is listening for the messageAdded broadcast. When this broadcast is detected by the controller, it fires the function to update the message collection retrieving the updated message list from the service.

I am finding this approach useful when the application you are working on gets a bit more complex and you have a lot of controllers on a page responsible for different things. In certain cases you may want to update a portion of the page that is managed by a controller, when a user clicks on another part of page managed by a different controller.

Here is the Plunker example

http://plnkr.co/edit/UA28cXfF0I8r3M54DyYa?p=preview

 

comments powered by Disqus