Angular 4.x 事件管理器及自定义EventManagerPlugin

在 Angular 中如何为同一个表达式绑定多个事件呢?如果我们这样做可能会是这样的:

<div><button (click, mouseover)="onClick()">Click me</button>
</div>复制代码

在继续分析绑定多个事件之前,我们先来分析一下,如果在模板中绑定一个事件如 click 事件,Angular 是如何工作的?

<div><button (click)="onClick()">Click me</button>
</div>复制代码

Angular 在解析 DOM 树的时候,对于事件绑定它会调用 DomRenderer 实例的 listen() 方法,进行事件绑定,listen() 方法具体实现如下:

// angular2/packages/platform-browser/src/dom/dom_renderer.ts
class DefaultDomRenderer2 implements Renderer2 {....listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):() => void {checkNoSyntheticProp(event, 'listener');if (typeof target === 'string') {return <() => void>this.eventManager.addGlobalEventListener(target, event, decoratePreventDefault(callback));}return <() => void>this.eventManager.addEventListener(target, event, decoratePreventDefault(callback)) as() => void;}
}复制代码

通过源码我们发现,不管走哪条分支,最终都是调用 this.eventManager 对象的方法设置事件监听。这里的 this.eventManager 是什么?它是 Angular 中的事件管理器 EventManager,我们先来会会它。

EventManager (事件管理器)

在 Angular 中所有的事件绑定都是由一个事件管理器来驱动,事件管理器本身由多个事件插件提供支持。Angular 中内置的事件插件如下:

  • KeyEventsPlugin - 处理键盘事件
  • HammerGesturesPlugin - 处理手势
  • DomEventsPlugin - 处理 DOM 事件

看完上面的内容,相信很多人也会有疑问 - EventManager 到底是如何管理不同事件的呢?要揭开这背后的秘密,我们的唯一途径就是看源码,因为它是最诚实的,它对你毫无保留,此刻脑海中突然想起一首歌:

美丽的神话

解开我 最神秘的等待
星星坠落 风在吹动
终于再将你拥入怀中
….

爱是心中唯一不变美丽的神话

放松一下,马上回到正题 - EventManager 类:

EventManager 类

// angular2/packages/platform-browser/src/dom/events/event_manager.ts
export class EventManager {// EventManagerPlugin列表private _plugins: EventManagerPlugin[]; // 缓存已匹配的eventName与对应的插件private _eventNameToPlugin = new Map<string, EventManagerPlugin>();constructor(@Inject(EVENT_MANAGER_PLUGINS) plugins: EventManagerPlugin[], private _zone: NgZone) {plugins.forEach(p => p.manager = this);/*** {provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},* {provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},* {provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true}* * slice(): 创建新的plugins数组* reverse(): 让DomEventsPlugin插件作为列表最后一项,因为它能够处理所有的事件。*/this._plugins = plugins.slice().reverse();}// 获取能处理eventName的插件,并调用对应插件提供的addEventListener()方法addEventListener(element: HTMLElement, eventName: string,handler: Function): Function {const plugin = this._findPluginFor(eventName);return plugin.addEventListener(element, eventName, handler);}// 获取能处理eventName的插件,并调用对应插件提供的addGlobalEventListener()方法addGlobalEventListener(target: string, eventName: string, handler: Function): Function {const plugin = this._findPluginFor(eventName);return plugin.addGlobalEventListener(target, eventName, handler);}// 获取NgZonegetZone(): NgZone { return this._zone; }/** @internal */_findPluginFor(eventName: string): EventManagerPlugin {// 优先从_eventNameToPlugin对象中获取eventName对应的EventManagerPluginconst plugin = this._eventNameToPlugin.get(eventName);  if (plugin) {return plugin;}// 遍历插件列表,判断当前插件是否支持eventName对应的事件名const plugins = this._plugins;for (let i = 0; i < plugins.length; i++) {const plugin = plugins[i];if (plugin.supports(eventName)) {this._eventNameToPlugin.set(eventName, plugin);return plugin;}}throw new Error(`No event manager plugin found for event ${eventName}`);}
}复制代码

