Angular2 Directive 学习笔记-基础篇
在Angular2中有三种类型的指令(Directive)
1. 组件 — 拥有模板的指令。
2. 结构型指令 - 通过添加和移除DOM元素来改变DOM结构的指令。例如:NgFor,NgIf …
3. 属性型指令 - 改变元素显示和行为的指令。例如:NgStyle …
Tips: 关于组件型的指令介绍会在专门的文章里面细致的介绍,毕竟Angular2中组件(Component)是如此的重要。
从创建一个简单属性型指令开始
在Angular2中,属性型指令的创建至少需要一个带有@Directive装饰器修饰的控制器类。@Directive装饰器指定了一个选择器名称,用于指出与此指令相关联的属性的名字。
控制器类实现了指令所具备的行为能力。接下来,开始创建一个简单的属性型指令,该指令的功能是,单鼠标在其修饰的元素上悬停时,改变其修饰元素的背景颜色。
创建一个highlight.directive.ts文件,其代码结构如下:
import { Directive,ElementRef,Renderer } from '@angular/core';
@Directive({
selector: '[prefixHightlight]'
})
export class HighlightDirective {
constructor(elem: ElementRef,renderer: Renderer) {
renderer.setElementStyle(el.nativeElement,'backgroundColor','red');
}
}
代码注解:
- 首先使用 import 语句从Angular Core库中导入我们需要使用的一些功能模块。
使用 Directive 模块提供的 @Directive 修饰器,对 HighlightDirective 类进行功能修饰;
在 HighlightDirective 类的构造函数中注入 ElementRef 和 Renderer 模块的实例(这里涉及到的DI-依赖注入关系之后也会有详细的学习笔记介绍)。
注入 ElementRef 的目的在于让指令可以引用到真实的 DOM 元素。ElementRef这个类可以用来在宿主标签内注入其它标签的应用,这些应用并不仅仅局限于 DOM 元素。ElementRef这个类可以用来在宿主标签内注入其它标签的应用,这些应用并不仅仅局限于 DOM 元素。
Renderer 可以让我们在 Hightlight 类里面的逻辑代码能够正确的渲染 DOM 元素的样式。- 在倒入需要使用的功能模块后,我们使用@Directive装饰器以‘配置对象’参数的形式,对指令的元数据进行说明。
属性型指令的@Directive装饰器需要一个css选择器来指定selector的值,以便Angular编译器从模版中识别出关联到这个指令的HTML。例如:代码中的‘[]’对应的就是css选择器中的属性选择器。- 紧接着在@Directive元数据后面,我们声明并导出里指令的控制器类 HighlightDirective,HighlightDirective类包含了prefixHightlight指令的工作逻辑。别忘记了导出指令的控制器,
这样才能够让我们定义的指令被别的指令(组件)访问。- 需要注意的是:属性型指令的 selector 必须要对应 CSS 的属性选择器,同时命名需要遵循驼峰式命名方式
Tips: 在Angualr的最佳实践中推荐,
1. 我们应该为我们定义的每一个指令,组件,服务都添加上前缀。这样做的好处在于:
确保我们自己声明定义的这些指令,组件,服务不会与标准的HTML属性冲突,也降低与第三方指令,组件,服务冲突的可能性。
2. 指令的名称应该具备一定的自解释性,这样方便我们通过指令的名称就能大概知道指令的用途。
Angular会为每一个被指令匹配上的元素创建一个该指令对应的控制器类的实例。并自动注入该控制器类依赖的别的类的实例。比如上面的代码中,Angualr会选择合适的时机,为我们注入 ElementRef 和 Renderer 的实例。
Angular具体怎么实现的依赖注入之后的学习笔记会在一起学习,如果使用过Spring等Java框架的小伙伴一定对DI不会陌生的。ElementRef 是一个服务,它赋予了我们直接访问 DOM 元素的能力。
通过 ElementRef 的 nativeElement 属性和 Renderer 服务的组合使用,我们便实现了我们需要的指令能力。
如何使用属性型指令呢?
这个~~~对于Angualr2内置的指令的使用相信大家的已经用的不能在溜溜。我们这里学习一下如何在别的指令,组件中使用我们定义的组件的方法。
要使用我们刚才定义的指令,我们需要创建一个模板,并把这个指令作为一个属性应用到一个DOM元素上,也就是我们需要为我们定一个这个指令找到一个宿主元素。
我们先来定一个组件的模板,并取名字叫做:app.component.html
<h1>Angualr2 Directive Study</h1>
<article prefixHightlight>hover me</article>
模板定义完成后,我们开始定义我们的组件,将组件的文件名称定义为:app.component.ts,其代码结构如下:
import { Component,OnInit } from '@angualr/core';
@Component({
selector: 'prefix-app',templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {
constructor() {}
ngOnInit(): void {
console.log(`AppComponent has inited : ${ Date.now() }`);
}
}
接着我们声明一个Angular的模块,并在这个Angular模块中显示的声明我们自己定义的指令,以便Angualr在解析模板时,能够正确的识别我们自己定一个指令。
我们将模块的名称定义为:app.module.ts,其代码结构如下:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { HighlightDirective } from './highlight.directive';
@NgModule({
imports: [ BrowserModule ],declarations: [
AppComponent,HighlightDirective
],bootstrap: [ AppComponent ]
})
export class AppModule {
}
一定要记住在 @NgModule 的 declarations 数组中显示的声明我们定义的指令。否则,我们运行我们的应用时,浏览器会无情的跟我们抛出下面的错误提示:
EXCEPTION: Template parse errors:
Can’t bind to ‘prefixHighlight’ since it isn’t a known property of ‘article’.
简单的来总结一下自定义的属性型指令的运行原理:
Angular在编译模板时,检测到DOM元素上我们正在尝试绑定到某些属性 ,但Angualr并不能认识这些属性(非内置的属性指令)。
Angular就会尝试在我们声明的 declarations 元数据数组中查找这个指令属性。 我们把 HighlightDirective 在元数据的 declarations 数组中进行了声明,
这样一来 Angular 在发现这个指令的导入信息后,接着就会去检查对应的导入语句,从而找到 highlight.directive.ts 中导出的类,进而服务宿主元素对应的行为能力。
实现预定的目标
如果你动手实践了上面已经完成的代码,你会惊讶的发现,我们并没有实现我们最初设想的功能,我们仅仅是对使用了prefixHighlight指令的元素简单粗暴的设置了其背景颜色,
并没有实现预期的鼠标悬停在宿主元素上时才变换背景颜色的功能。
为了现实我们预期的目标,我们需要我们定义的指令能够感知到鼠标事件,并能够在这些事件触发时能够进行特定的行为。
下面我们就开始来改造我们的指令,找到highlight.directive.ts文件,并开始动手修改其代码结构如下:
import { Directive,Renderer,HostListener } from '@angular/core';
@Directive({
selector: '[prefixHightlight]'
})
export class HighlightDirective {
private _domElem: ElementRef;
private _renderer: Renderer;
constructor(elem: ElementRef,renderer: Renderer) {
this._domElem = elem.nativeElement
this._render = renderer;
//renderer.setElementStyle(elem.nativeElement,'backgroundColor','red');
}
@HostListener('mouseenter')
onMouseEnter() {
this._render.setElementStyle(this._domElem,'red');
}
@HostListener('mouseleave')
onMouseLeave() {
this._render.setElementStyle(this.domElem,null);
}
}
代码解释:
我们新导入了 HostListener 类,并使用了这个类的 @HostListener 装饰器。这个装饰器引用了我们定义的属性型指令的宿主元素,在我们的学习笔记中就是 \
如何封装功能强大的指令--让指令可做的事情更多
通过上面的修改,我们已经让我们定义的 prefixHighlight 指令具备了我们预期的功能。
但是我们还可以让它更加灵活和强大。接下来就让我们一起来让我们定义指令具备更丰富的能力能够做更多的事情,让它真正的实现‘器大活好’的目标。
我们需要的是灵活可配置的指令
现在我们实现的指令中高亮颜色是在指令中硬编码进去的,并没有弹性。 我们期望我们的指令是灵活的,这个颜色能够通过绑定从外部设置。例如:
<article [pefixHighlight]="yellow">hover me</article>
还是老规矩,先上代码,之后在解释。接着修改highlight.directive.ts文件,修改后其代码结构如下:
import { Directive,HostListener,Input } from '@angular/core';
@Directive({
selector: '[prefixHightlight]'
})
export class HighlightDirective {
private _domElem: ElementRef;
private _renderer: Renderer;
private _defaultColor = 'red';
constructor(elem: ElementRef,'red');
}
@Input('prefixHighlight') highlightColor: string;
@HostListener('mouseenter')
onMouseEnter() {
this._render.setElementStyle(this._domElem,this.highlightColor || this._defaultColor);
}
@HostListener('mouseleave')
onMouseLeave() {
this._render.setElementStyle(this._domElem,null);
}
}
代码解释:
在上面的代码中,我们从 Angaulr Core 中新导入了 Input 模块,这个模块提供一个 @Input 装饰器,通过这个装饰器,使得我们的指令,能够接受外部的输入值,根据不同的输入值表现出不同的能力。
上面的代码中,我们定义了一个新的属性 highlightColor 并使用了 @Input 装饰器对其进行了装饰,这样一来 @Input 会把相关元数据添加到了类上,让 highlightColor 能被以 myHighlight 为别名进行绑定。
被 @Input 装饰器修饰的属性也被称为‘输入属性’。
通过上面的修改,我们基本实现了一个属性型指令的定义和使用,也简单粗暴的实现了一些指令输入的功能,由于时间关系(其实是学习程度的问题),这里先不做更深入的介绍,等我打完怪回来继续完善…
这篇学习笔记的结尾总感觉有点儿那什么,哎,算了。凑活看吧!以后打完怪在接着完善进阶篇,这样才有美剧的风格~~~~哈哈哈哈哈哈哈哈~~~~~
彩蛋吗???
额外的知识点:
上面的代码解释中提到了输入属性,这里就简单的介绍一下什么叫做输入属性吧。这样一来可以让这篇没有结尾的学习笔记看起来并没有哪吗突兀啦!
PS:关于 @Input @Output 的详细学习会在后面的进阶篇里面和大家一起学习
指令的 输入(Input)属性
我们在上面的最后一次修改中添加的 highlightColor 属性是 HighlightDirective 指令的一个 input 属性。
为了更好的理解为什么需要输入属性这个东西,我们需要先理解一下,
Angular在绑定的源 和 绑定的目标 之间的一个巧妙但重要的区别。
关于源和目标的一个简单定义:
如果属性出现在了模板表达式等号 (=) 的右侧,它就是一个源。如果它出现在了方括号 ([ ]) 中,并且出现在等号 (=) 的左侧,它就是一个目标**
就像在绑定到 HighlightDirective 的 myHighlight 属性时所做的那样。
<p [prefixHighlight]="color">hover me</p>
[prefixHighlight]=”color” 中的 ‘color’ 就是绑定源 。 源属性不需要特别声明。
[prefixHighlight]=”color” 中的 ‘prefixHighlight’ 就是绑定目标。 必须把它定义为一个 Input 属性,否则,Angular 就会拒绝这次绑定,并给出一个明确的错误。
当前只需要记住,通过@Input装饰的输入声明可以确保指令的消费者只能绑定到公开的 API 中的属性,而不是其它的属性。有效的保护了指令或组件的封装性。