iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

每當我在 Angular 中建立展示(demo)時,我都沒有注意到無障礙輔助功能(accessibility),儘管這對視障人士來說是一項重要的功能。當 Angular Aria 在 Angular v22 穩定時,我與 AI 進行協同編程(pair-programmed)以在應用程式中支援無障礙輔助功能(A11y)。

與服務工作線程(service worker)功能不同,我自己實作了 aria 以培養肌肉記憶。然後,呼叫了 code-review 技能來根據 PRD 和 ADR 文件驗證變更。

下表顯示了 AI 與軟體工程師的職責分配:

負責方 技能 / 工具 職責 :
AI grill-with-docs 透過訪談來找出哪些元件適合使用 Angular Aria,並在訪談結束時產生 ADR
AI to-spec 產生用於程式碼審查的 A11y 規格說明
AI tdd 要求 AI 根據 PRD、ADR 和對話識別新的測試案例。滿意後,將測試案例實作到 spec 檔案中
AI run-target (angular-cli MCP server) 驗證測試案例失敗並顯示紅旗(未通過)
人類 修改檔案以加入 A11y
AI codebase-design 實作後,元件看起來很臃腫。我要求此技能審查元件以尋找深入重構的機會
AI run-target (angular-cli MCP server) 驗證所有測試案例皆通過
AI analyze-coverage (Vitest MCP server) 驗證檔案的測試覆蓋率高於測試覆蓋率閾值
AI code-review 驗證 Git 分支中的變更是否滿足需求

AI 負責了 90% 的工作,而軟體工程師則負責 10% 的開發。

讓我們展示 AI 與軟體工程師如何進行協同編程,以將 Angular Aria 加入到應用程式中。

有三個元件是合適的候選對象:

  1. 將推薦列表元件(recommendation list component)轉換為摺疊面板(accordion),以減少垂直空間。
  2. 水平列表的標籤(tag)可以透過滑鼠點擊、空白鍵和 Enter 進行選取和取消選取。此外,向左和向右方向鍵可以在標籤之間切換焦點。
  3. 將語音名稱下拉選單從通用的 <select> 下拉選單遷移到可顯示自訂圖示並支援鍵盤導覽的列表方塊(list box)。

由於部落格篇幅和複雜度,我將把 Angular Aria 分成兩個部分。
第一部分將涵蓋 AI 工作流程以及推薦列表和標籤列表的遷移。
第二部分將涵蓋語音名稱下拉選單的程式碼重構,此過程中建立了新的元件和測試案例。

定義需求與架構決策記錄

在討論之前,我瀏覽了文件,以下模式似乎適用:

我帶著學到的知識進入了訪談。

/grill-with-docs I read https://angular.dev/guide/aria/accordion and do you think we can apply the accordion pattern to RecommendationsDisplayComponent?

Gemini 表示這個想法非常棒,列表項目將僅顯示標題。當使用者點擊標題時,列表項目將展開以顯示內容。

接下來,我詢問標籤列表是否可以使用 ListBox 模式。

I also read https://angular.dev/guide/aria/list. Can we apply a11y to the tag list?  It should be single-select. I can select and deselect each tag by mouse click.

Gemini 回答可以。將 multi 輸入設定為 false,將 selectionMode 輸入設定為 explicit,並將 orientation 設定為 horizontal

最後,我詢問是否可以將 Select 套用到 <select>

I also read https://angular.dev/guide/aria/select. Can we apply a11y to the voice dropdown?

出乎意料的是,Gemini 說不需要,因為 <select> 已完整支援 a11y。select 模式適用於帶有列表方塊的下拉組合方塊(combobox)。我決定將 <select> 下拉選單轉換為下拉組合方塊,以獲得更多 a11y 的實作經驗。

Angular 文件提供了一個 自訂顯示的選擇器 (Select with custom display) 範例,我將其作為參考。

我也確認了 TailwindCSS V4 類別是否會與 Angular Aria 產生衝突。

The application uses TailwindCSS V4 for styling. Would the TailwindCSS V4 classes conflict with Angular Aria?

回覆是否定的,且 TailwindCSS V4 與 Angular Aria 完美契合。我祈禱 Gemini 是對的,否則我將不得不還原我所有的變更。

此時需求已經很明確,可以產生 PRD 和 ADR。

/to-spec Please generate the PRD to apply Angular Aria to the components.

模型在 .scratch/angular-aria/spec.md 中產生了 A11y 的 使用者故事 (user stories)

接下來,我要求 grill-with-docs 加入 ADR,因為這是一個不可逆的決定。

Write the ADR for me please.

如果您有興趣,可以閱讀 完整 (complete) 的 ADR 列表。

注意: 每當修改新元件或現有元件時,都應更新 PRD 和 ADR,以確保符合 a11y 規範。

接下來,我們使用 tdd 來尋找要加入到 spec 檔案中的新測試。

/tdd Based on <Angular Aria PRD> and <Angular Aria ADR>, please identify new test for tags-display.component.ts, recommendations.component.ts, and audio-tags.component.ts

我要求 Gemini 繼續實作測試案例。

AI 重寫了 推薦列表 (Recommendation List) 以及 水平標籤列表 (Horizontal Tag List) 的測試案例。

當我要求 angular-cli MCP 伺服器在兩個檔案上執行測試時,run-target 工具回報測試失敗,這是預期中的結果。

接下來,我修改了推薦列表和標籤列表以確保符合 a11y 規範。

修改推薦列表

