Angular CDK Bidi 双向文本方向(LTR/RTL)支持全解析:Directionality 服务与 Dir 指令实战指南

Angular CDK Bidi 双向文本方向(LTR/RTL)支持全解析:Directionality 服务与 Dir 指令实战指南 Angular CDK Bidi 双向文本方向LTR/RTL支持全解析Directionality 服务与 Dir 指令实战指南【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components本指南以angular/cdk_bidi包的公开 API 报告goldens/cdk/bidi/index.api.md为核心骨架结合该包在仓库中的文档、源码与单元测试系统讲解 Angular CDK 中 LTR/RTL 布局方向体系的完整用法。你将掌握如何用Directionality服务读取全局方向、如何用Dir指令让组件感知最近的祖先方向上下文、CDK 如何解释auto值以及DIR_DOCUMENT令牌在测试中的价值——这些能力是 overlay 定位、键盘导航等方向敏感组件正确工作的基础。包定位angular/cdk/bidi是什么bidiBidirectional Text双向文本是 Angular CDK 中的一个基础包它为组件提供了一套获取并响应应用 LTR/RTL 布局方向变化的公共机制。正如该包文档src/cdk/bidi/bidi.md开篇所述Thebidipackage provides a common system for components to get and respond to change in the applications LTR/RTL layout direction.在阿拉伯语、希伯来语、波斯语等从右向左书写的语言环境中页面布局需要整体镜像翻转。CDK 的众多组件如 overlay 浮层定位、菜单键盘导航都必须知道当前元素处于 RTL 还是 LTR 环境才能正确工作。bidi包正是为此提供统一抽象的底层模块。该包的公开 API 表面非常精简由 API Extractor 生成的报告 goldens/cdk/bidi/index.api.md 明确列出了全部对外导出公开成员类型作用BidiModuleNgModule导入后即可使用Dir指令并注入Directionality服务DirectionalityService应用或子树的方向上下文暴露当前方向与变更流DirDirective匹配带dir属性的元素自身即作为Directionality提供DIR_DOCUMENTInjectionTokenDocument注入 document 的令牌便于在测试中伪造方向来源DirectionTypeltr \| rtl字面量联合类型所有符号均标注public即该包的稳定公共契约_rawDir、valueSignal等以_开头或标注(undocumented)的成员属于内部实现细节不在公共 API 承诺范围内。入口统一由 src/cdk/bidi/public-api.ts 导出并经 src/cdk/bidi/index.ts 转发。第一步导入BidiModule要使用该包的能力首先需要在模块中导入BidiModule。其实现src/cdk/bidi/bidi-module.ts非常简洁import {NgModule} from angular/core; import {Dir} from ./dir; NgModule({ imports: [Dir], exports: [Dir], }) export class BidiModule {}从源码结构看BidiModule本身没有提供任何服务——Directionality与DIR_DOCUMENT均声明为providedIn: root详见后文因此只要导入该模块即可在任意组件中注入方向服务。import {NgModule} from angular/core; import {BidiModule} from angular/cdk/bidi; NgModule({ imports: [BidiModule], }) export class MyModule {}注入Directionality服务读取当前方向当应用引入了BidiModule后组件即可注入Directionality来获取当前的文本方向RTL 或 LTR。API 报告中Directionality的公开成员为readonly change: EventEmitterDirection——方向变化时发出事件的流get value(): Direction——当前方向readonly valueSignal: WritableSignalDirection(undocumented)内部实现。官方文档示例响应方向变化src/cdk/bidi/bidi.md 给出了注入并使用Directionality的完整示例Component({ ... }) export class MyWidget implements OnDestroy { /** Whether the widget is in RTL mode or not. */ private isRtl: boolean; /** Subscription to the Directionality change EventEmitter. */ private _dirChangeSubscription Subscription.EMPTY; constructor(dir: Directionality) { this.isRtl dir.value rtl; this._dirChangeSubscription dir.change.subscribe(() { this.flipDirection(); }); } ngOnDestroy() { this._dirChangeSubscription.unsubscribe(); } }要点拆解初始值构造函数中通过dir.value rtl一次性读取当前方向用于首次渲染响应变化订阅dir.change事件流当方向变化时触发flipDirection()之类的布局翻转逻辑资源释放在ngOnDestroy中unsubscribe防止内存泄漏。虽然Directionality.ngOnDestroy()会complete事件流源码见 src/cdk/bidi/directionality.ts但规范做法仍是主动取消订阅。全局方向是如何确定的body/html 优先级与DIR_DOCUMENTDirectionality的构造函数src/cdk/bidi/directionality.ts揭示了初始方向的计算规则constructor() { const _document inject(DIR_DOCUMENT, {optional: true}); if (_document) { const bodyDir _document.body ? _document.body.dir : null; const htmlDir _document.documentElement ? _document.documentElement.dir : null; this.valueSignal.set(_resolveDirectionality(bodyDir || htmlDir || ltr)); } }解析顺序为优先读取body元素的dir属性若 body 未设置则回退到html元素的dir两者均未设置时默认ltr。这一优先级在单元测试 src/cdk/bidi/directionality.spec.ts 中有明确验证should read dir from the body even it is also specified on the html element与should default to ltr if nothing is specified on either body or the html element。其中DIR_DOCUMENT令牌的实现src/cdk/bidi/dir-document-token.ts值得一提它以providedIn: root注册工厂函数inject(DOCUMENT)复用 Angular 平台自带的DOCUMENT令牌。单独定义这一令牌而非直接注入DOCUMENT的原因源码注释写得很清楚单元测试中不能直接使用真实的 document在 Safari 中修改真实dir会导致基于几何测量的测试失败而重新 provide platform-browser 的DOCUMENT又会与测试代码自身的querySelector冲突。因此测试中可轻松伪造 document见 src/cdk/bidi/directionality.spec.tsTestBed.configureTestingModule({ providers: [{provide: DIR_DOCUMENT, useFactory: () fakeDocument}], });这也是为什么Directionality的注入使用{optional: true}——在没有 Angular 平台如纯 SSR 或特殊测试环境时不会抛错。Dir指令为子树提供局部方向上下文BidiModule还导出了一个匹配任何带dir属性的元素的指令Dir。API 报告显示其关键声明ɵdir: [Dir, [dir], [dir], { dir: { alias: dir; required: false; } }, { change: dirChange }, ..., true]即选择器为[dir]输入属性dir可选输出事件dirChange。源码src/cdk/bidi/dir.ts进一步揭示了两个重要细节Directive({ selector: [dir], providers: [{provide: Directionality, useExisting: Dir}], host: {[attr.dir]: _rawDir}, exportAs: dir, })自身即Directionality通过providers: [{provide: Directionality, useExisting: Dir}]Dir把自己以Directionality的身份提供给后代。这样任何注入Directionality的组件拿到的是最近的祖先方向上下文而不是全局值保留原始属性host 绑定[attr.dir]: _rawDir把消费者传入的原始字符串如auto原样写回 DOM而value则返回规范化后的ltr | rtl。测试should preserve the consumer-provided dir attribute while normalizing the directive valuedirectionality.spec.ts专门验证了这一点。基本用法!-- 为整个子树声明 RTL 布局 -- div dirrtl my-widget/my-widget !-- 此组件注入的 Directionality.value rtl -- /div !-- 绑定与监听变化 -- div [dir]currentDirection() (dirChange)onDirectionChange($event) ... /div与Directionality相同的 APIDir实现Directionality接口公开成员与之一致change: EventEmitterDirection输出别名dirChange、dir属性getter/setter、valuegetter、valueSignal。因此对Dir也可以使用与 bidi.md 中Directionality示例完全相同的订阅模式。setter 内部的完整逻辑Dir的dirsettersrc/cdk/bidi/dir.ts展示了规范化与事件触发的完整链路set dir(value: Direction | auto) { const previousValue this.valueSignal(); this.valueSignal.set(_resolveDirectionality(value)); this._rawDir value; if (previousValue ! this.valueSignal() this._isInitialized) { this.change.emit(this.valueSignal()); } }将输入值经_resolveDirectionality规范化为ltr | rtl存入 signal同时把原始字符串存入_rawDir以便原样写回 DOM仅当值确实发生变化、且组件已完成初始化ngAfterContentInit中置_isInitialized true见 dir.ts时才发出dirChange事件避免初始化阶段的冗余触发ngOnDestroy中change.complete()结束事件流dir.ts。auto值的特殊解释基于浏览器语言而非文本内容HTML 原生规范允许dirauto浏览器会根据元素文本内容的首个强方向字符来推断方向。CDK 对此有不同的解释bidi.md 明确说明了原因与差异对于性能考量CDK 通过查看浏览器语言navigator.language并与一组已知的 RTL 区域设置匹配来解析auto值。这与浏览器基于元素文本内容的处理方式不同。之所以采用基于语言的简化匹配而非基于内容是因为按文本内容推断计算成本高而 overlay、键盘导航等场景只需知道元素大体处于 RTL 还是 LTR 布局即可正确工作。底层实现RTL 区域正则_resolveDirectionality与RTL_LOCALE_PATTERN定义在 src/cdk/bidi/directionality.ts/** Regex that matches locales with an RTL script. Taken from goog.i18n.bidi.isRtlLanguage. */ const RTL_LOCALE_PATTERN /^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*-_)(?!.*-_($|-|_))($|-|_)/i; export function _resolveDirectionality(rawValue: string): Direction { const value rawValue?.toLowerCase() || ; if (value auto typeof navigator ! undefined navigator?.language) { return RTL_LOCALE_PATTERN.test(navigator.language) ? rtl : ltr; } return value rtl ? rtl : ltr; }该正则源自goog.i18n.bidi.isRtlLanguage可识别的 RTL 语言涵盖阿拉伯语ar、库尔德语ckb、迪维希语dv、希伯来语he/iw、波斯语fa、曼丁哥语nqo、普什图语ps、信德语sd、维吾尔语ug、乌尔都语ur、意第绪语yi以及带 RTL 文字标签如Arab、Hebr、Thaa等的区域设置同时排除明确标注Latn/Cyrl文字的情况例如fa-Latn仍视为 LTR。_resolveDirectionality的完整规则可归纳为输入值处理结果rtl大小写不敏感rtlltr大小写不敏感ltrauto且navigator.language匹配 RTL 区域正则rtlauto且语言不匹配或不可用ltr其他任何非法值如not-validltr安全回退大小写不敏感与非法值回退均有测试佐证directionality.spec.ts、L119-L128、L144-L149。关键行为验证来自测试的实证src/cdk/bidi/directionality.spec.ts 对该包的核心契约做了完整验证可作为使用时的行为参考Service 层面html 元素方向可被读取body 优先级高于 html默认ltr非法值回退ltrchange流在 destroy 时 completeDir 指令层面指令自身以Directionality身份被注入should provide itself as Directionality值变化时发出dirChange事件changeCount从 0 变为 1销毁时 complete 事件流保留原始dirauto属性同时规范化内部值大小写不敏感RTL→rtl。实践要点与最佳实践总结全局方向在index.html的html或body上设置dir属性Directionality服务会自动读取body 优先局部覆盖在子树上使用div dirrtl该子树内的组件通过Dir提供的局部Directionality感知最近上下文实现全局 RTL 页面中的局部 LTR 区块反之亦然响应变化订阅dir.change或模板中的(dirChange)事件在方向切换时执行布局翻转逻辑并记得在销毁时取消订阅auto语义差异CDK 按浏览器语言解析auto与浏览器按文本内容推断的语义不同方向敏感的场景overlay、键盘导航请按 CDK 语义理解测试友好通过DIR_DOCUMENT令牌注入伪造 document可在不触碰真实 DOM 的情况下测试各种方向组合。延伸阅读API 报告原文goldens/cdk/bidi/index.api.md包内使用文档src/cdk/bidi/bidi.md核心实现src/cdk/bidi/directionality.ts、src/cdk/bidi/dir.ts令牌定义src/cdk/bidi/dir-document-token.ts模块声明src/cdk/bidi/bidi-module.ts行为测试src/cdk/bidi/directionality.spec.ts同仓库中依赖该方向能力的示例overlay 定位、菜单/列表键盘导航等模块均以Directionality作为方向上下文来源【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考