公司項目開發用的是angularjs,關於事件通信一直用的是EventBus,直到上週寫一個小組件懶得引用EventBus時,想到用angularjs自帶的事件通訊時,結果很尷尬的忘記原生方法單詞怎麼寫了....html
可能如今記錄這個真的算很晚了,包括對於顯得有些老舊的angularjs,但咱們學習的畢竟是思想,而非框架,因此仍是獨立一篇文章來聊聊angularjs中的事件通訊$on,$emit與$broadcast。angularjs
1、爲何要用事件通訊?小程序
爲何要用事件通訊?確定要用啊,不用又解決不了問題,只能用事件通訊維持生活這樣子;在聊這個以前,先簡單說說常見的跨做用域通訊的幾種場景。框架
問題情景一:父controller傳子controllerdom
咱們在用angularjs平常開發中,跨做用域傳值十分常見;好比父級做用域有一個屬性,想跨做用域傳遞給子級,初學者可能習慣把這個屬性綁在父級scope上,經過做用域繼承讓子級做用域能夠直接使用。異步
<div ng-controller="parentCtrl"> <span>我是父做用域</span> <div ng-controller="childCtrl">我是子做用域:{{name}}</div> </div>
let parentCtrl = function ($scope) { //父做用域定義name屬性 $scope.name = "聽風是風"; }; let childCtrl = function ($scope) { //子做用域經過繼承獲得name屬性 }; angular.module('myApp', []) .controller("parentCtrl", ['$scope', parentCtrl]) .controller("childCtrl", ['$scope', childCtrl])
這是一種解決方法,可是將全部屬性方法綁在scope上並非很好的作法,這會讓父子做用域的屬性顯得特別混亂。(先不談controller as vm);函數
問題情景二:父做用域傳值給子組件學習
這樣的狀況也十分常見,父做用域有個值在子組件中也須要使用,友好一點,咱們經過bindings來解決這個問題:this
<div ng-controller="parentCtrl as vm"> <ting-feng echo="vm.name"></ting-feng> </div>
let parentCtrl = function () { let vm = this; vm.name = '聽風是風'; }; angular.module('myApp', []) .controller('parentCtrl', parentCtrl) .component('tingFeng', { template: '<div>我是子組件,個人名字是:{{self.echo}}</div>', controllerAs: 'self', bindings: { echo: '<' }, controller: function () {} });
能夠看到,此次沒利用scope繼承,使用了組件的屬性也順利完成了傳值,問題不大。spa
問題情景三:異步跨做用域傳值
有時候,咱們在父做用域拿值是一個異步操做,咱們不知道何時才能拿到值,這也決定了咱們不知道何時才能夠把這個值傳給子做用域。
舉個實際的例子,如今需求要作一個loading組件,頁面初始化時顯示loading,當請求完成,由父做用域通知loading組件隱藏loading效果。
很明顯,此時經過bindings依舊能解決問題,難度不大。
上面三種情景均爲父做用域傳值給子做用域,咱們如今反過來,統統由子做用域傳父做用域,怎麼解決?有什麼一種方法能簡單的的處理這三種狀況呢?固然有,事件通訊。
2、什麼是事件通訊?
angularjs的事件通訊分爲監聽和派發兩個重要階段,有人(無中生有)對於監聽和派發理解老是很模糊,其實不難理解。
你們在爲dom綁定事件時必定用過事件監聽addEventListener,好比:
document.getElementById("#div").addEventListener("click", function () { console.log('我被點擊啦'); });
在上述代碼中,咱們爲一個id爲div的元素添加了一個事件監聽,它由click觸發;當用戶點擊這個元素,就會執行事件監聽的回調函數,也就是咱們但願點擊後執行的操做。
angularjs的事件通訊其實和這個有些相似,咱們老是先去監聽一個東西,而後在知足某個狀況下再去觸發它,執行你想要的回調。
好比我在前面舉的loading組件的例子,首先loading默認顯示,而且監聽控制是否顯示的字段;當父做用域請求後臺完畢,調用父傳子的方法;子組件響應監聽,執行回調拿到狀態值,並修改loading的顯示狀態。咱們拆分步驟:
1.loading子組件默認顯示,監聽isShow字段。
2.父做用域請求完畢,對應isShow字段派發事件。
3.子組件做響應監聽,修改isShow,隱藏loading;
記住,永遠都是先監聽,後派發,這就像咱們永遠都是先聲明函數,後調用它;若是你調用一個都爲聲明的函數,又怎麼起做用域呢?
angularjs提供了用於監聽的方法,還分別提供了父傳子,子傳父的方法,因此很好解決前面提到的子做用域但願傳值給父做用域的狀況。
3、angularjs事件通訊$on,$emit,$broadcast方法
1.$broadcast方法
父做用域傳給子做用域使用的方法,常見寫法:
$scope.$broadcast(eventName,data);
eventName:父傳子派發事件名稱,與子做用域中的監聽名保持一致,必寫項。
data:須要傳遞的數據若是沒有,能夠不寫。
注意:此方法通常寫在某個觸發條件中,好比請求完成後派發;被點擊事件包裹,點擊時派發,不能直接寫;緣由前提說過了,事件通訊永遠知足一點先監聽後派發,你得保證監聽在派發前初始化完成,這個涉及到了angularjs聲明週期的問題,這裏先不細談。
2.$emit方法
子做用域傳給父做用域使用的方法,常見寫法:
$scope.$emit(eventName,data);
eventName:子傳父派發事件名稱,與父做用域中的監聽名保持一致,必寫項。
data:須要傳遞的數據,若是沒有,能夠不寫。
3.$on方法
監聽方法,與$emit,$broadcast配合使用,好比在父做用域派發給子,父做用域中使用$broadcast方法,那麼對應的子做用域中就是用$on方法進行監聽。常見寫法:
$scope.$on('eventName', function (event, data) { // do something... });
eventName:監聽名,與子傳父,或父傳子的事件派發名相同,必寫。
event:有多個方法屬性,這個對象使用很少。
currentScope:響應派發的當前做用域
targetScope:派發事件的原始做用域
name:事件名
defaultPrevented:默認爲false
preventDefault:調用此方法defaultPrevented會變爲true。
data:派發過來的數據,若是沒有,能夠不寫。
4、一個例子
這裏模擬一個前面提到的loading加載的例子,假設3秒後數據請求完成,則派發事件,通知子組件關閉loading顯示。這裏咱們沒有傳遞任何值給子組件,只是單純的通知子組件要去作什麼。
<div ng-controller="parentCtrl as self"> <on-loading></on-loading> </div>
let parentCtrl = ($scope, $timeout) => { //三秒後派發事件,通知loading組件關閉顯示 $timeout(() => { $scope.$broadcast('loadingEnd'); }, 3000); }; angular.module('myApp', []) .controller("parentCtrl", ['$scope', '$timeout', parentCtrl]) .component('onLoading', { template: '<b>{{vm.num}}---{{vm.loading}}</b>', controllerAs: 'vm', controller: function ($scope, $interval) { let vm = this; //初始化loading狀態,默認顯示 vm.loading = 'loading進行中'; vm.num = 3; $interval(() => { vm.num ? vm.num-- : null; }, 1000) //監聽,用於響應事件派發 $scope.$on('loadingEnd', () => { vm.loading = 'loading已關閉'; }); } });
大體效果以下:
我在前面說,公司對於angularjs事件通訊使用的是EventBus,爲何呢?主要是由於angularjs並未提供兄弟之間通訊的方法,而使用EventBus不用考慮這點,無論什麼做用域,都能很便捷的通訊。
我後面會專門花一篇文章介紹EventBus,加上最近和同窗打算一塊兒開個一個我的的小程序,可能將來時間也比較緊張,反正必定會更新,也是爲了本身。
那麼本文就寫到這裏了。
2019.9.17:解決父子組件異步傳值,不用事件通訊,只用ng-if也能搞定,有興趣閱讀 angularjs ng-if妙用,ng-if解決父子組件異步傳值 這篇文章。