相关说明

  • 在 addEventListener() 或 addGlobalEventListener() 方法内部都会调用 _findPluginFor() 方法,查询对应的能够处理 eventName 对应的 EventManagerPlugin 插件对象。
  • _findPluginFor() 方法中,会遍历插件列表,然后以 eventName 作为参数调用插件对象提供的 supports() 方法,判断当前是否能够处理 eventName 对应的事件。因此对于 EventManagerPlugin 插件对象,如果要声明能够处理某类事件,就需要在 supports() 方法中进行相应处理。
  • DomEventsPlugin 插件作为列表最后一项,因为它能够处理所有的事件。
  • KeyEventsPlugin、HammerGesturesPlugin、DomEventsPlugin 插件类都继承于 EventManagerPlugin 抽象类。

EventManagerPlugin 抽象类

export abstract class EventManagerPlugin {constructor(private _doc: any) {}manager: EventManager;// 判断是否支持eventName对应的事件abstract supports(eventName: string): boolean;// 添加事件监听abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function;// 添加全局的事件监听addGlobalEventListener(element: string, eventName: string, handler: Function): Function {const target: HTMLElement = getDOM().getGlobalEventTarget(this._doc, element);if (!target) {throw new Error(`Unsupported event target ${target} for event ${eventName}`);}return this.addEventListener(target, eventName, handler);};
}复制代码

时机已成熟,接下来我们开始实现上述的功能。

自定义插件

Step 1: Creating a new plugin

正如上面提到的,我们希望在我们的 Angular 模板上有多个事件绑定到同一个表达式:

<div><button (click, mouseover)="onClick()">Click me</button>
</div>复制代码

如果是这样,我们的 supports() 函数的内部规则应该很清楚。我们需要一个字符串,其中有一个或多个逗号,分隔事件名称。当人们把一些愚蠢的东西放在(,click)中时,我们也应该处理。所以我们的 supports() 函数如下:

getMultiEventArray(eventName: string): string[] {return eventName.split(",").filter((item, index): boolean => { return item && item != '' })
}supports(eventName: string): boolean {return this.getMultiEventArray(eventName).length > 1
}复制代码

这将允许 EventManager 将事件字符串如 (click, mouseover) 委派给此插件。

Step 2: Implementing the eventListeners

现在我们已经实现了supports() 方法,EventManager 将调用 plugin.addEventListener() 方法,因此插件需要实现 addEventListener() 方法,从而实现我们的自定义行为。我们的自定义行为很简单 - 为我们解析的eventArray 中的所有事件添加事件侦听器。

addEventListener

addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {let zone = this.manager.getZone();let eventsArray = this.getMultiEventArray(eventName);// Entering back into angular to trigger changeDetectionlet outsideHandler = (event: any) => {zone.runGuarded(() => handler(event));};// Executed outside of angular so that change detection is not // constantly triggered.let addAndRemoveHostListenersForOutsideEvents = () => {eventsArray.forEach((singleEventName: string) => {this.manager.addEventListener(element, singleEventName, outsideHandler);});}return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);}复制代码

addGlobalEventListener

 addGlobalEventListener(target: string, eventName: string, handler: Function): Function {let zone = this.manager.getZone();let eventsArray = this.getMultiEventArray(eventName);let outsideHandler = (event: any) => zone.runGuarded(() => handler(event));return this.manager.getZone().runOutsideAngular(() => {eventsArray.forEach((singleEventName: string) => {this.manager.addGlobalEventListener(target, singleEventName, outsideHandler);})});
}复制代码

Step 3: Register plugin

import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser';@NgModule({...providers: [{ provide: EVENT_MANAGER_PLUGINS, useClass: MultiEventPlugin, multi: true }]
})
export class AppModule { }复制代码

完整示例

multi-event.plugin.ts

