
在先前的文章中,我們已經概覽了Angular的相關內容。在自訂指令的部分,我們已經能夠實現編寫,但是,在實際場景中,我們還需要標準化的管理。
Angular 是Angular.js 的升版。 【相關教學推薦:《angular教學》】
So,本文,我們就以Tooltip來講解下自訂指令的內容。
線上效果圖,如下:

目錄結構
在上一篇文章的實現的程式碼專案基礎上,執行命令列:
# 進入directives 資料夾$ cd directives # 建立tooltip 資料夾$ mkdir tooltip # 進入tooltip 資料夾$ cd tooltip # 建立tooltip 元件$ ng generate component tooltip # 建立tooltip 指令$ ng generate directive tooltip
執行完上面的命令列之後,你會得到app/directive/tooltip的檔案目錄結構如下:
tooltip ├── tooltip // tooltip 元件│ ├── user-list.component.html // 頁面骨架│ ├── user-list.component.scss // 頁面獨有樣式│ ├── user-list.component. spec.ts // 測試檔案│ └── user-list.component.ts // javascript 檔案├── tooltip.directive.spec.ts // 測試檔案└── tooltip.directive.ts // 指令檔
嗯,這裡我將元件放在tooltip的同級,主要是方便管理。當然,這個因人而異,你可以放在公共元件components資料夾內。
編寫tooltip 元件
在html檔中,有:
<div class="caret"></div>
<div class="tooltip-content">{{data.content}}</div>在樣式檔.scss中,有:
$black: #000000;
$white: #ffffff;
$caret-size: 6px;
$tooltip-bg: transparentize($black, 0.25); // transparentize 是sass 的語法$grid-gutter-width: 30px;
$body-bg-color: $white;
$app-anim-time: 200ms;
$app-anim-curve: ease-out;
$std-border-radius: 5px;
$zindex-max: 100;
// :host 偽類選擇器,為元件元素本身設定樣式:host {
position: fixed;
padding: $grid-gutter-width/3 $grid-gutter-width/2;
background-color: $tooltip-bg;
color: $body-bg-color;
opacity: 0;
transition: all $app-anim-time $app-anim-curve;
text-align: center;
border-radius: $std-border-radius;
z-index: $zindex-max;
}
.caret { // 脫字符width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid $tooltip-bg;
position: absolute;
頂: -$caret-size;
left: 50%;
margin-left: -$caret-size/2;
border-bottom-color: $tooltip-bg;
}嗯~,
css是個神奇的東西,之後會安排一篇文章來講解下sass相關的內容...
然後,在javascript文件tooltip.component.ts內容如下:
import {
Component,
ElementRef, // 元素指向HostBinding,
OnDestroy,
OnInit
} from '@angular/core';
@Component({
selector: 'app-tooltip', // 標識符,表示我這個元件叫做啥,這裡是app-tooltip
templateUrl: './tooltip.component.html', // 本元件的骨架styleUrls: ['./tooltip.component.scss'] // 本元件的私有樣式})
export class TooltipComponent implements OnInit {
public data: any; // 在directive 賦值private displayTimeOut:any;
// 元件本身host 綁定相關的裝飾器@HostBinding('style.top') hostStyleTop!: string;
@HostBinding('style.left') hostStyleLeft!: string;
@HostBinding('style.opacity') hostStyleOpacity!: string;
constructor(
private elementRef: ElementRef
) { }
ngOnInit(): void {
this.hostStyleTop = this.data.elementPosition.bottom + 'px';
if(this.displayTimeOut) {
clearTimeout(this.displayTimeOut)
}
this.displayTimeOut = setTimeout((_: any) => {
// 這裡計算tooltip 距離左側的距離,這裡計算公式是:tooltip.left + 目標元素的.width - (tooltip.width/2)
this.hostStyleLeft = this.data.elementPosition.left + this.data.element.clientWidth / 2 - this.elementRef.nativeElement.clientWidth / 2 + 'px'
this.hostStyleOpacity = '1';
this.hostStyleTop = this.data.elementPosition.bottom + 10 + 'px'
}, 500)
}
// 元件銷毀ngOnDestroy() {
// 元件銷毀後,清除定時器,防止記憶體外洩if(this.displayTimeOut) {
clearTimeout(this.displayTimeOut)
}
}
}寫tooltip 指令
這是本文的重點,具體的說明,我在程式碼上標註出來~
相關的文件tooltip.directive.ts內容如下:
import {
ApplicationRef, // 全域呼叫偵測ComponentFactoryResolver, // 建立元件物件ComponentRef, // 元件實例的關聯與指引,指向ComponentFactory 所建立的元素Directive, ElementRef,
EmbeddedViewRef, // EmbeddedViewRef 繼承於ViewRef,用於表示範本元素中定義的UI 元素。
HostListener, // DOM 事件監聽Injector, // 依賴注入Input
} from '@angular/core';
import { TooltipComponent } from './tooltip/tooltip.component';
@Directive({
selector: '[appTooltip]'
})
export class TooltipDirective {
@Input("appTooltip") appTooltip!: string;
private componentRef!: ComponentRef<TooltipComponent>;
// 取得目標元素的相關位置,例如left, right, top, bottom
get elementPosition() {
return this.elementRef.nativeElement.getBoundingClientRect();
}
constructor(
protected elementRef: ElementRef,
protected appRef: ApplicationRef,
protected componentFactoryResolver: ComponentFactoryResolver,
protected injector: Injector
) { }
// 建立tooltip
protected createTooltip() {
this.componentRef = this.componentFactoryResolver
.resolveComponentFactory(TooltipComponent) // 綁定tooltip 元件.create(this.injector);
this.componentRef.instance.data = { // 綁定data 資料content: this.appTooltip,
element: this.elementRef.nativeElement,
elementPosition: this.elementPosition
}
this.appRef.attachView(this.componentRef.hostView); // 新增視圖const domElem = (this.componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
document.body.appendChild(domElem);
}
// 刪除tooltip
protected destroyTooltip() {
if(this.componentRef) {
this.appRef.detachView(this.componentRef.hostView); // 移除視圖this.componentRef.destroy();
}
}
// 監聽滑鼠移入@HostListener('mouseover')
OnEnter() {
this.createTooltip();
}
// 監聽滑鼠移出@HostListener('mouseout')
OnOut() {
this.destroyTooltip();
}
}到這裡,已經完成了99%的功能了,下面我們在頁面上呼叫即可。
頁面上
呼叫我們在user-list.component.html上新增下面的內容:
<p style="margin-top: 100px;">
<!-- [appTooltip]="'Hello Jimmy'" 是重點-->
<span
[appTooltip]="'Hello Jimmy'"
style="margin-left: 200px; width: 160px; text-align: center; padding: 20px 0; display: inline-block; border: 1px solid #999;"
>Jimmy</span>
</p> TooltipDirective這個指令我們已經在app.module.ts上進行聲明,我們直接呼叫即可。目前的效果如下:

我們實現的tooltip是底部居中展示,也就是我們平常使用框架,例如angular ant design中tooltip的bottom屬性。對於其他屬性,讀者有興趣的話,可以進行擴充。
至此,我們可以很好的維護自己寫的指令檔了。