• 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解决父子组件异步传值 这篇文章。

  • 相关阅读:
    Vue入门系列(四)之Vue事件处理
    Vue入门系列(五)Vue实例详解与生命周期
    微信为啥不能直接下载.apk安装包
    Oracle行转列SQL
    MyISAM 和InnoDB区别
    jQuery easyui datagrid数据绑定
    js调用百度地图API创建地图,搜索位置
    python tornado框架使用
    python数据库连接池
    python操作数据库
  • 原文地址:https://www.cnblogs.com/echolun/p/11052966.html
Copyright © 2020-2023  润新知