ngx-bootstrap Tooltip 基础示例(Basic Use-case)完整指南:从场景规格到源码实现
ngx-bootstrap Tooltip 基础示例(Basic Use-case)完整指南:从场景规格到源码实现
导读:本文以 ngx-bootstrap 仓库中 Tooltip 模块的 basic 演示用例(tooltip.examples.basic.use-case.md)为核心,完整还原"Simple demo"按钮悬停显示/移出隐藏的交互场景,并深入源码讲解 TooltipDirective 的触发机制、默认配置与容器渲染原理。读完本文,你将掌握在 Angular(Ivy)项目中快速接入基础 Tooltip 的完整姿势,并能基于同一套机制扩展出位置、延迟、自定义触发、手动控制等进阶用法。
一、Use-case 场景规格:Basic Tooltip 到底在演示什么
本用例是 ngx-bootstrap Tooltip 文档站中最基础、也是所有其他 Tooltip 示例的起点。原文档用标准 use-case 模板描述了它:
| 要素 | 内容 |
|---|---|
| Primary Actor(主要参与者) | User(用户) |
| Scope(适用范围) | Ngx-bootstrap DEMO / Bootstrap 版本 3 & 4 |
| Goal(目标) | 向用户展示 Basic Tooltip 的基础功能 |
主成功场景(Main success scenario)
- 用户打开 Tooltip 演示页面;
- 用户点击 Basic 子菜单;
- 页面出现一个名为 "Simple demo" 的按钮;
- 当用户将鼠标悬停(hover)在 "Simple demo" 按钮上时,一个带指定文本内容的 tooltip 容器显示在按钮上方(top);
- 当用户将鼠标光标移离 tooltip 按钮时,tooltip 容器消失。
变体(Variations)
2*:用户也可以通过滚动到达 Basic 子菜单(即除了点击导航,滚动定位到该演示区块同样属于合法入口)。
这份规格虽然简短,却精确锁定了本用例的两个核心验收点:hover 触发显示与移出隐藏,以及默认位置为 top。这两点在后续的源码与测试中都能得到一一印证。
二、最小可运行实现:一个按钮搞定 Basic Tooltip
Basic 演示的落地代码极其精简,全部内容就是 basic.html 中的一个按钮:
<button type="button" class="btn btn-primary"
tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
Simple demo
</button>
对应的组件类 basic.ts 甚至不需要任何逻辑:
import { Component } from '@angular/core';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'demo-tooltip-basic',
templateUrl: './basic.html',
standalone: false
})
export class DemoTooltipBasicComponent {}
这里的关键就是 tooltip="..." 这个属性型指令(attribute directive)。注意它的写法:tooltip 后面直接跟的是普通字符串属性值,而非 [tooltip] 属性绑定。指令内部会自动读取该字符串作为提示内容。
指令的选择器与导出
tooltip 属性对应的是 TooltipDirective:
@Directive({
selector: '[tooltip], [tooltipHtml]',
exportAs: 'bs-tooltip',
standalone: true,
providers: [
ComponentLoaderFactory, PositioningService
]
})
- 选择器:
[tooltip], [tooltipHtml]——只要元素上存在tooltip或(已废弃的)tooltipHtml属性,指令即生效; exportAs: 'bs-tooltip':允许在模板里用#myTooltip="bs-tooltip"拿到指令实例,从而手动调用show()/hide()/toggle()(见后文"手动控制");- standalone:指令本身是独立的,可被 standalone 组件直接导入使用。
三、安装与模块引入
Standalone 组件用法
usage.md 中给出了两种接入方式。对于使用 standalone 组件的新式 Angular 应用:
import { TooltipModule } from 'ngx-bootstrap/tooltip';
@Component({
standalone: true,
imports: [TooltipModule,...]
})
export class AppComponent(){}
传统 NgModule 用法
import { TooltipModule } from 'ngx-bootstrap/tooltip';
@NgModule({
imports: [TooltipModule,...]
})
export class AppModule(){}
TooltipModule 在 tooltip.module.ts 中的定义非常轻量——它只是把指令与容器组件打包并导出指令:
@NgModule({
imports: [CommonModule, TooltipDirective, TooltipContainerComponent],
exports: [TooltipDirective]
})
export class TooltipModule {}
提示:文档站的安装入口还提供了
ng add ngx-bootstrap --component tooltip的 schematics 方式(见 tooltip-section.list.ts),可只安装 Tooltip 组件,避免引入整个库。
四、源码原理:悬停显示、移出隐藏是如何实现的
Basic 场景中"hover 显示 / 移出隐藏"的效果,由两个默认值共同决定,它们都定义在 TooltipConfig 中:
@Injectable({ providedIn: 'root' })
export class TooltipConfig {
/** sets disable adaptive position */
adaptivePosition = true;
/** tooltip placement, supported positions: 'top', 'bottom', 'left', 'right' */
placement = 'top';
/** array of event names which triggers tooltip opening */
triggers = 'hover focus';
/** a selector specifying the element the tooltip should be appended to. */
container?: string;
/** delay before showing the tooltip */
delay = 0;
}
可以看到:
placement = 'top':解释了"tooltip 显示在按钮上方";triggers = 'hover focus':解释了"悬停显示",同时兼顾键盘focus无障碍场景。
触发监听与事件解析
在 TooltipDirective.ngOnInit 中,指令通过 ComponentLoader 的 listen 挂载触发器:
this._tooltip.listen({
triggers: this.triggers(),
show: () => this.show()
});
triggers 字符串会交给 parseTriggers 解析成一组 {open, close} 事件对。默认 'hover focus' 会展开为 mouseenter → mouseleave 与 focus → blur 两组:鼠标进入按钮时调用 show(),离开时调用 hide();键盘聚焦/失焦同理。
显示与隐藏的核心逻辑
show() 方法中有一个重要的前置守卫:
if (this.isOpen || isDisabledValue || this._delayTimeoutId || !tooltipValue) {
return;
}
也就是说:没有 tooltip 内容(tooltipValue 为空)时,即使触发事件发生也不会显示任何东西——这正是"Simple demo"按钮必须写 tooltip="..." 内容字符串的原因。随后通过 ComponentLoader 将 TooltipContainerComponent 附加到 container(默认 undefined,即追加到指令宿主附近),并按 placement 定位。
hide() 则先移除 in class 触发淡出,再经 tooltipFadeDuration(默认 150ms)延迟后真正销毁容器。
容器渲染结构
Basic 场景中最终呈现在按钮上方的 DOM,由 tooltip-container.component.ts 的模板生成:
<div class="tooltip-arrow arrow"></div>
<div class="tooltip-inner"><ng-content></ng-content></div>
组件宿主上会动态拼出 show tooltip in tooltip-top bs-tooltip-top top ... 等 class(并兼容 Bootstrap 5 的 PlacementForBs5 映射),role="tooltip" 用于无障碍语义。测试用例正是通过断言 .tooltip-inner 是否存在来判断显示与隐藏(见下文第七节)。
五、核心配置项速查表
以 TooltipConfig 与 TooltipDirective 的输入属性为准,Basic 示例可直接沿用的参数如下:
| 输入属性 | 默认值 | 说明 |
|---|---|---|
tooltip |
— | 提示内容,支持字符串或 TemplateRef(动态内容/自定义模板场景) |
placement |
'top' |
位置:top / bottom / left / right,以及 auto 自动适配 |
triggers |
'hover focus' |
空格分隔的触发事件列表,如 hover、focus、click,或空字符串表示手动 |
container |
无 | 容器追加位置的选择器,如 container="body" 可规避 overflow: hidden 父级的遮挡问题 |
delay |
0 |
显示前的延迟毫秒数(见 TooltipDirective) |
isDisabled |
false |
禁用 tooltip |
adaptivePosition |
true |
是否开启自适应翻转/防溢出(对应 flip、preventOverflow modifiers) |
containerClass |
'' |
追加到 tooltip 容器的自定义 CSS class |
boundariesElement |
无 | 防溢出的边界元素:viewport / scrollParent / window |
说明:
show()内部会把adaptivePosition映射为 positioning 服务的flip与preventOverflowmodifiers(见 tooltip.directive.ts),开启后当按钮贴近屏幕边缘时,tooltip 会自动翻转到合适方向,避免超出可视区。
六、从 Basic 出发的进阶延伸
Basic 只是入门。同一演示区块(见 tooltip-section.list.ts)还覆盖了以下可直接复用的变体,它们与 Basic 共享同一套指令与配置体系:
1. 位置(Placement)
placement.html 展示了 top / right / auto / left / bottom 五种取值,只需给按钮加 placement="...":
<button type="button" class="btn btn-default btn-secondary mb-2"
tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
placement="auto">
Tooltip auto
</button>
auto 会让组件自行检测最合适的位置。
2. 点击后消失(Dismiss on next click)
dismiss.html 将 triggers 改为 focus,实现"点击后下次点击时消失":
<button type="button" class="btn btn-success"
tooltip="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
triggers="focus">
Dismissible tooltip
</button>
3. 动态内容(Dynamic Content)
将 tooltip 从属性值升级为属性绑定,内容来自组件数据:
<button type="button" class="btn btn-info" [tooltip]="content">
Simple binding
</button>
4. 手动触发(Manual triggering)
triggers-manual.html 演示了最灵活的用法:triggers="" 关闭自动触发,通过 #pop="bs-tooltip" 拿到指令实例,用 show() / hide() / toggle() 三个方法完全接管显示逻辑:
<span tooltip="Hello there! I was triggered manually"
triggers="" #pop="bs-tooltip">
This text has attached tooltip
</span>
<button type="button" class="btn btn-success" (click)="pop.show()">Show</button>
<button type="button" class="btn btn-warning" (click)="pop.hide()">Hide</button>
<button type="button" class="btn btn-info" (click)="pop.toggle()">Toggle</button>
5. 全局配置默认值(Configuring defaults)
config.ts 展示了通过注入 TooltipConfig 覆盖默认值的工厂模式:
import { TooltipConfig } from 'ngx-bootstrap/tooltip';
export function getAlertConfig(): TooltipConfig {
return Object.assign(new TooltipConfig(), {
placement: 'right',
container: 'body',
delay: 500
});
}
@Component({
selector: 'demo-tooltip-config',
templateUrl: './config.html',
providers: [{ provide: TooltipConfig, useFactory: getAlertConfig }]
})
export class DemoTooltipConfigComponent {}
这样该组件作用域内所有 tooltip 都会默认靠右显示、追加到 body 并延迟 500ms。
七、测试验证:Basic 场景的行为如何被锁定
Basic 用例"初始不显示、悬停/聚焦后显示、移出后隐藏"的行为,在 tooltip.directive.spec.ts 中有对应的自动化测试:
- 初始不显示:
tooltip should not be displayed until user does not any actions——断言页面中不存在.tooltip-inner(spec L59-L62); - focus 触发显示:
tooltip should be displayed by focus event after 0 ms by default——对输入框调用.focus()后断言.tooltip-inner出现; - delay 生效:
tooltip should be displayed after specified delay——fakeAsync+tick(1100)模拟 1000ms 延迟后的显示; - mouseenter 触发显示:
tooltip should be displayed by mouseenter event——fireEvent(tooltipElement, 'mouseenter')模拟悬停; - 手动 show/hide:点击
#showTooltipBtn/#hideTooltipBtn按钮分别断言 tooltip 出现与消失。
这些用例正好把 use-case 文档中第 4、5 步(hover 显示、移出消失)翻译成了可回归的断言。
八、无障碍与最佳实践小结
回顾 Basic 场景,有两点值得在实际项目中沿用:
- 默认
triggers = 'hover focus':同时覆盖鼠标(hover)与键盘(focus)用户,focus触发也是实现"点击外部消失"(dismiss)的基础; - 自动 ARIA 关联:指令在显示/隐藏时会自动设置/移除宿主元素的
aria-describedby(见 setAriaDescribedBy),提示内容由容器上的role="tooltip"承接,确保屏幕阅读器可感知。
综上,Basic Tooltip 从一份 use-case 规格出发,经 basic.html 一行属性即可接入,其背后的 TooltipDirective 与 TooltipConfig 则以最小默认值提供了可扩展的完整机制——这正是 ngx-bootstrap Tooltip 模块"简单上手、深入可扩展"的设计缩影。