Almost everyone in the world is asking! Is there anyone outside? Here is USS AngularJS. We are in trouble. Our service is Klingon and our controllers cannot communicate with their Ferengi commands. Can anyone help us!
I don't know how many times I've encountered this kind of question about what is the best way to communicate with components in AngularJS. Many times the answer is to use the $rootScope object to broadcast a message to anyone who wants to listen to it. However, that's not the best way to do this. Broadcasting messages between components means they need to know how much details of other components encoding, which limits their modularity and reuse.
In this article, I will show how to use the publish/subscribe mode for internal component communication in AngularJS.
AngularJS has a variety of ways to use for communication between components, and the most commonly used methods require you to know too much details about how those components communicate, which increases the coupling between components and reduces their modularity and cohesion. This makes it difficult for your components to reuse in other applications.
By using the publish/subscribe design pattern, we can reduce the coupling between components and encapsulate the details of communication between them. This will help increase the modularity, testability, and reusability of your components.
I'll describe the Publish/Subscribe mode implementation by Eric Burley, @eburley in its postObserve, about publishing subscription model..Recommended in.
The sample application I described will show you how you can apply publish/subscribe mode to internal controller communications as well as the controller's service communications. You can use my library on GitHubangularjs-pubsub The source code is found below.
First we need a communication pipeline
First, let’s talk about the services used to process publishing and subscribing information. I define a service interface that provides a way to publish and subscribe to information, which we can use to process the information we want to use to exchange.
In the following code, I define two internal information; _EDIT_DATA_, used to indicate that we need to edit the data that follows the information, and _DATA_UPDATED_, used to indicate that our data has been changed. These are all defined internally and users cannot access them, which helps hide specific implementations.
For each message, there are two methods; one is used to publish information and push it to the subscriber, and the other can allow the subscriber to register a callback method. When the information is received, this method will be called.
The method used to publish information to subscribers is editData, on line 9, and dataUpated, on line 19. They push private notifications to pending events through the $rootScope.$broadcast method.
The method used to register events is to establish listening through $scope.$on. When a broadcast message is received, the events registered by the subscriber to the service will be executed in turn. At the same time, since subscribers need their own scope to pass as parameters, we can use it to perform the listening information, thus avoiding the complex processing of maintaining the listener list. The method to register an event is onEditData, on line 13, and onDataUpdated in line 23.
To hide implementation details, I used the Revealing Module mode, returning only those methods I want the user to use.
(['']) // define the request notification channel for the pub/sub service .factory('requestNotificationChannel', ['$rootScope', function ($rootScope) { // private notification messages var _EDIT_DATA_ = '_EDIT_DATA_'; var _DATA_UPDATED_ = '_DATA_UPDATED_'; // publish edit data notification var editData = function (item) { $rootScope.$broadcast(_EDIT_DATA_, {item: item}); }; //subscribe to edit data notification var onEditData = function($scope, handler) { $scope.$on(_EDIT_DATA_, function(event, args) { handler(); }); }; // publish data changed notification var dataUpdated = function () { $rootScope.$broadcast(_DATA_UPDATED_); }; // subscribe to data changed notification var onDataUpdated = function ($scope, handler) { $scope.$on(_DATA_UPDATED_, function (event) { handler(); }); }; // return the publicly accessible methods return { editData: editData, onEditData: onEditData, dataUpdated: dataUpdated, onDataUpdated: onDataUpdated }; }])
Post a message
Posting a message is very simple. First, we need to introduce some dependencies for the requestNotificationChannel in our controller. You can see this in the second line of the dataService definition below. When an event occurs, if you need to send a signal to other objects that need to know that there is a change, you only need to call the appropriate notification method on the requestNotificationChannel. If you notice the saveHop, deleteHop and addHop methods of the dataService, you will see that they all call the dataUpdated method on the requestNotificationChannel. This method will send a signal to the listener, and the listener has been registered with the onDataUpdated method.
// define the data service that manages the data .factory('dataService', ['requestNotificationChannel', function (requestNotificationChannel) { // private data var hops = [ { "_id": { "$oid": "50ae677361d118e3646d7d6c"}, "Name": "Admiral", "Origin": "United Kingdom", "Alpha": 14.75, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": "Bittering hops derived from Wye Challenger. Good high-alpha bittering hops. Use for: Ales Aroma: Primarily for bittering Substitutions: Target, Northdown, Challenger", "Type": "Bittering", "Form": "Pellet", "Beta": 5.6, "HSI": 15.0, "Humulene": 0.0, "Caryophyllene": 0.0, "Cohumulone": 0.0, "Myrcene": 0.0, "Substitutes": ""} , { "_id": { "$oid": "50ae677361d118e3646d7d6d"}, "Name": "Ahtanum", "Origin": ".", "Alpha": 6.0, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": "Distinctive aromatic hops with moderate bittering power from Washington. Use for: Distinctive aroma Substitutes: N/A", "Type": "Aroma", "Form": "Pellet", "Beta": 5.25, "HSI": 30.0, "Humulene": 0.0, "Caryophyllene": 0.0, "Cohumulone": 0.0, "Myrcene": 0.0, "Substitutes": ""} , { "_id": { "$oid": "50ae677361d118e3646d7d6e"}, "Name": "Amarillo Gold", "Origin": ".", "Alpha": 8.5, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": "Unknown origin, but character similar to Cascade. Use for: IPAs, Ales Aroma: Citrus, Flowery Substitutions: Cascade, Centennial", "Type": "Aroma", "Form": "Pellet", "Beta": 6.0, "HSI": 25.0, "Humulene": 0.0, "Caryophyllene": 0.0, "Cohumulone": 0.0, "Myrcene": 0.0, "Substitutes": ""} , { "_id": { "$oid": "50ae677361d118e3646d7d6f"}, "Name": "Aquila", "Origin": ".", "Alpha": 6.5, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": "Aroma hops developed in 1988. Limited use due to high for: Aroma hops Substitutes: ClusterNo longer commercially grown.", "Type": "Aroma", "Form": "Pellet", "Beta": 3.0, "HSI": 35.0, "Humulene": 0.0, "Caryophyllene": 0.0, "Cohumulone": 0.0, "Myrcene": 0.0, "Substitutes": ""} , { "_id": { "$oid": "50ae677361d118e3646d7d70"}, "Name": "Auscha (Saaz)", "Origin": "Czech Republic", "Alpha": 3.3, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": " Use for: Pilsners and Bohemian style lagers Aroma: Delicate, mild, clean, somewhat floral -- Noble hops Substitute: Tettnanger, LublinExamples: Pulsner Urquell", "Type": "Aroma", "Form": "Pellet", "Beta": 3.5, "HSI": 42.0, "Humulene": 0.0, "Caryophyllene": 0.0, "Cohumulone": 0.0, "Myrcene": 0.0, "Substitutes": ""} , ]; // sends notification that data has been updated var saveHop = function(hop) { (); }; // removes the item from the array and sends a notification that data has been updated var deleteHop = function(hop) { for(var i = 0; i < ; i++) { if(hops[i]._id.$oid === hop._id.$oid) { (i, 1); (); return; } }; }; // internal function to generate a random number guid generation var S4 = function() { return (((1+())*0x10000)|0).toString(16).substring(1); }; // generates a guid for adding items to array var guid = function () { return (S4() + S4() + "-" + S4() + "-4" + S4().substr(0,3) + "-" + S4() + "-" + S4() + S4() + S4()).toLowerCase(); }; // function to add a hop to the array and sends a notification that data has been updated var addHop = function(hop) { .$oid = guid(); (hop); (); }; // returns the array of hops var getHops = function() { return hops; }; // returns a specific hop with the given id var getHop = function(id) { for(var i = 0; i < ; i++) { if(hops[i]._id.$oid === id) { return hops[i]; } }; }; // return the publicly accessible methods return { getHops: getHops, getHop: getHop, saveHop: saveHop, deleteHop: deleteHop, addHop: addHop } }]);
Receive event notifications
It is also easy to receive event notifications from requestNotificationChannel. The extra one needs to just callback processors to use notifications to do some of their own processing when the message is sent. We will again need to add some dependencies to our requestNotificationChannel for our controllers, services, and instructions, which you can see in the second line of the code below. Next we need to define an event callback processor to respond to event notifications, which you can see in the fifth line of the code below. Then we need to call the onDataUpdated method to register our callback processor to requestNotificationChannel and pass in the scope from the controller and callback processor. We did these things in the 9th line of the code.
//define the controller for view1 .controller('view1-controller', ['$scope', 'dataService', 'requestNotificationChannel', function($scope, dataService, requestNotificationChannel) { $ = (); var onDataUpdatedHandler = function() { $ = (); } ($scope, onDataUpdatedHandler); $ = function(hop) { (hop); } $ = function(hop) { (hop); } }]);
Controller for controller communication
We can also use the requestNotificationChannel for communication between controllers. We only need one controller to play the role of publisher and another controller to play the role of subscriber. If you observe the onEdit method of view1-controller in line 11 of the previous code, you will see that it sends an editData message containing items that need to be edited using requestNotificationChannel. The following view2-controller registers its onEditDataHandler with requestNotificationChannel from line 5 to line 9. In this way, whenever view1-controller sends an editData message, brings the item to be modified, the view2-controller will be notified by the editData message, obtains the item and updates it to its model.
//define the controller for view1 .controller('view2-controller', ['$scope', 'dataService', 'requestNotificationChannel', function($scope, dataService, requestNotificationChannel) { $ = null; var onEditDataHandler = function(item) { $ = item; }; ($scope, onEditDataHandler); $ = function() { ($); $ = null; } $ = function() { $ = null; } }]);
Write a good interface document
One thing that may be ignored is that we use communication interfaces between components, and these interfaces need a good documentation to illustrate how to use them. In the above example, if there is no document, the user will definitely not know that onEditData will pass a data to the callback function to be edited. So when you start using this pattern, the good trick is to write comment documents to make sure the notification service knows exactly what is going on.
Summarize
OK, we've explored how to use subscription/release mode to enable inter-module communication in your AngularJS application. This mode allows your module to be decoupled from internal messages, making it easier to reuse. You can even replace all communication between modules with subscription/publish mode. This mode is very effective especially when there are many asynchronous requests in your service and you want to cache data in the service to reduce communication with the server.
I hope this helps you, you can do it in my GitHub repositoryangularjs-pubsub The code for the example is found below.