0%

ng2属性指令

在angular中共有三种指令:

  • 组件(特殊的指令)
  • 结构指令,通过添加或移除DOM元素来更改DOM布局
  • 属性指令,改变元素、组件或其他组件的外观或行为

属性指令修改一个元素的外观或行为。

创建属性指令

先创建指令文件:

1
ng generate directive hero/ad

在hero模块里面创建ad指令文件,创建的文件为:ad.directive.ts,并且自动引入到模块文件里面:

1
2
3
4
5
6
7
8
9
10
11
import { AdDirective } from './ad.directive';

@NgModule({
imports: [
CommonModule,
],
declarations: [
AdDirective,
]
})
export class HeroModule { }

可以看到在模块中导入了我们的AdDirective指令,并在这模块的declarations数组中引入了,复习下NgModule装饰器的declarations元数据的作用:
declarations 声明本模块中拥有的视图类,angular有三种视图类:组件、指令、管道。

再看指令内容:

1
2
3
4
5
6
7
8
9
10
import { Directive, ElementRef } from '@angular/core';

@Directive({
selector: '[appAd]'
})
export class AdDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'blue';
}
}

首先我们导入了Directive符号和ElementRef服务,Directive提供了@Directive指令装饰器函数,ElementRef服务可以让我们通过它的nativeElement属性直接访问DOM元素。

装饰器函数@Directive以配置对象参数的形式,声明指令的元数据。

指令装饰器允许将一个类装饰为angular指令,并提供额外的元数据,以确定应该如何在运行时处理、实例化和使用指令。

一个指令必须属于一个NgModule才能被另一个指令、组件或应用程序使用,这就是为啥我们在前面引入指令的时候将指令添加到了NgModule的declarations数组中。

指令的元数据属性

指令的元数据属性挺丰富的,我就挑常用的几个就好了,后续用到再更新。

常用的指令元数据:

  • selector:string 选择器
  • inputs:string[] 输入属性
  • outputs:string[] 输出属性
  • host:{[key:string:string]} 监听宿主元素的dom事件
  • exportAs:string 定义可以在模板中引用的名称,已将该指令分配给变量

selector:string 选择器

angular只允许指令触发不跨越元素边界的CSS选择器,选择器可以被的形式:

  • element-name:按照元素名称选择,比如<app-ad></app-ad>
  • .class:按照类名选择,比如<div class="app-ad"></div>
  • [attribute]:按照属性名称选择,比如:<div appAd></div>
  • [attribute=value]:按照属性名和值进行选择,比如<div appAd="test"></div>
  • :not(sub_selector):仅当元素不匹配sub_selector时才选择
  • selector1, select2:selector1或select2两者出现一个就选择

例如前面的AdDirective指令:

1
2
3
@Directive({
selector: '[appAd]'
})
1
<h1 appAd>{{title}}</h1>

inputs:string[]/outputs:string 输入属性/输出属性

标记输入类型的属性。

1
2
3
4
@Directive({
selector: '[appAd]',
inputs: ['color']
})

但是建议是使用通过装饰器标记输入输出属性,因为这样更易读。

1
2
3
4
5
6
@Directive({
selector: '[appAd]',
})
export class AdDirective {
@Input() color: string;
}

输出属性和输入属性类似,只不过输入属性是从父组件到子组件,输出属性是从子组件到父组件。

host 宿主监听

定义与宿主元素相关的事件、操作、属性(attribute)、属性(property)。

Host Lestion

通过定义一个键值对方法来监听宿主元素的DOM事件:

  • event:指令监听的DOM事件,至于有哪些监听DOM事件,可以看这个MDN
  • statement:事件发生时候执行的命令,如果返回false,就将preventDefault应用于DOM事件

例如:

1
2
3
host: {
'(click)': 'onClick($event.target)'
}

不过上面的这种写法是不推荐的,推荐使用装饰器函数@HostListener

1
2
3
4
5
@HostListener('click') onClick(btn) {
console.log('button', btn);
console.log('number of clicks:', this.numberOfClicks++);
return false;
}

