Open jiayisheji opened 4 years ago
Angular提供了许多事件类型来与你的应用进行通信。 Angular中的事件可帮助你在特定条件下触发操作,例如单击,滚动,悬停,聚焦,提交等。
通过事件,可以在Angular应用中触发组件的逻辑。
Angular 组件和 DOM 元素通过事件与外部进行通信, Angular 事件绑定语法对于组件和 DOM 元素来说是相同的 - (eventName)="expression" 。
(eventName)="expression"
DOM 元素触发的一些事件通过 DOM 层级结构传播。这种传播过程称为事件冒泡。事件首先由最内层的元素开始,然后传播到外部元素,直到它们到根元素。DOM 事件冒泡与 Angular 可以无缝工作。
Angular事件分为原生事件和自定义事件:
Angular Events 常用列表
(click)="myFunction()" (dblclick)="myFunction()" (submit)="myFunction()" (blur)="myFunction()" (focus)="myFunction()" (scroll)="myFunction()" (cut)="myFunction()" (copy)="myFunction()" (paste)="myFunction()" (keyup)="myFunction()" (keypress)="myFunction()" (keydown)="myFunction()" (mouseup)="myFunction()" (mousedown)="myFunction()" (mouseenter)="myFunction()" (drag)="myFunction()" (drop)="myFunction()" (dragover)="myFunction()"
默认处理的事件应从原始HTML DOM组件的事件映射:
HTML DOM
关于原生事件有哪些,可以参照W3C标准事件。
只需删除on前缀即可。
on
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<button (click)="myFunction($event)">Click Me</button>', styleUrls: ['./app.component.css'] }) export class AppComponent { myFunction(event) { console.log('Hello World'); } }
当我们点击按钮时候,控制台就会打印Hello World。
Hello World
Angular 允许开发者通过 @Output() 装饰器和 EventEmitter 自定义事件。它不同于 DOM 事件,因为它不支持事件冒泡。
@Output()
EventEmitter
@Component({ selector: 'my-selector', template: ` <div> <button (click)="callSomeMethodOfTheComponent()">Click</button> <sub-component (my-event)="callSomeMethodOfTheComponent($event)"></sub-component> </div> `, directives: [SubComponent] }) export class MyComponent { callSomeMethodOfTheComponent() { console.log('callSomeMethodOfTheComponent called'); } } @Component({ selector: 'sub-component', template: ` <div> <button (click)="myEvent.emit()">Click (from sub component)</button> </div> ` }) export class SubComponent { @Output() myEvent: EventEmitter; constructor() { this.myEvent = new EventEmitter(); } }
自定义事件写法和原生Dom事件一样。那么它们需要注意:
stopPropagation()
$event
EventEmitter.emit()
EventEmitter.next()
在实际项目中,我们经常需要在事件处理器中调用 preventDefault() 或 stopPropagation() 方法。
preventDefault()
还有一个比较少用功能比较强大,stopPropagation 增强版 stopImmediatePropagation()。
stopPropagation
stopImmediatePropagation()
preventDefault()最常见的例子就是 <a> 阻止标签跳转链接
<a>
<a id="link" href="https://www.baidu.com">baidu</a> <script> document.getElementById('link').onclick = function(ev) { ev.preventDefault(); // 阻止浏览器默认动作 (页面跳转) // 处理一些其他事情 window.open(this.href); // 在新窗口打开页面 }; </script>
在Angular中使用:
preventDefault()页面直接使用:
<a id="link" href="https://www.baidu.com" (click)="$event..preventDefault(); myFunction()">baidu</a>
还可以使用:
```html <a id="link" href="https://www.baidu.com" (click)="myFunction($event); false">baidu</a>
stopPropagation()页面直接使用:
<a id="link" href="https://www.baidu.com" (click)="$event.stopPropagation(); myFunction($event)">baidu</a>
在事件处理方法里面使用和原生一样。
myFunction(e: Event) { e.stopPropagation(); e.preventDefault(); // ...code return false; }
看完 Angular 提供写法,写法太麻烦。
项目中最常用当属stopPropagation(),懒惰的程序员就想到各种方法:
方法1:
import {Directive, HostListener} from "@angular/core"; @Directive({ selector: "[click-stop-propagation]" }) export class ClickStopPropagation { @HostListener("click", ["$event"]) public onClick(event: any): void { event.stopPropagation(); } }
弄一个阻止冒泡的指令
<div click-stop-propagation>Stop Propagation</div>
方法2:
import { Directive, EventEmitter, Output, HostListener } from '@angular/core'; @Directive({ selector: '[appClickStop]' }) export class ClickStopDirective { @Output() clickStop = new EventEmitter<MouseEvent>(); constructor() { } @HostListener('click', ['$event']) clickEvent(event: MouseEvent) { event.stopPropagation(); event.preventDefault(); this.clickStop.emit(event); } }
弄一个阻止冒泡的自定义事件指令
<div appClickStop (clickStop)="testClick()"></div>
看起来很不错,就是支持click事件,我要支持多种事件,我需要些更多的指令。
click
用过 Vue - 事件修饰( Event modifiers ) 的同学,一定让使用 Angular 的同学很羡慕。
<button v-on:click="add(1)"></button> # 普通事件 <button v-on:click.once="add(1)"></button> # 这里只监听一次 <a v-on:click.prevent="click" href="http://google.com">click me</a> # 阻止默认事件 <div class="parent" v-on:click="add(1)"> <div class="child" v-on:click.stop="add(1)">click me</div> # 阻止冒泡 </div>
那 Angular 可以实现吗?当然
import { Directive, EventEmitter, Output, HostListener, OnDestroy, OnInit, Input } from '@angular/core'; import { Subject, } from 'rxjs'; import { takeUntil, throttleTime} from 'rxjs/operators'; @Directive({ selector: '[click.stop]', }) export class ClickStopDirective implements OnInit ,OnDestroy{ @Output('click.stop') clickStop = new EventEmitter<MouseEvent>(); /// 自定义间隔 @Input() throttleTime = 1000; click$: Subject<MouseEvent> = new Subject<MouseEvent>() onDestroy$ = new Subject(); @HostListener('click', ['$event']) clickEvent(event: MouseEvent) { event.stopPropagation(); event.preventDefault(); this.click$.next(event); } constructor() { } ngOnInit() { this.click$.pipe( takeUntil(this.onDestroy$), throttleTime(this.throttleTime) ).subscribe((event) => { this.clickStop.emit(event); }) } ngOnDestroy() { /// 销毁并取消订阅 this.onDestroy$.next(); this.onDestroy$.complete(); } }
扩展一个原生事件指令
<div class="parent" (click)="add(1)"> <div class="child" (click.stop)="add(1)">click me</div> </div>
看起来很美好,还支持防抖骚操作,缺点还是支持一个事件,如果需要多种事件需要写更多的事件指令。
Angular 不支持 (事件名.修饰) 这种语法吗?
如果你用过键盘事件,你就会发现,Angular 提供一系列的快捷操作:
当绑定到Angular模板中的keyup或keydown事件时,可以指定键名。 这使得仅在按下特定键时才很容易触发事件。
<input (keydown.enter)="onKeydown($event)">
还可以将按键组合在一起以仅在触发按键组合时触发事件。 在以下示例中,仅当同时按下Control和1键时才会触发事件:
<input (keyup.control.1)="onKeydown($event)">
此功能适用于特殊键和修饰键,例如Enter,Esc,Shift,Alt,Tab,Backspace和Command,但它也适用于字母,数字,方向箭头和F键(F1-F12)。
Enter
Esc
Shift
Alt
Tab
Backspace
Command
<input (keydown.enter)="..."> <input (keydown.a)="..."> <input (keydown.esc)="..."> <input (keydown.shift.esc)="..."> <input (keydown.control)="..."> <input (keydown.alt)="..."> <input (keydown.meta)="..."> <input (keydown.9)="..."> <input (keydown.tab)="..."> <input (keydown.backspace)="..."> <input (keydown.arrowup)="..."> <input (keydown.shift.arrowdown)="..."> <input (keydown.shift.control.z)="..."> <input (keydown.f4)="...">
这个看起来很不错呀,和 Vue 那个事件修饰写法一致。这种可以 Angular 原生实现,那一定有方法可以做到。
我们去查看源码:https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/key_events.ts
在源码里面由一个突出的导入:
import {EventManagerPlugin} from './event_manager';
而的实现,
export class KeyEventsPlugin extends EventManagerPlugin {}
就是继承了这个抽象类
export abstract class EventManagerPlugin { constructor(private _doc: any) {} manager!: EventManager; 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); } }
抽象类里面我们需要实现supports和addEventListener方法。
supports
addEventListener
Dom.addEventListener()
在 DomEventsPlugin 的类实现:
DomEventsPlugin
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function { element.addEventListener(eventName, handler as EventListener, false); return () => this.removeEventListener(element, eventName, handler as EventListener); }
在我们使用 Renderer2.listen 绑定事件时候:如果需要销毁事件
Renderer2.listen
// 绑定事件 const fn = Renderer2.listen(); // 销毁事件 fn();
这种操作就是源码是这样的实现的。
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean): () => void { NG_DEV_MODE && 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; }
关于 Angular Events Plugin 的文章介绍很少,所以很多人不知道可以有以下的骚操作。
我们也来实现事件修饰符:
新建三个文件:
once.plugin.ts stop.plugin.ts prevent.plugin.ts
先从常用的 .stop 开始:
.stop
注意:EventManagerPlugin是一个内部抽象类,所以我们无法扩展它
EventManagerPlugin
import { Injectable, Inject } from '@angular/core'; import { EventManager } from '@angular/platform-browser'; const MODIFIER = '.stop'; @Injectable() export class StopEventPlugin { manager: EventManager; supports(eventName: string): boolean { return eventName.indexOf(MODIFIER) !== -1; } addEventListener( element: HTMLElement, eventName: string, handler: Function ): Function { const stopped = (event: Event) => { event.stopPropagation(); handler(event); } return this.manager.addEventListener( element, eventName.replace(MODIFIER, ''), stopped, ); } addGlobalEventListener(element: string, eventName: string, handler: Function): Function { const stopped = (event: Event) => { event.stopPropagation(); handler(event); } return this.manager.addGlobalEventListener( element, eventName.replace(MODIFIER, ''), stopped, ); } }
我们这里使用先去supports 查询,只有事件名里面有.stop,才会执行StopEventPlugin。
StopEventPlugin
addEventListener里面调用的EventManager.addEventListener,我们只需要对事件处理函数进行包装一下即可:
EventManager.addEventListener
const stopped = (event: Event) => { event.stopPropagation(); handler(event); }
在把包装之后的处理函数返还给EventManager.addEventListener,并且去掉.stop,防止死循环。
.prevent基本和.stop一模一样:
.prevent
import { Injectable, Inject } from '@angular/core'; import { EventManager } from '@angular/platform-browser'; const MODIFIER = '.prevent'; @Injectable() export class PreventEventPlugin { manager: EventManager; supports(eventName: string): boolean { return eventName.indexOf(MODIFIER) !== -1; } addEventListener( element: HTMLElement, eventName: string, handler: Function ): Function { const prevented = (event: Event) => { event.preventDefault(); handler(event); } return this.manager.addEventListener( element, eventName.replace(MODIFIER, ''), prevented, ); } addGlobalEventListener(element: string, eventName: string, handler: Function): Function { const prevented = (event: Event) => { event.preventDefault(); handler(event); } return this.manager.addGlobalEventListener( element, eventName.replace(MODIFIER, ''), prevented, ); } }
.once 有点特殊:
.once
import { Injectable, Inject } from '@angular/core'; import { EventManager } from '@angular/platform-browser'; const MODIFIER = '.once'; @Injectable() export class OnceEventPlugin { manager: EventManager; supports(eventName: string): boolean { return eventName.indexOf(MODIFIER) !== -1; } addEventListener( element: HTMLElement, eventName: string, handler: Function ): Function { const fn = this.manager.addEventListener( element, eventName.replace(MODIFIER, ''), (event: Event) => { handler(event); fn(); }, ); return () => {}; } addGlobalEventListener(element: string, eventName: string, handler: Function): Function { const fn = this.manager.addGlobalEventListener( element, eventName.replace(MODIFIER, ''), (event: Event) => { handler(event); fn(); }, ); return () => {}; } }
fn 返回的就是 return () => this.removeEventListener(element, eventName, handler as EventListener);,.once操作就是使用一次就注销事件操作。所以我们先把fn获取到,然后事件调用完成以后取消绑定即可。最后要返回一个空函数,不然手动销毁事件就会抛出错误。
return () => this.removeEventListener(element, eventName, handler as EventListener);
在根模块注册插件:
import { EVENT_MANAGER_PLUGINS } from '@angular/platform-browser'; import { PreventEventPlugin } from './prevent.plugin'; import { StopEventPlugin } from './stop.plugin'; import { OnceEventPlugin } from './once.plugin'; @NgModule({ imports: [BrowserModule, FormsModule], declarations: [ AppComponent, ], providers: [ ...., { provide: EVENT_MANAGER_PLUGINS, useClass: PreventEventPlugin, multi: true, }, { provide: EVENT_MANAGER_PLUGINS, useClass: StopEventPlugin, multi: true, }, { provide: EVENT_MANAGER_PLUGINS, useClass: OnceEventPlugin, multi: true, }, ], bootstrap: [AppComponent] }) export class AppModule { }
这样我们就可以正常使用了
<a href="https://www.baidu.com" (click.prevent)="onConsole($event)">baidu</a> <div (click)="onConsole($event)"> 标题 <div (click.stop)="onConsole($event)">内容</div> </div> <form (submit.stop)="onConsole($event)"> <input name="username"> <button type="submit">提交</button> </form> <div (click)="onConsole($event)"> 标题 <div (click.once)="onConsole($event)">内容</div> </div>
事件处理函数:
onConsole($event: Event) { console.log('onConsole',$event.target) }
我们已经实现普遍版本的事件修饰,如果想要加上防抖,节流更风骚的操作我们该如何做了,这个留个大家一个悬念,可以思考一下,欢迎和我交流心得。
最后:我们不光可以做事件修饰插件还可以做事件打印日志插件,你看完上面的例子,应该很简单操作了。如果不知道怎么下手,欢迎和我交流心得。
今天就到这里吧,伙计们,玩得开心,祝你好运
Angular提供了许多事件类型来与你的应用进行通信。 Angular中的事件可帮助你在特定条件下触发操作,例如单击,滚动,悬停,聚焦,提交等。
通过事件,可以在Angular应用中触发组件的逻辑。
Angular事件
Angular 组件和 DOM 元素通过事件与外部进行通信, Angular 事件绑定语法对于组件和 DOM 元素来说是相同的 -
(eventName)="expression"
。DOM 元素触发的一些事件通过 DOM 层级结构传播。这种传播过程称为事件冒泡。事件首先由最内层的元素开始,然后传播到外部元素,直到它们到根元素。DOM 事件冒泡与 Angular 可以无缝工作。
Angular事件分为原生事件和自定义事件:
Angular Events 常用列表
默认处理的事件应从原始
HTML DOM
组件的事件映射:只需删除
on
前缀即可。当我们点击按钮时候,控制台就会打印
Hello World
。Angular 允许开发者通过
@Output()
装饰器和EventEmitter
自定义事件。它不同于 DOM 事件,因为它不支持事件冒泡。自定义事件写法和原生Dom事件一样。那么它们需要注意:
stopPropagation()
方法解决问题,但实际工作中,不建议这样使用。on
前缀,方法名可以使用on
开头,参考风格指南-不要给输出属性加前缀$event
返回是 DOM Events$event
返回是EventEmitter.emit()
传递的值,也可以使用EventEmitter.next()
。事件修饰
在实际项目中,我们经常需要在事件处理器中调用
preventDefault()
或stopPropagation()
方法。preventDefault()
最常见的例子就是<a>
阻止标签跳转链接在Angular中使用:
preventDefault()
页面直接使用:还可以使用:
stopPropagation()
页面直接使用:在事件处理方法里面使用和原生一样。
看完 Angular 提供写法,写法太麻烦。
项目中最常用当属
stopPropagation()
,懒惰的程序员就想到各种方法:方法1:
弄一个阻止冒泡的指令
方法2:
弄一个阻止冒泡的自定义事件指令
看起来很不错,就是支持
click
事件,我要支持多种事件,我需要些更多的指令。用过 Vue - 事件修饰( Event modifiers ) 的同学,一定让使用 Angular 的同学很羡慕。
那 Angular 可以实现吗?当然
扩展一个原生事件指令
看起来很美好,还支持防抖骚操作,缺点还是支持一个事件,如果需要多种事件需要写更多的事件指令。
Angular 不支持 (事件名.修饰) 这种语法吗?
如果你用过键盘事件,你就会发现,Angular 提供一系列的快捷操作:
当绑定到Angular模板中的keyup或keydown事件时,可以指定键名。 这使得仅在按下特定键时才很容易触发事件。
还可以将按键组合在一起以仅在触发按键组合时触发事件。 在以下示例中,仅当同时按下Control和1键时才会触发事件:
此功能适用于特殊键和修饰键,例如
Enter
,Esc
,Shift
,Alt
,Tab
,Backspace
和Command
,但它也适用于字母,数字,方向箭头和F键(F1-F12)。这个看起来很不错呀,和 Vue 那个事件修饰写法一致。这种可以 Angular 原生实现,那一定有方法可以做到。
我们去查看源码:https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/key_events.ts
在源码里面由一个突出的导入:
而的实现,
就是继承了这个抽象类
抽象类里面我们需要实现
supports
和addEventListener
方法。Dom.addEventListener()
方法。默认使用冒泡在
DomEventsPlugin
的类实现:在我们使用
Renderer2.listen
绑定事件时候:如果需要销毁事件这种操作就是源码是这样的实现的。
关于 Angular Events Plugin 的文章介绍很少,所以很多人不知道可以有以下的骚操作。
我们也来实现事件修饰符:
Renderer2.listen
绑定事件实现stopPropagation()
实现。preventDefault()
实现。新建三个文件:
先从常用的
.stop
开始:注意:
EventManagerPlugin
是一个内部抽象类,所以我们无法扩展它我们这里使用先去
supports
查询,只有事件名里面有.stop
,才会执行StopEventPlugin
。addEventListener里面调用的
EventManager.addEventListener
,我们只需要对事件处理函数进行包装一下即可:在把包装之后的处理函数返还给
EventManager.addEventListener
,并且去掉.stop
,防止死循环。.prevent
基本和.stop
一模一样:.once
有点特殊:fn 返回的就是
return () => this.removeEventListener(element, eventName, handler as EventListener);
,.once
操作就是使用一次就注销事件操作。所以我们先把fn获取到,然后事件调用完成以后取消绑定即可。最后要返回一个空函数,不然手动销毁事件就会抛出错误。在根模块注册插件:
这样我们就可以正常使用了
事件处理函数:
我们已经实现普遍版本的事件修饰,如果想要加上防抖,节流更风骚的操作我们该如何做了,这个留个大家一个悬念,可以思考一下,欢迎和我交流心得。
最后:我们不光可以做事件修饰插件还可以做事件打印日志插件,你看完上面的例子,应该很简单操作了。如果不知道怎么下手,欢迎和我交流心得。
今天就到这里吧,伙计们,玩得开心,祝你好运