歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> AngularJS 中的通信(發布訂閱模式)

AngularJS 中的通信(發布訂閱模式)

日期:2017/3/1 9:39:13   编辑:Linux編程

現在幾乎滿世界的人都在問! 外面有人麼? 這裡是 USS AngularJS, 我們遇到麻煩了,我們的服務講得是克靈貢語(Klingon) 而我們的控制器不能同它們的Ferengi 指令通信了. 有人能幫助我們麼!

我已經不知道有多少次遇到這種有關什麼才是AngularJS裡面的組件通信的最佳方式這樣的問題了. 很多時候答案都會是為此使用 $rootScope 對象去向任何想要收聽的人廣播$broadcast出一條消息. 然而,那還真不是做這件事的最佳方式. 組件之間廣播消息意味著它們需要多少知道一些其它組件編碼的細節,這樣就限制了它們的模塊化和重用.

本文我就將展示如何為AngularJS中的內部組件通信使用發布/訂閱模式.

AngularJS 有多種方式可供你用於組件之間的通信,而最常使用的方法卻需要你知道太多有關那些組件如何通信的細節,這樣就增加了組件之間的耦合度,並降低了它們的模塊性和內聚程度. 這樣也就使得你的組件很難在其它應用程序中重用.

通過使用發布/訂閱設計模式,我們可以降低組件之間的耦合度,並將它們的之間通信的細節封裝起來. 這將能幫助增加你組件的模塊化程度,可測試性以及可重用性.

我將會描述的發布/訂閱模式實現由 Eric Burley, @eburley 在它的帖子angularjs.org 觀察, 有關發布訂閱模式.. 中推薦過。

我所描述的示例應用程序,會向你展示你可以將發布/訂閱模式如何運用於內部控制器通信以及控制器的服務通信. 你可以在GitHub上我的資源庫 angularjs-pubsub 下面找到源代碼.

首先我們需要一條通信管道

首先我們來講講用於處理發布和訂閱信息的服務。我定義了一個服務接口,提供了發布和訂閱信息的方法,我們可以用它來處理我們想要用來交換的信息。

在下面的代碼中,我定義了兩個內部信息; _EDIT_DATA_, 用來表示我們需要編輯跟隨信息傳過來的數據,和 _DATA_UPDATED_, 用來表示我們的數據已經被改變。這些都是定義在內部的,用戶沒辦法訪問到它們的,這樣有助於隱藏具體實現。

而對於每條信息,有兩個方法; 一個用來發布信息推送給訂閱者,另一個可以讓訂閱者注冊一個回調方法,當接收到信息的時候,這個方法就會被調用。

用來向訂閱者發布信息方法是 editData,在第 9 行,還有 dataUpated,在第 19 行。它們通過 $rootScope.$broadcast 方法向待處理事件推送私有通知。

用來注冊事件的方法,通過 $scope.$on 建立監聽,當接收到廣播的消息之後,就會輪流執行那些被訂閱者注冊到服務上的事件。同時,由於訂閱者需要自己的 scope 作為參數傳過來,我們可以用它來執行監聽的信息,從而避免了維護監聽者列表這些復雜的處理。注冊事件的方法是 onEditData,在 13 行,還有 onDataUpdated 在 23 行。

為了隱藏實現細節,我用了 Revealing Module (揭示模塊:好丑的名字)模式,只返回那些我希望讓用戶使用的方法。

angular.module(['application.services'])
// 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(args.item);
});
};
// 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
};
}])

發布消息

發布消息很簡單,首先我們需要在我們的控制器裡為 requestNotificationChannel 引入一些依賴. 你可以在下面dataService的定義第二行看到這個. 當事件發生時,如果需要向需要了解有變化發生的其它對象發送信號, 你只需要調用requestNotificationChannel上恰當的通知方法就可以了. 如果你注意到了dataService的 saveHop, deleteHop 和 addHop 方法, 你就會看到它們都調用了 requestNotificationChannel 上的dataUpdated方法, 這個方法將會給偵聽器發送信號,偵聽器則已經用 onDataUpdated 方法注冊過了.

// 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": "U.S.", "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": "U.S.", "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": "U.S.", "Alpha": 6.5, "Amount": 0.0, "Use": "Boil", "Time": 0.0, "Notes": "Aroma hops developed in 1988. Limited use due to high cohumolone.Used 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) {
requestNotificationChannel.dataUpdated();
};
// removes the item from the array and sends a notification that data has been updated
var deleteHop = function(hop) {
for(var i = 0; i < hops.length; i++) {
if(hops[i]._id.$oid === hop._id.$oid) {
hops.splice(i, 1);
requestNotificationChannel.dataUpdated();
return;
}
};
};
// internal function to generate a random number guid generation
var S4 = function() {
return (((1+Math.random())*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) {
hops.id.$oid = guid();
hops.push(hop);
requestNotificationChannel.dataUpdated();
};
// 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 < hops.length; 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
}
}]);