是不是很完美?然而,我们可以发现触发函数后,btn是undefined。💔

很绝望啊有木有!

我们不妨去看看装饰器@HostListener

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
47
/**
* Type of the HostListener decorator / constructor function.
*
* @stable
*/
export interface HostListenerDecorator {
/**
* ### Example
*
* @Directive({selector: 'button[counting]'})
* class CountClicks {
* numberOfClicks = 0;
*
* @HostListener('click', ['$event.target'])
* onClick(btn) {
* console.log('button', btn, 'number of clicks:', this.numberOfClicks++);
* }
* }
*
* @Component({
* selector: 'app',
* template: '<button counting>Increment</button>',
* })
* class App {}
*
* @stable
* @Annotation
*/
(eventName: string, args?: string[]): any;
new (eventName: string, args?: string[]): any;
}
/**
* Type of the HostListener metadata.
*
* @stable
*/
export interface HostListener {
eventName?: string;
args?: string[];
}
/**
* HostListener decorator and metadata.
*
* @stable
* @Annotation
*/
export declare const HostListener: HostListenerDecorator;

可以看到,出了eventName,还有个args: string[],也就是参数数组,所以我们可以用数组的方式往装饰器函数里面传参数:

1
2
3
4
5
@HostListener('click', ['$event.target', '$event.x']) onClick(btn: HTMLElement, x) {
console.log('button', btn);
console.log('x', x);
return false;
}

ok,完美。

再看看响应事件方法里面的reture false有什么用。如果返回false,就将preventDefault应用于DOM事件。那么什么是preventDefault

如果事件可取消,则取消该事件,而不停止事件的进一步传播。也就是取消事件的默认动作。该方法通知web浏览器不要执行与事件关联的默认动作(如果存在这样的动作)。

比如,一个复选框标签,点击后会选中,如果使用preventDefault就可以在点击后无法选中复选框:

1
<input id="test" type="checkbox" appAd value="1"/><label for="test">测试默认事件</label>

我们将指令应用在一个复选框上,同时,指令用@HostListener装饰器绑定的onClick函数返回false

1
2
3
4
5
6
@HostListener('click', ['$event.target', '$event.x']) onClick(btn: HTMLElement, x) {
console.log('button', btn);
console.log('x', x);
console.log('number of clicks:', this.numberOfClicks++);
return false;
}

然后点击复选框的时候,发现click方法是正常的,但是点击无法选中复选框。

HostBinding

angular在更改检测期间自动检查绑定的宿主属性,如果绑定发生变化,那么它将更新指令的宿主元素。

HostBinding接受一个可选参数,指定被更新的主机元素的属性名称,如果未提供,就是用类属性名称,例如:

1
<input type="text" [(ngModel)]="myHero.name" appAd />
1
2
3
4
5
6
7
8
9
10
11
12
@HostBinding('class.valid') get valid() {
console.log(this.control);
return Number(this.control.value) > 0;
}
@HostBinding('class.invalid') get invalid() {
return !(Number(this.control.value) > 0);
}

@HostBinding('attr.role') role = 'button';

constructor(private el: ElementRef, public control: NgModel) {
}

第一个是给宿主元素绑定了类valid,根据valid的值来决定是否加上这个类。第二个绑定类似,第三个绑定是绑定了属性role,通过role值来绑定,这个值是写死的,不会改变。前两个值是通过输入框里面绑定的NgModel的值来判断,在应用运行的过程中会动态监测。

exportAs

定义可以在模板中使用的名称,可以将指令通过模板引用的方式引用给变量。

1
2
3
4
5
6
7
8
9
@Directive({
selector: '[appAd]',
exportAs: 'aa',
})
export class AdDirective {
public testFun(): void {
console.log('testFun');
}
}
1
2
<input type="text" [(ngModel)]="myHero.name" appAd #aa="aa"/>
<button (click)="aa.testFun()">testFun</button>
码字辛苦,打赏个咖啡☕️可好?💘