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


公司項目開發用的是angularjs,關於事件通訊一直用的是EventBus,直到上周寫一個小組件懶得引用EventBus時,想到用angularjs自帶的事件通信時,結果很尷尬的忘記原生方法單詞怎么寫了....

可能現在記錄這個真的算很晚了,包括對於顯得有些老舊的angularjs,但我們學習的畢竟是思想,而非框架,所以還是獨立一篇文章來聊聊angularjs中的事件通信$on,$emit與$broadcast。

一、為什么要用事件通信?

為什么要用事件通信?肯定要用啊,不用又解決不了問題,只能用事件通信維持生活這樣子;在聊這個之前,先簡單說說常見的跨作用域通信的幾種場景。

問題情景一:父controller傳子controller

我們在用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來解決這個問題:

<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繼承,使用了組件的屬性也順利完成了傳值,問題不大。

問題情景三:異步跨作用域傳值

有時候,我們在父作用域拿值是一個異步操作,我們不知道什么時候才能拿到值,這也決定了我們不知道什么時候才可以把這個值傳給子作用域。

舉個實際的例子,現在需求要做一個loading組件,頁面初始化時顯示loading,當請求完成,由父作用域通知loading組件隱藏loading效果。

很明顯,此時通過bindings依舊能解決問題,難度不大。

上面三種情景均為父作用域傳值給子作用域,我們現在反過來,通通由子作用域傳父作用域,怎么解決?有什么一種方法能簡單的的處理這三種情況呢?當然有,事件通信。

二、什么是事件通信?

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提供了用於監聽的方法,還分別提供了父傳子,子傳父的方法,所以很好解決前面提到的子作用域希望傳值給父作用域的情況。

三、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派發過來的數據,如果沒有,可以不寫。

四、一個例子

這里模擬一個前面提到的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解決父子組件異步傳值 這篇文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM