`<mat-radio-button>` 与 `<mat-radio-group>` 完全指南:单选按钮实现、表单集成与无障碍设计)
Angular Materialcomponents 仓库mat-radio-button与mat-radio-group完全指南单选按钮实现、表单集成与无障碍设计【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components本指南基于当前仓库 src/material/radio/radio.md 展开系统讲解 Angular Material 单选按钮mat-radio-button与单选组mat-radio-group的完整用法从标签label位置控制、单选分组与 name 继承机制到angular/forms表单集成再到面向无障碍Accessibility的最佳实践。同时结合仓库内 radio.ts、radio.html 等源码实现与 radio.spec.ts 测试用例深入解析底层工作方式帮助你写出可运行、可维护、可通过测试的 radio 交互。一、mat-radio-button是什么mat-radio-button提供与原生input typeradio完全相同的功能并通过 Material Design 的样式与动画进行增强。也就是说它的选择语义、键盘操作、name 分组行为都与原生 radio 一致但外观与动效遵循 Material Design 规范。在使用任何 Material 组件前需要先导入MatRadioModule。模块定义位于 radio-module.tsimport {NgModule} from angular/core; import {MatRippleModule} from ../core; import {MatRadioButton, MatRadioGroup} from ./radio; NgModule({ imports: [MatRippleModule, MatRadioGroup, MatRadioButton], exports: [BidiModule, MatRadioGroup, MatRadioButton], }) export class MatRadioModule {}模块内部依赖MatRippleModule波纹动效并同时导出BidiModule双向文本方向支持以及MatRadioGroup、MatRadioButton两个指令/组件。在应用模块或独立组件中导入MatRadioModule后即可使用mat-radio-button。单选集合的基本语义所有name相同的单选按钮构成一个集合set同一时刻只能选中其中一个——这是原生 radio 的标准行为mat-radio-button完整继承了这一语义。在源码中这一约束由 Angular CDK 的UniqueSelectionDispatcher保证radio.ts 中每个按钮注入UniqueSelectionDispatcher在ngOnInit中注册监听器见 radio.tsthis._removeUniqueSelectionListener this._radioDispatcher.listen((id, name) { if (id ! this.id name this.name) { this.checked false; } });当某个按钮被选中checked置为true时它会通过this._radioDispatcher.notify(this.id, this.name)通知所有同名按钮取消选中见 radio.ts从而实现同一 name 下互斥选择。二、Radio-button 的标签labelmat-radio-button的标签直接以元素内容的形式提供mat-radio-button valuespringSpring/mat-radio-button此时 Spring 即为该单选按钮的可访问标签。标签可以出现在按钮的左侧或右侧通过labelPosition属性控制取值为before标签在前或after标签在后默认值。mat-radio-button labelPositionbefore valuespringSpring/mat-radio-button从源码看labelPosition的默认解析逻辑为按钮自身 → 所属组 → after三级回退radio.tsget labelPosition(): before | after { return this._labelPosition || (this.radioGroup this.radioGroup.labelPosition) || after; }MatRadioGroup上的labelPosition同理默认after且 setter 会强制归一化到before | after两种取值见 radio.ts。在模板层面标签的摆放由 radio.html 中的label mat-internal-form-field [labelPosition]labelPosition [for]inputId完成label 内容本身通过ng-content投影到.mdc-label中见 radio.html。不显示标签时使用 aria-label / aria-labelledby如果你不希望在按钮旁边显示文字标签可以使用aria-label或aria-labelledby提供可访问标签mat-radio-button valuespring aria-labelSpring option/mat-radio-button这一点在文档 radio.md 中有明确说明aria-label或aria-labelledby用于指定合适的替代标签。三、Radio groups单选组除非 DOM 结构不允许例如按钮被分散在表格单元格中否则单选按钮应该放在mat-radio-group内。单选组具有value属性反映组内当前选中的单选按钮的值mat-radio-group [(ngModel)]favoriteSeason mat-radio-button valuespringSpring/mat-radio-button mat-radio-button valuesummerSummer/mat-radio-button mat-radio-button valueautumnAutumn/mat-radio-button mat-radio-button valuewinterWinter/mat-radio-button /mat-radio-group组内每个单独的mat-radio-button会自动继承该组的name。这一行为在源码中体现为MatRadioGroup.ngOnInit之后对子按钮统一赋值radio.ts 中按钮在ngOnInit时执行this.name this.radioGroup.name而组的namesetter 又会调用_updateRadioButtonNames()将 name 广播给所有子按钮见 radio.ts。MatRadioGroup在源码中是一个指令Directive选择器为mat-radio-groupexportAs: matRadioGroup并设置了roleradiogroup与类名mat-mdc-radio-group见 radio.ts这些属性直接作用于可访问性与样式挂钩。组的核心输入输出成员类型说明valueany组当前选中的值与选中按钮的value保持一致namestring组内所有按钮共用的 name默认由_IdGenerator生成前缀mat-radio-group-labelPositionbefore \| after组内按钮标签位置默认aftercolorThemePalette主题色primary/accent/warn默认accentdisabledboolean是否禁用整组booleanAttribute转换requiredboolean是否必选booleanAttribute转换disabledInteractiveboolean禁用状态下按钮是否仍可交互selectedMatRadioButton \| null当前选中的按钮实例changeEventEmitterMatRadioChange组值因用户交互改变时触发MatRadioChange事件对象包含source触发事件的按钮与value按钮的值两个字段定义见 radio.tsexport class MatRadioChangeT any { constructor( public source: MatRadioButton, public value: T, ) {} }注意change事件只在用户交互导致值变化时触发与原生input typeradio的行为一致通过[(ngModel)]或编程方式赋值不会触发该事件相关注释见 radio.ts。组值如何驱动按钮选中当组的value变化时_updateSelectedRadioFromValue()会遍历所有子按钮将值与组值相等的按钮置为选中见 radio.tsprivate _updateSelectedRadioFromValue(): void { const isAlreadySelected this._selected ! null this._selected.value this._value; if (this._radios !isAlreadySelected) { this._selected null; this._radios.forEach(radio { radio.checked this.value radio.value; if (radio.checked) { this._selected radio; } }); } }反过来当某个按钮被选中时它会通过this.radioGroup.selected this把自身同步为组的selected进而驱动组value更新见 radio.ts。这套双向同步机制保证了组与按钮状态永远一致。四、与 Angular Forms 集成mat-radio-group兼容angular/forms同时支持模板驱动表单FormsModule与响应式表单ReactiveFormsModule。它实现了ControlValueAccessor接口见 radio.ts因此可以直接使用[(ngModel)]、formControl或formControlName。MatRadioGroup通过MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR提供者注册为NG_VALUE_ACCESSOR见 radio.ts并实现了四个核心方法writeValue(value)—— 由表单写入值内部走this.value value并markForCheck()radio.tsregisterOnChange(fn)—— 注册模型变化回调用户点击按钮时由_onInputInteraction调用radio.tsregisterOnTouched(fn)—— 注册失焦touched回调按钮失焦时通过_touch()触发radio.tssetDisabledState(isDisabled)—— 由表单控制禁用状态radio.ts。模板驱动表单示例仓库中的官方示例 radio-ng-model-example.html 展示了完整用法label idexample-radio-group-labelPick your favorite season/label mat-radio-group aria-labelledbyexample-radio-group-label classexample-radio-group [(ngModel)]favoriteSeason for (season of seasons; track season) { mat-radio-button classexample-radio-button [value]season{{season}}/mat-radio-button } /mat-radio-group divYour favorite season is: {{favoriteSeason()}}/div响应式表单示例import {FormControl} from angular/forms; Component({...}) export class RadioReactiveExample { readonly favoriteSeason new FormControl(spring); }mat-radio-group [formControl]favoriteSeason mat-radio-button valuespringSpring/mat-radio-button mat-radio-button valuesummerSummer/mat-radio-button /mat-radio-groupFormControl的值、校验状态与禁用状态都会自动同步到单选组。相关行为在 radio.spec.ts 中有完整测试覆盖例如组禁用时点击无效disabledInteractive下禁用按钮仍可点击但不改变选中态等场景。五、主题颜色与默认配置mat-radio-button与mat-radio-group都支持color属性取值为primary、accent、warn。颜色解析同样采用按钮 → 组 → 全局默认的回退链见 radio.tsget color(): ThemePalette { return ( this._color || (this.radioGroup this.radioGroup.color) || (this._defaultOptions this._defaultOptions.color) || accent ); }注意默认主题色为accent——这正是 M2 设计规范对 selection control 的指定源码注释引用了 Material Design 规范见 radio.ts。仓库还提供了全局默认配置注入令牌MAT_RADIO_DEFAULT_OPTIONS见 radio.ts允许为整个应用统一配置默认颜色与disabledInteractiveexport interface MatRadioDefaultOptions { color: ThemePalette; disabledInteractive?: boolean; } export const MAT_RADIO_DEFAULT_OPTIONS new InjectionTokenMatRadioDefaultOptions( mat-radio-default-options, { providedIn: root, factory: () ({ color: accent, disabledInteractive: false, }), }, );在应用中覆盖默认值providers: [ { provide: MAT_RADIO_DEFAULT_OPTIONS, useValue: {color: primary, disabledInteractive: true}, }, ]注意color仅对 M2 主题生效在 M3 主题下没有作用M3 下的颜色自定义需要走 color variant 相关机制源码注释见 radio.ts。六、无障碍AccessibilityMatRadioButton在内部使用了一个真实的input typeradio元素来提供可访问体验。模板 radio.html 中的原生 input 承担了键盘操作、屏幕阅读器朗读与表单语义的全部职责input #input classmdc-radio__native-control typeradio [id]inputId [checked]checked [disabled]disabled !disabledInteractive [attr.name]name [attr.value]value [required]required aria-invalidfalse [attr.aria-label]ariaLabel [attr.aria-labelledby]ariaLabelledby [attr.aria-describedby]ariaDescribedby [attr.aria-disabled]disabled disabledInteractive ? true : null (change)_onInputInteraction($event)这个内部 radio input 接收焦点并会被mat-radio-button元素的文本内容自动标记label 通过label forinputId与 input 关联见 radio.html。6.1 不要在按钮内容中添加其他交互控件避免在mat-radio-button的内容中加入其他可交互控件如按钮、链接、额外 input这会降低辅助技术用户的使用体验——文档 radio.md 对此有明确警告。6.2 为无文本内容的按钮提供可访问标签对于没有描述性文本内容的单选按钮必须通过aria-label或aria-labelledby提供可访问标签。对于需要动态绑定标签和描述的场合MatRadioButton提供了三个专用 input 属性aria-label、aria-labelledby、aria-describedby。关键点绑定这三个属性时不要加attr.前缀因为它们是组件本身的Input见 radio.ts会被转发到内部 input 上。文档 radio.md 给出了如下示例mat-radio-button [aria-label]getMultipleChoiceAnswer() /mat-radio-button如果误写成[attr.aria-label]属性会落在组件宿主元素上而不会到达内部的原生 input从而无法被屏幕阅读器正确读取。6.3 优先使用单选组并始终为组提供标签尽量把所有单选按钮放进mat-radio-group而不是创建游离的独立按钮因为单选组更适合纯键盘操作。同时所有mat-radio-group元素都应通过aria-label或aria-labelledby提供可访问标签例如官方 ng-model 示例中使用的aria-labelledbyexample-radio-group-label见 radio-ng-model-example.html。6.4 焦点管理与 roving tabindex单选组内采用selection follows focus的焦点管理模式MatRadioButton在ngDoCheck与ngAfterViewInit中调用_updateTabIndex()见 radio.ts仅让当前选中按钮保持可聚焦的 tabindex其余按钮设为-1当焦点停留在旧按钮而选中状态切换时会自动把焦点迁移到新选中的按钮上。此外组件通过FocusMonitor监听焦点在失焦时调用组的_touch()从而正确驱动表单的 touched 状态见 radio.ts。七、测试支持Component Harness仓库为 radio 提供了官方的组件测试 HarnessMatRadioButtonHarness与MatRadioGroupHarness位于 testing/radio-harness.ts。Harness 的宿主选择器分别为.mat-mdc-radio-button与.mat-mdc-radio-group。MatRadioGroupHarness支持按 name 过滤HarnessPredicate.with({name})并能通过读取组内所有按钮的 name 来推导组 name因为组本身并不总是把 name 写成宿主属性见 testing/radio-harness.ts。借助 Harness 可以在不依赖 DOM 细节的情况下编写稳定的组件测试const group await loader.getHarness(MatRadioGroupHarness.with({name: favorite })); const buttons await group.getRadioButtons(); await buttons[1].check(); expect(await buttons[1].isChecked()).toBe(true);结合 radio.spec.ts 中大量基于TestBed的行为测试如组禁用、labelPosition继承、disabledInteractive、事件触发等你可以完整验证组件在真实场景中的表现。八、快速上手清单在模块或独立组件中导入MatRadioModule见 radio-module.ts。使用mat-radio-group包裹mat-radio-button为每个按钮设置[value]为组提供aria-label/aria-labelledby。需要表单集成时直接使用[(ngModel)]或[formControl]MatRadioGroup已实现ControlValueAccessor。需要调整标签位置时使用labelPositionbefore不显示文字时用aria-label。需要整组禁用或禁用仍可交互时使用disabled与disabledInteractive也可通过MAT_RADIO_DEFAULT_OPTIONS全局配置。涉及无障碍时记住不要向按钮内容中添加其他交互控件绑定aria-label/aria-labelledby/aria-describedby时不要加attr.前缀。相关资源官方文档src/material/radio/radio.md核心实现radio.ts、radio.html、radio-module.ts行为测试radio.spec.ts组件测试 Harnesstesting/radio-harness.ts运行示例radio-ng-model-example.html、radio-overview-example.html主题样式_radio-theme.scss、radio.scssM2/M3 分别见 _m2-radio.scss 与 _m3-radio.scss【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考