博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Angular 4.x 事件管理器及自定义EventManagerPlugin
阅读量:5949 次
发布时间:2019-06-19

本文共 9860 字,大约阅读时间需要 32 分钟。

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

复制代码

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

复制代码

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

// angular2/packages/platform-browser/src/dom/dom_renderer.tsclass 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.tsexport class EventManager {  // EventManagerPlugin列表  private _plugins: EventManagerPlugin[];   // 缓存已匹配的eventName与对应的插件  private _eventNameToPlugin = new Map
(); 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); } // 获取NgZone getZone(): NgZone { return this._zone; } /** @internal */ _findPluginFor(eventName: string): EventManagerPlugin { // 优先从_eventNameToPlugin对象中获取eventName对应的EventManagerPlugin const 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 模板上有多个事件绑定到同一个表达式:

复制代码

如果是这样,我们的 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 changeDetection        let 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 changeDetection        let 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: `    
`})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 { }复制代码

参考资源

转载地址:http://uvsxx.baihongyu.com/

你可能感兴趣的文章
Shell编程基础
查看>>
Shell之Sed常用用法
查看>>
3.1
查看>>
校验表单如何摆脱 if else ?
查看>>
<气场>读书笔记
查看>>
领域驱动设计,构建简单的新闻系统,20分钟够吗?
查看>>
web安全问题分析与防御总结
查看>>
React 组件通信之 React context
查看>>
Linux下通过配置Crontab实现进程守护
查看>>
ios 打包上传Appstore 时报的错误 90101 90149
查看>>
密码概述
查看>>
jQuery的技巧01
查看>>
基于泛型实现的ibatis通用分页查询
查看>>
gopacket 使用
查看>>
AlertDialog对话框
查看>>
我的友情链接
查看>>
linux安全---cacti+ntop监控
查看>>
鸟哥的linux私房菜-shell简单学习-1
查看>>
nagios配置监控的一些思路和工作流程
查看>>
通讯组基本管理任务三
查看>>