接收事件通知

從 requestNotificationChannel 接收事件通知也很簡單,額外的我們只需要回調處理器來在消息被發送時使用通知來做一些自己的處理. 我們將再次需要添加一些依賴到面向我們的控制器、服務以及指令的 requestNotificationChannel 上, 你可以在下面代碼的第二行中看到這些. 接下來我們需要定義一個事件回調處理器來對事件通知做出回應,你可以在下面的第五行代碼中看到. 然後我們需要通過調用 onDataUpdated 方法來吧我們的回調處理器注冊到requestNotificationChannel,並傳入來自控制器和回調處理器的范圍, 我們在第9行代碼中做了這些事情.

//define the controller for view1 .controller('view1-controller', ['$scope', 'dataService', 'requestNotificationChannel', function($scope, dataService, requestNotificationChannel) { $scope.hops = dataService.getHops(); var onDataUpdatedHandler = function() { $scope.hops = dataService.getHops(); } requestNotificationChannel.onDataUpdated($scope, onDataUpdatedHandler); $scope.onEdit = function(hop) { requestNotificationChannel.editData(hop); } $scope.onDelete = function(hop) { dataService.deleteHop(hop); } }]);

用於控制器通信的控制器

我們也可以將 the requestNotificationChannel 用於控制器間的通信. 我們只需要有一個控制器扮演發布者的角色,而另外一個控制器扮演訂閱者的角色就行了. 如果你觀察到前段代碼第11行view1-controller的onEdit方法,你會看到它發送了一個editData消息,消息包含需要使用 requestNotificationChannel 編輯的項. 下面的 view2-controller 從第5行到第9行將它的 onEditDataHandler 用 requestNotificationChannel 進行了注冊. 如此無論何時view1-controller一旦發送editData消息,帶上要修改的項,view2-controller都會受到editData消息的通知,獲得該項並將其更新到它的模型.

//define the controller for view1 .controller('view2-controller', ['$scope', 'dataService', 'requestNotificationChannel', function($scope, dataService, requestNotificationChannel) { $scope.hop = null; var onEditDataHandler = function(item) { $scope.hop = item; }; requestNotificationChannel.onEditData($scope, onEditDataHandler); $scope.onSave = function() { dataService.saveHop($scope.hop); $scope.hop = null; } $scope.onCancel = function() { $scope.hop = null; } }]);

寫一個好的接口文檔

有一件事情可能會被忽略,我們在組件間用了通信接口,而這些接口,它們需要一個好的文檔來說明應當如何使用。上面的例子中,如果沒有文檔,用戶肯定不會知道 onEditData 會給回調函數傳一個待編輯數據。所以當你開始用這個模式,用好的技巧在於,給方法寫注釋文檔,以確保通知服務明確知道發生了什麼事情。

總結

好了,我們探討了如何在你的 AngularJS 應用中使用訂閱/發布模式來實現模塊間通信。該模式可以讓你的模塊從內部消息解耦,更便於復用。你甚至可以把模塊之間的通信全部替換成訂閱/發布模式。尤其當你的服務中有很多異步請求,以及你希望把數據緩存在服務中,從而減少和服務器通信的時候,這種模式相當有效。

我希望這對你有所幫助,你可以在我的 GitHub 倉庫 angularjs-pubsub 下找到例子的代碼。

希望你喜歡,並分享我的工作~帶你走近AngularJS系列

  1. 帶你走近AngularJS - 基本功能介紹 http://www.linuxidc.com/Linux/2014-05/102140.htm
  2. 帶你走近AngularJS - 體驗指令實例 http://www.linuxidc.com/Linux/2014-05/102141.htm
  3. 帶你走近AngularJS - 創建自定義指令 http://www.linuxidc.com/Linux/2014-05/102142.htm

如何在 AngularJS 中對控制器進行單元測試 http://www.linuxidc.com/Linux/2013-12/94166.htm

在 AngularJS 應用中通過 JSON 文件來設置狀態 http://www.linuxidc.com/Linux/2014-07/104083.htm

AngularJS 之 Factory vs Service vs Provider http://www.linuxidc.com/Linux/2014-05/101475.htm

AngularJS —— 使用 ngResource、RESTful APIs 和 Spring MVC 框架提交數據 http://www.linuxidc.com/Linux/2014-07/104402.htm

AngularJS 的詳細介紹:請點這裡
AngularJS 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved