Angular
中的装饰器可以简单的总结为以下几句
- 它是一个表达式
- 该表达式被执行后,返回一个函数
- 函数的入参分别为
target
、name
和 descriptor
- 执行该函数后,可能返回
descriptor
对象,用于配置 target
对象
它分为一下四类
- 类装饰器 (
Class decorators
)
- 属性装饰器 (
Property decorators
)
- 方法装饰器 (
Method decorators
)
- 参数装饰器 (
Parameter decorators
)
TypeScript 中的装饰器
先来了解一下 TypeScript
当中的装饰器,它们有以下这些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void
declare type PropertyDecorator = (target:Object, propertyKey: string | symbol ) => void
declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol, descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number ) => void
|
Angular 内置装饰器
在 Angular
当中已经为我们内置了一些装饰器,如下
- 类装饰器:
@Component
,@NgModule
,@Pipe
,@Injectable
- 属性装饰器:
@Input
,@Output
,@ContentChild
,@ContentChildren
,@ViewChild
,@ViewChildren
- 方法装饰器:
@HostListener
,@HostBinding
- 参数装饰器:
@Inject
,@Optional
,@Self
,@SkipSelf
,@Host
有些是默认生成组件的时候就自带了的,有的使用频率也较低,所以我们在这里主要介绍 @Input
,@Output
,@ViewChild
,@ViewChildren
,@HostListener
和 @HostBinding
六种
Input
是属性装饰器,用来定义组件内的输入属性,一般用来实现父组件向子组件传递数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { Component, Input } from '@angular/core' @Component({ selector: 'exe-counter', template: ` <p>当前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` }) export class CounterComponent { @Input() count: number = 0
increment() { this.count++ }
decrement() { this.count-- } }
import { Component } from '@angular/core'
@Component({ selector: 'exe-app', template: ` <exe-counter [count]="initialCount"></exe-counter> ` })
export class AppComponent { initialCount: number = 5 }
|
Input
装饰器支持一个可选的参数,用来指定组件绑定属性的名称,如果没有指定,则默认使用 @Input
装饰器装饰的属性名,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13
| export class CounterComponent { @Input('value') count: number = 0 }
@Component({ selector: 'exe-app', template: ` <exe-counter [value]="initialCount"></exe-counter> ` })
|
另外还可以使用 inputs
属性将绑定的输入属性名称直接写到 @Component({})
的元数据当中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export class CounterComponent { @Input('value') count: number = 0 }
@Component({ selector: 'exe-app', template: ` <exe-counter [value]="initialCount"></exe-counter> `, inputs: ['count: value'] })
export class CounterComponent { count: number = 0 }
|
不过需要注意的是,不能同时使用 @Input
装饰器,或在 @Directive
、@Component inputs
字段中定义同一个输入属性
1 2 3 4 5 6 7 8 9
| @Component({ selector: 'exe-counter', inputs:['count: value'] })
export class CounterComponent { @Input('value') count: number = 0 }
|
它们都是用来定义输入属性,而不同的地方在于
inputs
定义在指令的 metadata
信息中,开发者对指令的输入属性一目了然
- 此外对于未选用
TypeScript
作为开发语言的开发者,也只能在 metadata
中定义指令的输入属性
@Input
属于属性装饰器,通过它可以一起定义属性的访问描述符(public
、private
、protected
)
1
| @Input() public attr: string
|
@Output
与其类似
1
| @Output('countChange') change: EventEmitter<number> = new EventEmitter<number>()
|
setter & getter
setter
和 getter
是用来约束属性的设置和获取,它们提供了一些属性读写的封装,可以让代码更便捷,更具可扩展性,通过 setter
和 getter
方式,我们对类中的私有属性进行了封装,能避免外界操作影响到该私有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { Component, Input } from '@angular/core'
@Component({ selector: 'exe-counter', template: ` <p>当前值: {{ count }} </p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` })
export class CounterComponent { _count: number = 0 biggerThanTen: boolean = false
@Input() set count (num: number) { this.biggerThanTen = num > 10 this._count = num }
get count(): number { return this._count }
increment() { this.count++ }
decrement() { this.count-- } }
|
Output
Output
是属性装饰器,用来定义组件内的输出属性,主要用来实现子组件将信息通过事件的形式通知到父级组件
EventEmitter
Output
属性装饰器一般是和 EventEmitter
一起相互配合来使用的,先看 EventEmitter
1 2 3 4 5
| let numberEmitter: EventEmitter<number> = new EventEmitter<number>()
numberEmitter.subscribe((v: number) => console.log(v))
numberEmitter.emit(10)
|
具体的应用流程为
- 子指令创建一个
EventEmitter
实例,并将其作为输出属性导出
- 子指令调用已创建的
EventEmitter
实例中的 emit(payload)
方法来触发一个事件
- 而父指令通过事件绑定(
eventName
)的方式监听该事件,并通过 $event
对象来获取 payload
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { Component, Input, Output, EventEmitter } from '@angular/core'
@Component({ selector: 'exe-counter', template: ` <p>当前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` }) export class CounterComponent {
@Input() count: number = 0 @Output() change: EventEmitter<number> = new EventEmitter<number>()
increment() { this.count++ this.change.emit(this.count) }
decrement() { this.count-- this.change.emit(this.count) } }
import { Component } from '@angular/core'
@Component({ selector: 'exe-app', template: ` <exe-counter [count]="initialCount" (change)="countChange($event)"></exe-counter> ` }) export class AppComponent {
initialCount: number = 5
countChange(ev: number) { console.log(ev) } }
|
@Output(bindingPropertyName)
同 @Input
一样,@Output
装饰器支持一个可选的参数,用来指定组件绑定属性的名称,如果没有指定,则默认使用 @Output
装饰器,装饰的属性名
1 2 3 4 5 6 7 8 9 10 11 12 13
| export class CounterComponent { @Output('counterChange') change: EventEmitter<number> = new EventEmitter<number>() }
@Component({ template: ` <exe-counter [count]="initialCount" (counterChange)="countChange($event)"></exe-counter> ` })
|
outputs
同 inputs
一样,也支持在组建内部添加 outputs
属性,但是和上面一样不太推荐这种写法,所以在这里也不过多的进行介绍了
ngOnChanges
当数据绑定输入属性的值发生变化的时候,Angular
将会主动调用 ngOnChanges
方法,它会获得一个 SimpleChanges
对象,包含绑定属性的新值和旧值,它主要用于监测组件输入属性的变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { Component, Input, SimpleChanges, OnChanges } from '@angular/core'
@Component({ selector: 'exe-counter', template: ` <p>当前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> ` })
export class CounterComponent implements OnChanges{ @Input() count: number = 0
ngOnChanges(changes: SimpleChanges) { console.dir(changes['count']) }
increment() { this.count++ }
decrement() { this.count-- } }
|
可以在控制台当中看到 SimpleChanges
对象的一些值(包括新值和旧值)
需要注意的是,当手动改变输入属性的值,是不会触发 ngOnChanges
钩子的
@ViewChild()
通过 @ViewChild()
装饰器可以获得子组件的引用,从而可以在父组件当中来直接调用子组件的方法
1 2
| // 父组件 <app-header #child1></app-header>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export class AppComponent implements OnInit{
@ViewChild('child1') child1: HeaderComponent
ngOnInit() { this.child1.run('...') } }
export class HeaderComponent implements OnInit { constructor() { } ngOnInit() { }
run(name) { console.log(name) } }
|
@ViewChild 使用类型查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { Component, OnInit } from '@angular/core'
@Component({ selector: 'exe-child', template: ` <p>Child Component</p> ` }) export class ChildComponent { name: string = 'child-component' }
import { Component, ViewChild, AfterViewInit } from '@angular/core' import { ChildComponent } from './child.component'
@Component({ selector: 'my-app', template: ` <h4>Welcome to Angular World</h4> <exe-child></exe-child> `, }) export class AppComponent {
@ViewChild(ChildComponent) childCmp: ChildComponent
ngAfterViewInit() { console.dir(this.childCmp) } }
|
ViewChildren
ViewChildren
用来从模版视图中获取匹配的多个元素,返回的结果是一个 QueryList
集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Component, ViewChildren, QueryList, AfterViewInit } from '@angular/core' import { ChildComponent } from './child.component'
@Component({ selector: 'my-app', template: ` <h4>Welcome to Angular World</h4> <exe-child></exe-child> <exe-child></exe-child> <exe-child></exe-child> `, }) export class AppComponent {
@ViewChildren(ChildComponent) childCmps: QueryList<ChildComponent>
ngAfterViewInit() { console.dir(this.childCmps) } }
|
运行之后可以在控制台当中看到输出多个 ChildComponent
小结
ViewChild
装饰器用于获取模板视图中的元素,它支持 Type
类型或字符类型的选择器,同时支持设置 read
查询条件,以获取不同类型的实例
ViewChildren
装饰器是用来从模板视图中获取匹配的多个元素,返回的结果是一个 QueryList
集合
HostListener & HostBinding
在介绍 HostListener
和 HostBinding
属性装饰器之前,我们可以先来了解一下 Host Element
(宿主元素),宿主元素的概念同时适用于指令和组件,对于指令来说,应用指令的元素就是『宿主元素』,而如果在自定义组件中使用的话,那么自定义组件就是宿主元素
HostListener
HostListener
是属性装饰器,用来为宿主元素添加事件监听,HostListenerDecorator
装饰器定义如下
1 2 3 4
| export interface HostListenerDecorator { (eventName: string, args?: string[]): any new (eventName: string, args?: string[]): any }
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Directive, HostListener } from '@angular/core'
@Directive({ selector: 'onClicks' })
export class onClicks {
@HostListener('click') onClick() { }
}
|
此外还可以监听宿主元素外,其他对象产生的事件,比如 window
或 document
对象,一个点击目标区域会添加背景颜色,点击其他区域取消掉高亮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export class SetBackgroundDirective {
constructor( private el: ElementRef, private re: Renderer2 ) {}
@HostListener('document:click', ['$event']) onClick(btn: Event) { if (this.el.nativeElement.contains(event.target)) { this.highlight('yellow') } else { this.highlight(null) } }
highlight(color: string) { this.re.setStyle(this.el.nativeElement, 'backgroundColor', color) } }
|
Host Event Listener
还可以使用 host
参数来进行绑定(不太建议使用这种方式,推荐使用装饰器风格)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { Directive } from '@angular/core'
@Directive({ selector: 'button[counting]', host: { '(click)': 'onClick($event.target)' } }) export class CountClicks { numberOfClicks = 0
onClick(btn: HTMLElement) { console.log('button', btn, 'number of clicks:', this.numberOfClicks++) } }
|
HostBinding
HostBinding
是属性装饰器,用来动态设置宿主元素的属性值,定义如下
1 2 3 4
| export interface HostBindingDecorator { (hostPropertyName?: string): any new (hostPropertyName?: string): any }
|
应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Directive({ selector: '[exeButtonPress]' })
export class ExeButtonPress { @HostBinding('attr.data') data = 'button' @HostBinding('class.active') isActive: boolean
@HostListener('mouseenter') enter() { this.isActive = true }
@HostListener('mouseleave') leave() { this.isActive = false } }
|
和上面一样,我们也可以在指令的元数据当中来进行绑定(同样的不建议这样使用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Directive({ selector: '[exeButtonPress]', host: { 'data': 'button', '[class.active]': 'isActive' } })
export class ExeButtonPress { isActive: boolean
@HostListener('mouseenter') enter() { this.isActive = true }
@HostListener('mouseleave') leave() { this.isActive = false } }
|