import { Injectable, Inject } from '@angular/core';
import { EventManager, DOCUMENT, ɵd as EventManagerPlugin } from '@angular/platform-browser';/*** Support Multi Event*/
@Injectable()
export class MultiEventPlugin extends EventManagerPlugin {manager: EventManager;constructor( @Inject(DOCUMENT) doc: any) { super(doc); }getMultiEventArray(eventName: string): string[] { return eventName.split(",")   // click,mouseover => [click,mouseover].filter((item, index): boolean => { return item && item != '' })}supports(eventName: string): boolean {return this.getMultiEventArray(eventName).length > 1;}addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {let zone = this.manager.getZone();let eventsArray = this.getMultiEventArray(eventName);// Entering back into angular to trigger changeDetectionlet outsideHandler = (event: any) => {zone.runGuarded(() => handler(event));};// Executed outside of angular so that change detection is// not constantly triggered.let addAndRemoveHostListenersForOutsideEvents = () => {eventsArray.forEach((singleEventName: string) => {this.manager.addEventListener(element, singleEventName, outsideHandler);});}return this.manager.getZone().runOutsideAngular(addAndRemoveHostListenersForOutsideEvents);}addGlobalEventListener(target: string, eventName: string, handler: Function): Function {let zone = this.manager.getZone();let eventsArray = this.getMultiEventArray(eventName);let outsideHandler = (event: any) => zone.runGuarded(() => handler(event));return this.manager.getZone().runOutsideAngular(() => {eventsArray.forEach((singleEventName: string) => {this.manager.addGlobalEventListener(target, singleEventName, outsideHandler);});});}
}复制代码

app.component.ts

import { Component } from '@angular/core';@Component({selector: 'exe-app',template: `<div><button (click,mouseover)="onClick()">Click me</button></div>`
})
export class AppComponent {onClick() {console.log('Click');}
}复制代码

app.module.ts

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';import { AppComponent } from './app.component';
import { MultiEventPlugin } from './plugins/multi-event.plugin';@NgModule({imports: [BrowserModule],declarations: [AppComponent],bootstrap: [AppComponent],providers: [{ provide: EVENT_MANAGER_PLUGINS, useClass: MultiEventPlugin, multi: true }],schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }复制代码

参考资源

  • Hacking Angular2: Binding Multiple DOM Events

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/290803.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dell服务器报内存配置不正确,DELL 服务器系统提示错误解决的若干办法

《DELL 服务器系统提示错误解决的若干办法》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《DELL 服务器系统提示错误解决的若干办法(9页珍藏版)》请在人人文库网上搜索。1、DELL 服务器有时会若硬件的改动&#xff0c;在开机以后会提示错误信息。信息一般会提示在显示…

JSP PO VO BO DTO POJO DAO解释

PO &#xff1a;persistent object持久对象 1 &#xff0e;有时也被称为Data对象&#xff0c;对应数据库中的entity&#xff0c;可以简单认为一个PO对应数据库中的一条记录。2 &#xff0e;在hibernate持久化框架中与insert/delet操作密切相关。 3 &#xff0e;PO中不应该包含任…

java之RSA和Base64加密帮助类

1、RSAUtils.java类 package com.sangfor.vpn.client.service.utils; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigIn…

更强的压缩比!PostgreSQL开始支持Zstd

文 | 局长出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013&#xff09;PostgreSQL 现已通过其 TOAST 存储技术提供压缩支持&#xff0c;并且在过去的一年里构建了 LZ4 压缩支持——用于压缩 WAL、备份压缩以及其他用途&#xff0c;现在 PostgreSQL 开发者正准备通过 …

最近面试遇到的技术问题

京东性能组 1. oracle awr2. mysql 慢查询3. redis详细架构、如何插入数据4. jmeter使用及集群搭建5. nginx使用及tomcat集成 6. 数据库及sql优化的方案 7. 写存储过程 8. linux使用9. shell10. java e代驾 1. 接口测试方法2. sockets连接建立方法3. http三次握手过程 京东金融…

小程序和app用什么样的服务器,小程序和APP的本质区别是什么?哪个更值得开发?...

从微信小程序和用户见面到现在&#xff0c;这个功能已经越来越完善了&#xff0c;经过更彻底的挖掘&#xff0c;商业价值被挖掘出来了&#xff01;小程序和app有什么区别呢&#xff1f;为什么广州会更受欢迎呢&#xff1f;两者的区别首先&#xff0c;两者的区别在于&#xff0c…

jbpm6.5 环境搭建(三) 数据库 切换

2019独角兽企业重金招聘Python工程师标准>>> 经过一晚上的折腾&#xff0c;终于搞定&#xff0c;成功切换Mysql 步骤一&#xff1a; 安装mysql 数据库 创建数据库 名字为jbpm 设置用户名密码 我本地默认使用 root 步骤二&#xff1a; ** 修改配置文件 ** F:\jb…

Android之HandlerThread源码分析和简单使用(主线程和子线程通信、子线程和子线程通信)

1、先熟悉handler方式实现主线程和子线程互相通信方式&#xff0c;子线程和子线程的通信方式 如果不熟悉或者忘记了&#xff0c;请参考我的这篇博客 Android之用Handler实现主线程和子线程互相通信以及子线程和子线程之间的通信 http://blog.csdn.net/u011068702/arti…

CENTOS6.4安装vnc-server

yum install tigervnc-servervi /etc/sysconfig/vncservers只需要两类内容就可以了&#xff0c;一个是定义用户&#xff0c;一个是定义用户登录情况&#xff1a;参考&#xff1a;VNCSERVERS"1:root 2:river"VNCSERVERARGS[1]"-geometry 800x600 -nolisten tcp&q…

Avalonia跨平台入门第八篇之控件的拖放

在前面分享的几篇中咱已经玩耍了Popup、ListBox多选、Grid动态分、RadioButton模板,过程还算顺利;今天接着把把ListBox中的Item拖放到Cavans中(基于官方的Samples实现的);直接看效果吧:1、ListBox中PointerPressed、DragOver事件:2、Canvas中的Drop事件:3、控件的移除无非就是通…

sql server 之函数小技巧 整数类型为空是用空字符串替代实现

1、判空函数 说明&#xff1a;使用指定的替换值替换 NULL。 语法&#xff1a;ISNULL ( check_expression , replacement_value ) 参数&#xff1a; check_expression&#xff1a;将被检查是否为 NULL 的表达式。check_expression 可以为任何类型。 replacement_value&#xff1…

车牌识别系统连不上服务器怎么办,车牌识别系统出现故障的解决方法有哪些?...

在日常生活中&#xff0c;各个小区、商业广场、酒店、办公楼等等地方出入口装置有车牌识别系统&#xff0c;有此可见车牌识别系统的使用越来越广泛。停车场办理系统的使用给人们带来便利的同时&#xff0c;也常常会出现一些小问题。今天小编就给大家分享一下车牌识别系统遇到故…

霍夫变换

作者&#xff1a;桂。 时间&#xff1a;2017-04-24 12:18:17 链接&#xff1a;http://www.cnblogs.com/xingshansi/p/6756305.html 前言 今天群里有人问到一个图像的问题&#xff0c;但本质上是一个基本最小二乘问题&#xff0c;涉及到霍夫变换&#xff08;Hough Transform&a…

ASP.NET Core 实现基于 ApiKey 的认证

ASP.NET Core 实现基于 ApiKey 的认证Intro之前我们有介绍过实现基于请求头的认证&#xff0c;今天来实现一个基于 ApiKey 的认证方式&#xff0c;使用方式参见下面的示例Sample注册认证服务services.AddAuthentication().AddApiKey(options >{options.ApiKey "123456…

Android之调用系统分享

1、调用系统分享关键代码 private void shareImage() {Intent intent = new Intent(Intent.ACTION_SEND); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File("sdcard/screenshot.png")));intent.setTyp…

hdu4750Count The Pairs(最小生成树找瓶颈边)

1 /*2 题意&#xff1a;就是给你一个图&#xff0c;图的每两个点都有多条路径&#xff0c;每一条路径中都有一条最大边&#xff0c;3 所有最大边的最小边&#xff08;也就是瓶颈边&#xff09;就是这两点之间的val值&#xff01;然后给你一个值f&#xff0c;4 问有多少…

服务器的响应一直一直发送不过去,zeroRPC:在发送响应后继续运行进程

我使用Python2.7和zeroRPC使客户机和服务器通信。我希望客户端向服务器发送一个请求&#xff0c;我希望服务器发送一个响应以确认它已收到请求。但是我希望服务器对该请求执行一些繁重的计算。这些计算将花费数小时&#xff0c;并且不会产生任何响应&#xff0c;因此客户机不应…

白平衡自己主动(AWB)算法---2,颜色计算

本文说明了白平衡算法估计当前场景的色温过程. 色温计算的原理并不复杂,但要做到,还是一道&#xff0c;认真做好每一步,这需要大量的测试,和算法一直完好. 关于该过程首先简要: 1, 取的图像数据,并划分MxN块,如果是25x25,并统计每一块的基本信息(,白色像素的数量及R/G/B通道的分…

【机房收费系统】多么痛的领悟

这次机房收费系统&#xff0c;是全部的项目中&#xff0c;自己完毕的最不惬意的了。 时间之长。效率之慢。一開始。就感觉无从下手&#xff0c;但总会相信自己能慢慢的进入状态。最终有机会自己练练手了。也自觉得之前自己设计模式学的还不错。也最终有机会能自己想想设计模式了…

linux(windows)之svn重定向地址

1、问题 svn下载的项目路径需要换&#xff0c;也就是下面的URL:SVN:// 需要修改 2、解决办法 linux平台 svn switch --relocate oldSvnPath newSvnPath windows平台 右击项目 TortoiseSVN->Relocate 然后修改就行