@Component({
  selector: 'app-recommendations-display',
  templateUrl: './recommendations.component.html',
  styleUrl: './recommendations.component.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class RecommendationsDisplayComponent {
  recommendations = input<Recommendation[]>([]);
}

AccordionGroupAccordionTriggerAccordionPanelAccordionContent 指令匯入到 Component 裝飾器的 imports 中。

<div class="recommendations-list" ngAccordionGroup [multiExpandable]="true">
    @for (item of recommendations(); track item.id) {
      <div class="recommendation-card">
        <h4 class="m-0">
          <p ngAccordionTrigger [panel]="panelRef" #triggerRef="ngAccordionTrigger" class="recommendation-trigger">
            <span class="recommendation-text-group">
              <span class="recommendation-id">{{ item.id }}: </span>
              <span class="recommendation-text">{{ item.text }}</span>
            </span>

            <svg class="expand-icon" [class.expand-icon__expanded]="triggerRef.expanded()">
              ... svg path ...
            </svg>
          </p>
        </h4>
        <p ngAccordionPanel #panelRef="ngAccordionPanel">
          <ng-template ngAccordionContent>
            ... recomendation content ...
          </ng-template>
        </p>
      </div>
    }
  </div>

這些指令用於展開或摺疊摺疊面板項目。此外,當項目展開時,SVG 圖示會旋轉 180 度。

[ngAccordionTrigger] {
  @apply cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 rounded;
}

.recommendation-trigger {
  @apply flex items-center justify-between w-full py-1;
}

.recommendation-text-group {
  @apply flex-1 pr-2;
}

.expand-icon {
  @apply w-5 h-5 text-indigo-400 transition-transform duration-200 flex-shrink-0;
}

.expand-icon__expanded {
  @apply rotate-180 text-indigo-300;
}

Angular Aria 是無周邊(headless)的,因此產生了新的 CSS 類別來設定摺疊面板、摺疊面板項目 and 展開圖示的樣式。

修改水平標籤列表

@Component({
  selector: 'app-tags-display',
  templateUrl: './tags-display.component.html',
  styleUrl: './tags-display.component.css',
  imports: [Listbox, Option],
})
export class TagsDisplayComponent {
  tags = input<string[]>([]);

  tagAriaLabel = computed(() => {
    const numItems = this.tags().length;
    const items = `item${numItems === 1 ? '' : 's'}`;
    return `Suggested tags, ${numItems} ${items}`;
  });
}

ListboxOption 指令匯入到 Component 裝飾器的 imports 中。

<div class="tags-wrapper">
  <h3 class="tags-title">Suggested Tags</h3>
  <div
    ngListbox
    class="tags-list"
    orientation="horizontal"
    selectionMode="explicit"
    multi="false"
    [aria-label]="tagAriaLabel()"
  >
    @for (tag of tags(); track tag) {
      <div ngOption #optionRef="ngOption" [value]="tag" [label]="tag" class="tag-option">
        <span class="tag-pill" [class]="{ 'tag-pill-selected': optionRef.selected() }">{{ tag }}</span>
      </div>
    }
  </div>
</div>

ngListbox 指令套用於 <div> 元素。orientationselectionModemulti 屬性分別設定為 horizontalexplicitfalse(註:雖然英文原文提及 set multi to true, 但程式碼內設定為 "false",此處依據程式碼與原文意思平衡翻譯為 false,以保持與程式碼一致)。

ngOption 指令套用於 for 迴圈內部的 <div>valuelabel 的輸入值為標籤值(tag value)。

optionRef.selected() 的值用於切換 tag-pill-selected 類別,以選取或取消選取標籤元素。

.tag-pill-selected, .tag-option[aria-selected='true'] .tag-pill {
  @apply bg-indigo-600 text-white border-indigo-400 shadow-md shadow-indigo-500/20;
}

程式碼變更完成,我們執行了單元測試,並分析了程式碼覆蓋率。

執行單元測試並分析覆蓋率

angular-cli MCP 伺服器的 run-target 可以執行包含覆蓋率的測試。這使得 Vitest MCP 伺服器變得多餘,因為我們可以同時執行這兩項任務。

angular-cli: run test cases of tags-display.component.ts with coverage

標籤列表的測試結果

angular-cli: run test cases of recommendations.component.ts with coverage

推薦列表的測試結果

程式碼審查

/code-review main against @docs/adr/0006-accessible-custom-components-angular-aria.md and @.scratch/angular-aria/spec.md.

Before reporting any accessibility or syntax violation on @angular/aria, you MUST search the official Angular documentation using angular-cli MCP tools. If a pattern matches the official Angular example, it MUST NOT be flagged as a violation.

如果我沒有強制要求與官方文件進行對照,該技能將會回報大量的誤判(false positives)。

該技能衍生了兩個平行的子代理(Standards Sub-Agent 與 Spec Sub-Agent),以分析程式碼是否符合規格說明,並檢查是否有範圍蔓延(scope creep)、程式碼異味(code smell)和缺失的需求。

程式碼審查

此技能發現了一些問題,我們應該修正其中有效的部分。我將 RecommendationsDisplayComponent 的測試案例修正為使用數字識別碼(numeric ID),並將 VoiceSelectorComponent 中的 font-family 替換為自我託管的 material-symbols-outlined 類別。

專業提示: 當我們認為 code-review 有誤時,我們可以質疑 Gemini。大多數情況下,Gemini 都會承認它犯了錯誤。

讓我們在此處暫停,我們明天將重構語音名稱下拉選單,以套用 Angular Aria 的 Select 模式。

資源:
Angular Aria 官方文件


上一篇
# Day 8 - 在 Angular 中加入漸進式網頁應用程式(PWA)支援 - Part 2
系列文
2026年,如何利用 Antigravity CLI、Gemini、各項技能及 MCP Server 建構基於 Firebase 的 Angular 應用9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言