angularjs事件通訊$on,$emit,$broadcast詳解

公司項目開發用的是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解決父子組件異步傳值 這篇文章。

相關文章
相關標籤/搜索