Angular指令内容小结
前言
在通往angualr的进阶之路上,指令的相关内容是必须熟悉的,这篇文章会通过几个例子来总结一下自己对于指令相关内容的学习。
指令分类
- 组件:用于构建UI组件,继承于 Directive 类
- 属性指令: 用于改变组件的外观或行为
- 结构指令: 用于动态添加或删除DOM元素来改变DOM布局
1) 所有的组件都是指令,这里不作说明
2) 属性型指令会监听和修改其他HTML元素或组件的行为,元素属性或者DOM属性,angular有一些内置的属性指令,比如说:ngClass
,ngStyle
等
3) 结构指令是负责重塑DOM结构的,通过添加、移除和操纵它们所附加到的宿主元素来实现的。同样angular也有一些我们经常用到的内置结构指令,如*ngIf
,*ngFor
下面通过一个小需求来看看如何实现自定义属性指令以及自定义结构指令。需求是这样的,实现类似toolTip组件的效果,mouseenter的时候显示提示信息,mouseleave的时候提示消失。
Angular4 实现自定义属性指令
1. 准备工作
首先通过命令行ng g d common/anotherTip
来创建anotherTip.directive指令文件,友情提示:通过ng generate
生成的文件,默认路径是src/app
,生成文件如下:
import { Directive, OnInit, } from '@angular/core';
@Directive({
selector: '[appAnotherTip]'
})
export class AnotherTipDirective implements OnInit {
constructor() { }
ngOnInit(): void { }
}
2. 名词介绍
背景介绍:
Angular 的口号是 "一套框架,多种平台。同时适用手机与桌面 (One framework.Mobile & desktop.)",即 Angular 是支持开发跨平台的应用,比如:Web 应用、移动 Web 应用、原生移动应用和原生桌面应用等。
为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口。如定义了抽象类 Renderer 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等
所以通过ElementRef就能封装不同平台下视图层的native元素,然后通过Render去统一操作这些元素,避免了应用层与渲染层之间的强耦合。
首先导入需要的依赖:Input
,ElementRef
,Renderer2
,HostListener
Input
:输入属性,用来获取需要提示的信息ElementRef
: 获取native元素Render2
: 在 Angular 4.x+ 版本,我们使用 Renderer2 替代 Renderer (Angular V2)。HostListener
:属性装饰器,用来为宿主元素添加事件监听(宿主元素就是使用指令的当前元素)
- 具体实现如下:
export class AnotherTipDirective implements OnInit {
@Input('appAnotherTip') anotherTip: string;
private visible: boolean = false; // 控制tip是否显示
componentHtml: HTMLElement; // 生成的模板
constructor(private elementRef: ElementRef, private render2: Renderer2) { }
ngOnInit(): void {
this.componentHtml = this.render2.createElement('div');
this.componentHtml.innerHTML = this.makeHtml();
const parentElement = this.elementRef.nativeElement; //父元素
this.render2.appendChild(parentElement.parentElement, this.componentHtml); // 父元素内添加对应内容
}
/**
* @returns 模板
*/
makeHtml(): string {
return `<div class="hoverTipWrapper" style="display: ${this.visible ? 'block': 'none'}">
<span>${this.anotherTip}</span>
</div>`
}
@HostListener('mouseenter') onmouseenter() {
this.visible = true;
this.componentHtml.innerHTML = this.makeHtml();
}
@HostListener('mouseleave') onmouseleave() {
this.componentHtml.innerHTML = '';
}
}
结合注释以及名词介绍,上面实现的tip指令的过程应该很清晰了,写好一个指令之后,就能直接在组件中使用了:
<div style="position:relative;margin-top:80px;">
<span appAnotherTip="测试another" >测试another tip指令</span>
</div>
效果如下(css在文章末尾贴出):
Angular4 实现自定义结构指令
这部分暂且先叫做自定义结构指令,为什么暂且,是因为下面的这种实现方式我还不确定能不能称之为结构指令(捂脸。。。),等到弄清楚再来更改,如果有大兄弟知道的,还请留言说一下。
1. 准备工作
首先还是用命令行ng g d common/directive/tip
生成一个tip.directive的文件,然后再ng g c common/component/hoverTip
的组件。
2. 名词介绍:
ViewContainerRef
:用于表示一个视图容器,可添加一个或多个视图。
节选一段来自官网的介绍:
The location of the View Container within the containing View is specified by the Anchor element. Each View Container can have only one Anchor Element and each Anchor Element can only have a single View Container.
Root elements of Views attached to this container become siblings of the Anchor Element in the Rendered View.
包含视图的视图容器的位置由锚点元素指定。每个视图容器只能有一个锚元素,每个锚元素只能有一个视图容器。连接到这个容器的视图的根元素成为呈现视图中的锚元素的兄弟元素。
<div style="position:relative;">
<span appTip="测试" >测试tip指令。</span>
</div>
结合上面指令的使用来看,span元素就是视图容器,appTip="测试"
就是锚点元素,由appTip这个指令产生的视图的根元素会与span成为兄弟元素。
-
ComponentRef
:提供了对组件实例以及与此组件实例相关的其他对象的访问 -
ComponentFactory
:提供一个create方法,创建一个component -
ComponentFactoryResolver
:提供一个resolveComponentFactory方法,该方法接收一个组建类作为参数,生成一个基于该组建类的组件工厂
- 具体实现:
export class TipDirective {
public hoverTip: ComponentRef<HovertipComponent>;
public factory: ComponentFactory<HovertipComponent>;
constructor(private viewContainer: ViewContainerRef, private resolve: ComponentFactoryResolver) {
// 获取组件工厂
//ViewContainerRef 用于表示一个视图容器
// 服务对象ComponentFactoryResolver提供resolveComponentFactory方法,接收一个组件类作为参数,返回ComponentFactory
this.factory = this.resolve.resolveComponentFactory(HovertipComponent);
}
@Input('appTip') tipText: string;
@HostListener('mouseenter') onmouseenter() {
// 创建之前先删除视图
this.viewContainer.clear();
console.log(this.viewContainer); //span
// 创建组件
this.hoverTip = this.viewContainer.createComponent(this.factory);
this.hoverTip.instance.tipText = this.tipText;
}
@HostListener('mouseleave') onmouseleave() {
if(this.hoverTip) {
this.hoverTip.destroy();
}
}
}
关于使用方法,上文介绍名词的时候已经说明了。执行结果如下:
贴一下上述tip使用的css:
.hoverTipWrapper{
position: absolute;
height: 30px;
line-height: 30px;
// top: 0;
// left: 0;
bottom: calc(100% + 5px);
left: 10px;
background-color: #000000;
padding: 0 5px;
border-radius: 3px;
&::after{
content: '';
position: absolute;
height: 0;
width: 0;
border: 4px solid transparent;
border-top-color: #000000;
left: 10px;
top: 100%;
}
span {
color: #fff;
font-size: 12px;
}
}
总结
关于指令这一块,还是要在多写多练的基础上再去深入了解原理,刚开始接触很容易被一些类,服务搞得晕乎乎的,有写的不对的地方欢迎指出;写这篇文章的过程当中又发现了一些不懂不会的点。做个计划表,后面会依次解决下列知识点:
- ViewContainerRef.createComponent()与ComponentFactory.create()的区别
- constructor和组件的生命周期关系
- Angular检测相关问题