<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Duncan Faulkner</title>
    <description>The latest articles on DEV Community by Duncan Faulkner (@duncanfaulkner).</description>
    <link>https://dev.to/duncanfaulkner</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F252191%2Fb3d864a7-0c0b-43bd-98ae-0b1b1762b3b4.png</url>
      <title>DEV Community: Duncan Faulkner</title>
      <link>https://dev.to/duncanfaulkner</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/duncanfaulkner"/>
    <language>en</language>
    <item>
      <title>Scanning public Angular apps with axe, and naming the component behind each finding</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Tue, 06 Oct 2026 21:38:04 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/scanning-public-angular-apps-with-axe-and-naming-the-component-behind-each-finding-f0</link>
      <guid>https://dev.to/duncanfaulkner/scanning-public-angular-apps-with-axe-and-naming-the-component-behind-each-finding-f0</guid>
      <description>&lt;p&gt;The plan was to offer people a free accessibility scan of their Angular app. Most real Angular apps sit behind a login, though, and the public ones are often brochure sites with little to test. So I scanned Angular apps that are public and widely used instead, to see what the ecosystem looks like.&lt;/p&gt;

&lt;p&gt;Two things came out of it. The raw scanner reported on the wrong page three times. And when it was right, it told me where a problem was in the DOM but not which component to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raw scanner output needs checking
&lt;/h2&gt;

&lt;p&gt;I ran &lt;a href="https://github.com/dequelabs/axe-core" rel="noopener noreferrer"&gt;axe-core&lt;/a&gt; over interactive pages. Three times it returned a list of violations for a page that wasn't the app:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I scanned&lt;/th&gt;
&lt;th&gt;What axe saw&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/login&lt;/code&gt; on the RealWorld reference app&lt;/td&gt;
&lt;td&gt;The app is hosted as a single-page app with no server fallback, so the deep link served GitHub Pages' "File not found" page. Eight of the contrast failures I nearly wrote up were GitHub's 404 styling.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep links into ngx-admin's dashboard&lt;/td&gt;
&lt;td&gt;They redirect to a theme-picker splash screen. My first pass returned 484 violations, all from that splash screen. I only reached the dashboard after clicking a theme card.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RealWorld's live demo (&lt;code&gt;angular.realworld.io&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Three issues. The home feed said "Loading articles… No articles are here… yet." The demo's backend has no data, so I had scanned an empty shell.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In each case the number looked believable and meant nothing. I caught it by reading the flagged HTML by hand: &lt;code&gt;&amp;lt;strong&amp;gt;File not found&amp;lt;/strong&amp;gt;&lt;/code&gt; gives it away.&lt;/p&gt;

&lt;p&gt;Two smaller things came up too. Two axe builds disagreed about whether one ngx-admin button failed &lt;code&gt;button-name&lt;/code&gt;; a stricter manual check said it did. And I discarded a "serious" &lt;code&gt;aria-hidden-focus&lt;/code&gt; finding on &lt;code&gt;cdk-focus-trap-anchor&lt;/code&gt;, which appears on every Angular app that uses the CDK focus trap. It's a framework pattern rather than an app bug.&lt;/p&gt;

&lt;p&gt;A scanner tells you where to look. You still have to check what it found.&lt;/p&gt;

&lt;h2&gt;
  
  
  A selector doesn't say which component to fix
&lt;/h2&gt;

&lt;p&gt;When axe is right, it gives you a CSS selector such as &lt;code&gt;.start-search&lt;/code&gt; or &lt;code&gt;a[href$="johndoe"] &amp;gt; img&lt;/code&gt;. That locates a node in the DOM. In an Angular app the fix lives in a component, often one reused across many pages, and the selector doesn't name it.&lt;/p&gt;

&lt;p&gt;Angular already has that information. In a development build it exposes a debug API on &lt;code&gt;window.ng&lt;/code&gt; that maps a DOM node to the component whose template rendered it. Pass axe's findings through that and each violation comes with a component name.&lt;/p&gt;

&lt;p&gt;So I added a headless &lt;strong&gt;report mode&lt;/strong&gt; to our open-source &lt;a href="https://ngbracket.com/tools/a11y-devtools" rel="noopener noreferrer"&gt;&lt;code&gt;@ngbracket/a11y-devtools&lt;/code&gt;&lt;/a&gt;. It drives a running dev build in a real browser, runs the same axe rules, and attributes each violation to the app component that rendered it. The report is grouped by component, and a &lt;code&gt;--fail-on&lt;/code&gt; switch makes it usable in CI. The app under test doesn't change; report mode reads Angular's debug API from outside.&lt;/p&gt;

&lt;p&gt;It needs a dev build, because production builds don't include &lt;code&gt;window.ng&lt;/code&gt;. That's why I could only do this for open-source apps I could run myself, and not for other people's deployed apps. The three scans above were raw axe against production. The two apps below were scanned with report mode against a dev build. The survey after them is raw axe again.&lt;/p&gt;

&lt;h2&gt;
  
  
  ngx-admin
&lt;/h2&gt;

&lt;p&gt;ngx-admin is a widely forked Angular admin template. Against its dashboard, report mode found 83 violation instances across 9 distinct rules, each attributed to a component.&lt;/p&gt;

&lt;p&gt;The header search button has no accessible name, so a screen reader announces it only as "button". Raw axe identifies it as &lt;code&gt;.start-search&lt;/code&gt;. Report mode attributes it to &lt;code&gt;HeaderComponent&lt;/code&gt;, and shows the chain of Nebular components between the button and the header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NbButtonComponent › NbSearchComponent › NbActionComponent › NbActionsComponent › HeaderComponent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It walks past the third-party UI components to the app component you would open to fix it. The rest of the run was sorted the same way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unlabelled &lt;code&gt;sidebar-toggle&lt;/code&gt; link&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HeaderComponent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A template bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unlabelled social links&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FooterComponent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Demo content you wouldn't ship&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25 "content outside a landmark" warnings&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OneColumnLayoutComponent&lt;/code&gt;, via Nebular's menu&lt;/td&gt;
&lt;td&gt;A gap in the app's layout shell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The search button comes from Nebular
&lt;/h3&gt;

&lt;p&gt;The component chain also showed that the search button comes from Nebular's &lt;code&gt;nb-search&lt;/code&gt; component, which ngx-admin uses. In Nebular's source, &lt;code&gt;nb-search&lt;/code&gt; renders its open and close buttons as icon-only &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements with no accessible name. Every ngx-admin fork inherits that.&lt;/p&gt;

&lt;p&gt;A look through Nebular's component templates found two &lt;code&gt;aria-label&lt;/code&gt; attributes in the whole library, and icon-only controls without names in &lt;code&gt;nb-window&lt;/code&gt;, the calendar's month arrows, the flip and reveal cards, and &lt;code&gt;nb-chat&lt;/code&gt;. Attribution showed which layer the bug belongs to as well as which component, so &lt;a href="https://github.com/akveo/nebular/issues/3318" rel="noopener noreferrer"&gt;I reported it to Nebular&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  RealWorld
&lt;/h2&gt;

&lt;p&gt;RealWorld is the reference app many developers copy when they learn Angular. Its live demo showed three shell-level issues because the feed was empty. Running the source in dev with real content gave a fuller picture.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;image-alt&lt;/code&gt; and &lt;code&gt;link-name&lt;/code&gt; both trace to &lt;code&gt;ArticleMetaComponent&lt;/code&gt;, the author byline on each article card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ArticleMetaComponent › ArticlePreviewComponent › ArticleListComponent › HomeComponent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's rendered in every preview on the page. The author's avatar has no &lt;code&gt;alt&lt;/code&gt; and the author link has no text. axe reports eight instances: four images without alt text and four empty links. That's one component with one mistake, rendered on every card, and one fix covers all eight.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick survey of other apps
&lt;/h2&gt;

&lt;p&gt;For breadth, I ran raw axe over a few more public apps. These are production builds, so there's no attribution, and the same caution about raw numbers applies.&lt;/p&gt;

&lt;p&gt;OpenProject is an actively maintained product. On its public work-package table, the coloured work-package type labels fail contrast. The "Task" label is &lt;code&gt;#08a5df&lt;/code&gt; text on white, a ratio of 2.82:1 against the 4.5:1 minimum in &lt;a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html" rel="noopener noreferrer"&gt;WCAG 1.4.3&lt;/a&gt;. A sortable column header also has no text.&lt;/p&gt;

&lt;p&gt;On Angular's own sites, a raw scan flagged colour contrast on &lt;code&gt;angular.dev&lt;/code&gt; and a group of &lt;code&gt;aria-allowed-role&lt;/code&gt; instances on the Angular Material docs. I'd verify those before filing anything.&lt;/p&gt;

&lt;p&gt;On counting: report the number of distinct rules as well as node instances. Most of ngx-admin's 83 instances are one landmark rule firing many times. The instance count shows how noisy a page is; the distinct-rule count is closer to how many fixes it needs. Our report shows both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What kept coming up
&lt;/h2&gt;

&lt;p&gt;Across OpenProject, RealWorld and ngx-admin, the same kinds of problem recurred:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Landmark structure: no &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, and content outside any landmark.&lt;/li&gt;
&lt;li&gt;Accessible names: icon-only buttons and links with no name.&lt;/li&gt;
&lt;li&gt;Basics: &lt;code&gt;lang&lt;/code&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, empty table headers, colour contrast.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Few of these are unusual widget problems. Most come from how components are put together into pages, or from one shared component that every page reuses. A page scan reports these as selectors. Knowing the owning component means you fix it once instead of on every page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where NgBracket fits
&lt;/h2&gt;

&lt;p&gt;NgBracket's component packs are built for this kind of gap. A pack ships an assembled piece of UI, such as a data table, a login flow or a dashboard shell, with the landmarks, accessible names, keyboard support and focus management already in place. They're built to support WCAG 2.2 AA. Every change is checked in CI with axe and accessibility-tree snapshots, and the interactive components also have automated NVDA checks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/tools/a11y-devtools"&gt;&lt;code&gt;@ngbracket/a11y-devtools&lt;/code&gt;&lt;/a&gt; is the tool I used here. It's free, open source and runs only in development. Add the provider for a live overlay while you work, or run report mode over your dev build in CI, and each finding names its owning component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;Automated tools like axe cover roughly a third of WCAG. Keyboard and screen-reader behaviour still need testing by a person.&lt;/p&gt;

&lt;p&gt;Every finding here is from September 2026, on the pages and builds I tested. I sent fixes where I could: pull requests to RealWorld (&lt;a href="https://github.com/realworld-apps/angular-realworld-example-app/pull/363" rel="noopener noreferrer"&gt;#363&lt;/a&gt;) and ngx-admin (&lt;a href="https://github.com/akveo/ngx-admin/pull/6073" rel="noopener noreferrer"&gt;#6073&lt;/a&gt;), and an issue about the missing names in Nebular (&lt;a href="https://github.com/akveo/nebular/issues/3318" rel="noopener noreferrer"&gt;#3318&lt;/a&gt;). OpenProject tracks issues on its own instance rather than GitHub.&lt;/p&gt;

&lt;p&gt;If you'd like a scan like this of your Angular app, &lt;a href="https://ngbracket.com/contact" rel="noopener noreferrer"&gt;get in touch&lt;/a&gt;, or start with the &lt;a href="https://ngbracket.com/#packs" rel="noopener noreferrer"&gt;free packs&lt;/a&gt;. No card is needed.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>developers</category>
      <category>a11y</category>
      <category>javascript</category>
    </item>
    <item>
      <title>A WCAG AA checklist for Angular components</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 12 Jul 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/the-wcag-aa-checklist-every-angular-component-should-pass-before-you-ship-3pdn</link>
      <guid>https://dev.to/duncanfaulkner/the-wcag-aa-checklist-every-angular-component-should-pass-before-you-ship-3pdn</guid>
      <description>&lt;p&gt;This is the checklist I run before an Angular component ships. It covers WCAG Level AA, groups the items by area, and includes Angular code for the common fixes.&lt;/p&gt;

&lt;p&gt;I've written Angular for ten years, and for most of them accessibility was something I'd do after the feature worked. It usually didn't get done. A written checklist made it part of finishing a component.&lt;/p&gt;

&lt;p&gt;Automated tools such as axe and Lighthouse check roughly a third of WCAG. They find missing &lt;code&gt;alt&lt;/code&gt; text, low text contrast and duplicate &lt;code&gt;id&lt;/code&gt;s. They can't tell whether the tab order makes sense, where focus goes after a dialog closes, or whether a live region is announced. Each item below ends with one of these labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated: axe or a similar tool checks it.&lt;/li&gt;
&lt;li&gt;Partly automated: a tool catches some cases, and you check the rest.&lt;/li&gt;
&lt;li&gt;Manual: you have to test it yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Keyboard and focus
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Everything interactive works with the keyboard alone. Put the mouse away and use the component with Tab, Enter, Space and the arrow keys. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native elements are used where possible. A &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; is focusable, works with the keyboard and is announced as a button. A &lt;code&gt;&amp;lt;div (click)&amp;gt;&lt;/code&gt; is none of those. If you're adding &lt;code&gt;role="button"&lt;/code&gt; and a &lt;code&gt;keydown&lt;/code&gt; handler to a &lt;code&gt;div&lt;/code&gt;, use a &lt;code&gt;button&lt;/code&gt; instead. Partly automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Focus is always visible. Don't set &lt;code&gt;outline: none&lt;/code&gt; without a replacement. Use &lt;code&gt;:focus-visible&lt;/code&gt; so keyboard users see a ring and mouse users don't. The ring needs 3:1 contrast against the background. Manual.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#1a56db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* your focus colour, at least 3:1 against the background */&lt;/span&gt;
    &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tab order follows reading order. Don't use a positive &lt;code&gt;tabindex&lt;/code&gt;. If the visual order and the DOM order differ, change the DOM. Partly automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modal dialogs trap focus and return it to the trigger when they close. The CDK &lt;code&gt;Dialog&lt;/code&gt; service does both. If you build your own dialog, &lt;code&gt;cdkTrapFocus&lt;/code&gt; handles the trap, and you have to return focus to the trigger yourself. Manual.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocusAutoCapture&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt; &lt;span class="na"&gt;aria-modal=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Confirm deletion&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    …
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Nothing else traps the keyboard. You can always Tab out of a component. An open modal is the only exception. Manual.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Focus after something changes
&lt;/h2&gt;

&lt;p&gt;Tools can't check most of this section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route changes move focus. After navigation in a single-page app, focus stays on an element that no longer exists. Move it to the new page's heading. Manual.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// h1 needs tabindex="-1"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Opening a menu, drawer or dialog moves focus into it, and closing it returns focus to the trigger. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deleting an item moves focus to a sensible place, such as the next row or the list heading. Focus shouldn't fall back to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A skip link lets keyboard users jump past the navigation to &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. Partly automated.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Semantics and ARIA
&lt;/h2&gt;

&lt;p&gt;Use a native element when one exists. Add ARIA only when it doesn't.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The page has landmarks: one &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, plus &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;footer&amp;gt;&lt;/code&gt; where they apply. Partly automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each page has one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, and heading levels don't skip. Screen-reader users move around a page by its headings. Partly automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom widgets have the right role and state. Set them in the &lt;code&gt;host&lt;/code&gt; object rather than with &lt;code&gt;@HostBinding&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ngbr-switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-disabled]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;disabled() || null&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.enter)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.space)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle(); $event.preventDefault()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NgbrSwitch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* checked = signal(false) … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Partly automated: axe checks that the role and attributes are valid, but not that the state changes when it should.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Icon-only buttons have an accessible name, such as &lt;code&gt;aria-label="Close"&lt;/code&gt;. An icon font or SVG on its own has no name. Automated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;aria-describedby&lt;/code&gt; and &lt;code&gt;aria-labelledby&lt;/code&gt; point at &lt;code&gt;id&lt;/code&gt;s that exist. A missing &lt;code&gt;id&lt;/code&gt; means nothing is announced. Partly automated: axe reports a missing &lt;code&gt;id&lt;/code&gt; as needing review, so a CI check that fails only on violations won't catch it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native elements don't repeat their own role. &lt;code&gt;&amp;lt;button role="button"&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;nav role="navigation"&amp;gt;&lt;/code&gt; add nothing. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Forms and errors
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Every input has a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; with a matching &lt;code&gt;for&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt;. A placeholder isn't a label. Partly automated: axe flags inputs with no name at all, but accepts a placeholder as a name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Required fields are marked in code with &lt;code&gt;required&lt;/code&gt; or &lt;code&gt;aria-required="true"&lt;/code&gt;, as well as visually. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Errors are linked to their field and announced. Connect the message with &lt;code&gt;aria-describedby&lt;/code&gt;, set &lt;code&gt;aria-invalid&lt;/code&gt;, and give the message &lt;code&gt;role="alert"&lt;/code&gt; so it's read when it appears. Manual.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
         &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"showError()"&lt;/span&gt;
         &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"showError() ? 'email-error' : null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @if (showError()) {
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-error"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email address.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;On submit, focus moves to the first error or to an error summary, so keyboard users don't have to search for it. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Errors are shown with text or an icon as well as colour (see section 5). Manual.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Colour, contrast and motion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Text meets AA contrast: 4.5:1 for body text and 3:1 for large text. Partly automated: axe can't work out contrast over translucent layers, gradients or images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Control borders, icons and focus indicators meet 3:1 against what's behind them (WCAG 1.4.11). axe doesn't check this. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Colour isn't the only way information is shown (WCAG 1.4.1). A red border needs an icon or text too. Charts and status indicators are where this fails most often. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The UI works at 200% zoom, and reflows at 400% without horizontal scrolling or clipped content. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Animation respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt;. Motion can trigger vestibular disorders. Manual.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Contrast holds in light and dark themes. Take colours from theme tokens rather than hard-coding them, and run the contrast check in both themes. Partly automated.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Dynamic content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Updates that happen without a page load are announced: "Showing 12 results", "Saved", "Loading". Use the CDK's &lt;code&gt;LiveAnnouncer&lt;/code&gt; rather than writing your own live region. Manual.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;announcer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LiveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// after a filter runs:&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announcer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; results`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Loading and busy states are exposed, for example with &lt;code&gt;aria-busy="true"&lt;/code&gt; on the region that is updating. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Images have useful &lt;code&gt;alt&lt;/code&gt; text, and decorative images have &lt;code&gt;alt=""&lt;/code&gt;. Automated for missing &lt;code&gt;alt&lt;/code&gt;, manual for whether the text is useful.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Charts have a text version. An SVG chart gives a screen reader nothing to read, so render the same data as a visually hidden &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;. No tool flags a chart without one. Manual.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to run it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Automated pass. Add &lt;code&gt;@axe-core/playwright&lt;/code&gt; or &lt;code&gt;jest-axe&lt;/code&gt; to CI so the automated items can't regress.&lt;/li&gt;
&lt;li&gt;Keyboard pass. Put the mouse away and Tab through the whole flow. Most manual items show up within a minute.&lt;/li&gt;
&lt;li&gt;Screen-reader pass. Use VoiceOver (Cmd+F5 on macOS) or NVDA (free on Windows). Listen for whether what you hear matches what's on screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most components fail on the same dozen items, which is why a written checklist helps.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;NgBracket is a set of Angular component packs built to support WCAG 2.2 AA, with keyboard support, focus management, ARIA and chart data tables built in. The &lt;a href="https://ngbracket.com/accessibility?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=wcag-post" rel="noopener noreferrer"&gt;conformance report&lt;/a&gt; lists how each criterion is tested and the known limitations. You can &lt;a href="https://ngbracket.com/?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=wcag-post" rel="noopener noreferrer"&gt;start with the free packs&lt;/a&gt; without a card.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>angular</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Managing focus in Angular: dialogs, drawers, route changes and deleted rows</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Fri, 10 Jul 2026 19:33:30 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/managing-focus-in-angular-dialogs-drawers-and-route-changes-without-stranding-your-users-4agf</link>
      <guid>https://dev.to/duncanfaulkner/managing-focus-in-angular-dialogs-drawers-and-route-changes-without-stranding-your-users-4agf</guid>
      <description>&lt;p&gt;Accessibility checklists list focus management as one item. In practice it covers many separate decisions, and it's often what separates an app that passes an automated audit from one a keyboard user can get through.&lt;/p&gt;

&lt;p&gt;The model that helped me is this: focus is a cursor, and there is only one. For someone using a keyboard or a screen reader, focus is where they are in your app. Mouse users don't think about it, because their eyes and pointer move independently. Everyone else has one position, and every time your UI changes what's on screen, you need to answer one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Where does focus go now?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is wrong, the user ends up somewhere useless. Usually that's the top of the page or &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, and they have to Tab through all of your navigation to get back. Automated tools won't tell you, because where focus went after a state change isn't in a DOM snapshot. Below are four places where Angular apps commonly get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Route changes
&lt;/h2&gt;

&lt;p&gt;This is the most common focus bug in Angular apps, and the hardest to notice. When you navigate a server-rendered site, the browser loads a new document and focus starts at the top. In a single-page app, the router swaps part of the DOM and focus stays on whatever you clicked, which is usually a link that no longer exists. Screen-reader users get no sign that the page changed.&lt;/p&gt;

&lt;p&gt;The fix is to move focus to the new page's heading after each navigation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCUMENT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two more things are needed. The &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; needs &lt;code&gt;tabindex="-1"&lt;/code&gt; so it can take focus from script without joining the tab order. And you want a skip link as the first focusable element, so people can jump past the navigation to &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"skip-link"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#main-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Skip to main content&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With one subscription, every route change now moves focus to the new page and a screen reader reads its heading.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Dialogs: trap focus, then return it
&lt;/h2&gt;

&lt;p&gt;A modal has two focus jobs, and many hand-written dialogs only do one of them.&lt;/p&gt;

&lt;p&gt;While the dialog is open, focus must stay inside it. Tab shouldn't reach the page behind. The CDK handles this correctly, including wrapping from the last element to the first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocusAutoCapture&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"dialog"&lt;/span&gt; &lt;span class="na"&gt;aria-modal=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Delete this project?&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- … --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the dialog closes, focus must go back to the element that opened it. Otherwise it falls to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; and the user starts again from the top of the page. This is the part people forget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;returnFocusTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;returnFocusTo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;returnFocusTo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're opening real modals, the &lt;code&gt;Dialog&lt;/code&gt; service in &lt;code&gt;@angular/cdk/dialog&lt;/code&gt; does both jobs for you. &lt;code&gt;restoreFocus&lt;/code&gt; is on by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Drawers, menus and disclosures
&lt;/h2&gt;

&lt;p&gt;A cart drawer, a navigation flyout and a filter panel follow the same rule as a modal: move focus in when it opens, and return it to the trigger when it closes. If a drawer opens but focus stays behind the overlay, the user tabs through content they can't see.&lt;/p&gt;

&lt;p&gt;For menus with arrow-key navigation, &lt;code&gt;ListKeyManager&lt;/code&gt; and &lt;code&gt;FocusKeyManager&lt;/code&gt; from &lt;code&gt;@angular/cdk/a11y&lt;/code&gt; give you roving focus. Arrow keys move between items and Tab leaves the widget, without you writing the keydown handling yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Deleting the focused item
&lt;/h2&gt;

&lt;p&gt;If you delete the row that has focus and do nothing else, focus falls to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. Send it somewhere sensible instead: the next row, or the list heading if you deleted the last row. In Angular the difficulty is timing. You have to wait for the signal update to reach the DOM before the new target exists. Use &lt;code&gt;afterNextRender&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;injector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Injector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;removeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nf"&gt;afterNextRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-row]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;injector&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern works when adding an item (focus the new input), loading more results (focus the first new one) and a failed submit (focus the first error).&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't move focus when nobody asked
&lt;/h2&gt;

&lt;p&gt;Managing focus doesn't mean moving it all the time. Avoid these two patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;autofocus&lt;/code&gt; on page load. Moving focus into a search box on every load gets in the way of people who navigate by heading or landmark. Only autofocus inside a modal or on a page with a single purpose.&lt;/li&gt;
&lt;li&gt;Moving focus on scroll or hover. Focus should follow something the user did on purpose, such as a click, a key press or an explicit action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the focus indicator visible. &lt;code&gt;:focus-visible&lt;/code&gt; shows it to keyboard users without showing it on every mouse click:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--ngbr-color-focus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;outline-offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't set &lt;code&gt;outline: none&lt;/code&gt; without a replacement. Users can't follow a focus they can't see.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test it
&lt;/h2&gt;

&lt;p&gt;Put your mouse down. Go through the whole flow with Tab, Enter, Space and the arrow keys, and after each interaction ask where focus went. Open a dialog: is focus inside it? Close it: did focus return to the button? Change route: are you on the new heading? Delete something: are you on a real element, or back at the top?&lt;/p&gt;

&lt;p&gt;Most of these bugs show up within a couple of minutes. They're easy to miss because you only see them when you use the app without a pointer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://ngbracket.com/?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=focus-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; is a set of Angular component packs built to support WCAG 2.2 AA. Its dialogs and drawers trap focus while open and return it to the trigger when they close. Its menus use the arrow keys and return focus to the trigger on Escape. The &lt;a href="https://ngbracket.com/accessibility?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=focus-post" rel="noopener noreferrer"&gt;conformance report&lt;/a&gt; lists how each criterion is tested and the known limitations. You can &lt;a href="https://ngbracket.com/?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=focus-post" rel="noopener noreferrer"&gt;start with the free packs&lt;/a&gt; without a card.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>angular</category>
      <category>frontend</category>
      <category>ux</category>
    </item>
    <item>
      <title>Why I built an Angular component library where accessibility isn't a backlog item</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 05 Jul 2026 17:31:39 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/why-i-built-an-angular-component-library-where-accessibility-isnt-a-backlog-item-4i65</link>
      <guid>https://dev.to/duncanfaulkner/why-i-built-an-angular-component-library-where-accessibility-isnt-a-backlog-item-4i65</guid>
      <description>&lt;p&gt;I've been building with Angular for ten years, and in that time I've watched the same thing happen on nearly every project. A component gets built, it works with a mouse, it ships. Accessibility — keyboard support, focus, ARIA, contrast, the screen-reader story — becomes a line in the backlog called something like &lt;em&gt;"a11y pass"&lt;/em&gt;. And that ticket sits there. It gets groomed, re-estimated, pulled into a sprint, bumped out of the sprint, and eventually closed as "we'll come back to it." We never came back to it.&lt;/p&gt;

&lt;p&gt;For a long time I thought that was a discipline problem. If we just cared more, tested more, remembered more, we'd get it right. I don't believe that anymore. &lt;strong&gt;Accessibility keeps losing the race to the deadline not because people don't care, but because it's genuinely hard to hold in your head while you're also wrangling state, layout, data and a ticket that was due yesterday.&lt;/strong&gt; You cannot fix a systemic problem with individual willpower. You fix it by changing the default.&lt;/p&gt;

&lt;p&gt;That realisation is the whole reason I built what I built. This is the story of the problem I was actually trying to solve, and how I went about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem isn't "not enough components"
&lt;/h2&gt;

&lt;p&gt;Let me be clear about what I was &lt;em&gt;not&lt;/em&gt; trying to do, because it matters. I wasn't trying to make another component library. There are plenty. A few new ones have appeared just in the last few months, and they're perfectly good — nicely styled, well documented, quick to drop in.&lt;/p&gt;

&lt;p&gt;But open the accessibility story on almost any of them and it's the same shape every time: a compliance checkbox, bolted on near the end, covering the third of WCAG that an automated tool can see. &lt;code&gt;aria-label&lt;/code&gt; is present, contrast is fine on the light theme, AXE is green. And then you drive it with a keyboard and the dialog swallows your focus, the custom select doesn't respond to arrow keys, the chart is a blank rectangle to a screen reader, and the dark theme quietly fails contrast because nobody checked.&lt;/p&gt;

&lt;p&gt;That's the gap. It's not a shortage of components — it's that in almost every library, &lt;strong&gt;accessibility is a feature you're expected to finish yourself&lt;/strong&gt;, and modern Angular is something the library will get to eventually. I didn't want to add to the pile of components. I wanted to close that gap.&lt;/p&gt;

&lt;p&gt;So I made two bets, and the whole thing is built on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bet one: accessibility is a first-class citizen, not a compliance pass
&lt;/h2&gt;

&lt;p&gt;The rule I set myself is simple to state and annoying to keep: no component ships until it works by keyboard and its accessibility tree is right. Not “until AXE is green” — AXE is a regression net for the easy third, and I run it in CI on every component, but it is not the test. The test is a human putting the mouse down and driving the whole thing with Tab, Enter, Space and arrows, then checking the accessibility tree — the roles, names and states a screen reader announces — against what’s on screen. (Hands-on NVDA/VoiceOver listening is the gate I’m adding next.)&lt;/p&gt;

&lt;p&gt;In practice that meant solving, once and properly, all the things that normally get skipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Focus management as a discipline&lt;/strong&gt; — trap it in dialogs and drawers, restore it to the trigger on close, move it to the heading on route change, send it somewhere sane after you delete the row it was sitting on. One position, always accounted for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State that's communicated, not just coloured&lt;/strong&gt; — &lt;code&gt;aria-invalid&lt;/code&gt; and &lt;code&gt;aria-describedby&lt;/code&gt; wiring errors to fields, &lt;code&gt;role="alert"&lt;/code&gt; so they announce, live regions that actually exist in the DOM before the text arrives so a screen reader speaks them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hardest case, data visualisation&lt;/strong&gt; — every chart is keyboard-navigable and carries a visually-hidden data table behind the SVG as the screen-reader source of truth, because "this picture holds information that exists nowhere else" is the single biggest blind spot in automated testing. (There's even optional audio sonification for trends, which was two weeks of my life and worth every day.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contrast that survives theming&lt;/strong&gt; — every colour is a token that re-tones for dark mode and holds AA on both surfaces, verified with an automated contrast pass across every component in light &lt;em&gt;and&lt;/em&gt; dark, not eyeballed once on white.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is exotic. It's all well-understood. The point was never to invent new accessibility techniques — it was to do the known-correct thing &lt;em&gt;every single time&lt;/em&gt;, so that the person consuming the library never has to file that "a11y pass" ticket, because it's already done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bet two: signals-first, because reactive accessibility is better accessibility
&lt;/h2&gt;

&lt;p&gt;The second bet is that this had to be built the modern Angular way — and not for fashion. &lt;strong&gt;Standalone components, signals, &lt;code&gt;OnPush&lt;/code&gt; everywhere, Material 3 tokens, strict TypeScript, zero NgModules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Signals aren't just a nicer state primitive here; they turn out to make accessibility &lt;em&gt;easier to get right&lt;/em&gt;. When your &lt;code&gt;aria-live&lt;/code&gt; announcement, your &lt;code&gt;aria-invalid&lt;/code&gt; state, your &lt;code&gt;aria-current&lt;/code&gt; and your focus targets are all derived from signals, the accessible state can't drift out of sync with the visual state — they're computed from the same source. Reactive accessibility is more correct accessibility, almost for free. Building on the old change-detection model, you're forever remembering to keep two representations in step. With signals you compute one and both fall out of it.&lt;/p&gt;

&lt;p&gt;It also means the library is built the way you're &lt;em&gt;already&lt;/em&gt; building in Angular v20+ — so it drops into a modern app without dragging a NgModule-era mental model back in with it. A component library that fights your architecture isn't saving you time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I actually went about it
&lt;/h2&gt;

&lt;p&gt;Two bets are easy to write on a slide. Holding them across a real, sellable product took some machinery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AXE wired into CI&lt;/strong&gt; as the automated floor, plus a structural accessibility test suite — the regression net for the third that tools &lt;em&gt;can&lt;/em&gt; catch, so nothing slips backwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A browser-driven contrast harness&lt;/strong&gt; that walks every component in both themes and fails the build on a single AA violation. That's how I know dark mode holds, rather than hoping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A token architecture&lt;/strong&gt; where every surface, text and semantic colour chains through shared design tokens, so theming and dark-mode contrast cascade instead of being re-hardcoded per component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dogfooding, relentlessly.&lt;/strong&gt; I built the marketing site itself, its FAQ, and three complete example apps — an admin dashboard, a storefront, and a booking diary — entirely out of the packs. If a component was awkward to use or quietly inaccessible, I found out by living in it, not by reading my own docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eight packs, a hundred-and-six components, all held to the same line. Built solo, which is terrifying to admit and also exactly why the defaults had to carry the weight — I can't manually re-check accessibility on a hundred components by hand every release, so it had to be baked in and enforced by tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm banging this drum
&lt;/h2&gt;

&lt;p&gt;I'm writing this partly because the space is getting busier, and I think the distinction is worth being loud about. More component libraries is good for Angular. But "accessible by default" and "modern by default" are not the headline feature on most of them — they're the footnote. For me they're the &lt;em&gt;entire product&lt;/em&gt;. If a green CI check is going to tell you the truth about whether a real person can use what you shipped, the two-thirds that automated tools can't see have to already be handled. That's the thing I built, and it's the thing I'll keep making noise about.&lt;/p&gt;

&lt;p&gt;If accessibility is the part of your build that always loses the race to the deadline — the ticket that never gets picked up — that's precisely the problem this is meant to take off your plate.&lt;/p&gt;

&lt;p&gt;And it's not finished. Eight packs is where it starts, not where it stops — there are more in the pipeline, on a steady cadence, each held to the same line. Which is also where you come in: I'd genuinely rather build the components &lt;em&gt;you're&lt;/em&gt; about to write by hand than guess. So if there's a widget you keep re-implementing, a pack you wish existed, or a gnarly accessibility case you've never seen a library get right — tell me. A lot of what's here started as exactly that kind of "someone should just build this properly" itch, and I'm very open to the next one being yours.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I got tired of watching accessibility lose the race, so I built the defaults instead. &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=why-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; is a set of Angular component packs — 8 packs, 106 components — where WCAG AA, AXE-passing, keyboard and screen-reader support are the baseline, and everything is signals-first, standalone, &lt;code&gt;OnPush&lt;/code&gt;, Material 3 themeable, zero NgModules. We're launching soon; &lt;a href="https://ngbracket.com/waitlist?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=why-post" rel="noopener noreferrer"&gt;join the waiting list&lt;/a&gt; for early access.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>axe passes but the component is still unusable: five bugs automated tools can't catch</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Thu, 02 Jul 2026 07:10:33 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/axe-passes-but-its-still-unusable-the-accessibility-bugs-automated-tools-cant-catch-26ea</link>
      <guid>https://dev.to/duncanfaulkner/axe-passes-but-its-still-unusable-the-accessibility-bugs-automated-tools-cant-catch-26ea</guid>
      <description>&lt;p&gt;A green accessibility report is easy to trust. The CI check passes, Lighthouse says 100 and the axe panel shows no violations. You ship it, and a person using a screen reader reaches your component and can't use it.&lt;/p&gt;

&lt;p&gt;I've shipped components like that more than once. What I learned is that automated accessibility testing checks whether your HTML is well-formed. It doesn't check whether your interface is usable. Users get stuck in the gap between those two questions.&lt;/p&gt;

&lt;p&gt;axe is very good, and you should run it in CI if you don't already. It works by inspecting a snapshot of the DOM against a set of rules. It can see that an attribute is &lt;em&gt;present&lt;/em&gt;. It can't see whether the attribute is &lt;em&gt;true&lt;/em&gt;, whether the order of things makes sense, or whether anything happens when you press a key. It catches roughly a third of WCAG issues. A person has to check the rest.&lt;/p&gt;

&lt;p&gt;Here are five components that all pass axe.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The modal that drops your focus
&lt;/h2&gt;

&lt;p&gt;A dialog opens, you confirm, it closes. axe has no complaints: &lt;code&gt;role="dialog"&lt;/code&gt;, &lt;code&gt;aria-modal&lt;/code&gt;, a labelled heading, focus trapped while it's open. All correct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ AXE passes. ❌ focus just fell to &amp;lt;body&amp;gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A keyboard user sees the dialog close and their focus land on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, at the top of the page. They've lost their place and have to Tab back through the whole UI. axe can't catch this. Where focus goes after a state change is a question about time, and the DOM snapshot doesn't hold it.&lt;/p&gt;

&lt;p&gt;The fix is to remember the trigger and return focus to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;openDialog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeElement&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// put them back where they were&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The error you can only see if you can see
&lt;/h2&gt;

&lt;p&gt;A form field fails validation and a red border appears. axe checks that the label is present and the border colour has enough contrast. Both are fine.&lt;/p&gt;

&lt;p&gt;But colour is the only signal that something went wrong. Some colour-blind users won't see it. A screen-reader user hears nothing, because the message was never tied to the field or announced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- passes AXE, communicates nothing --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"error-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing connects that &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; to the input, and nothing tells assistive technology it appeared. The fix is to make the error programmatic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;formControlName=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
       &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"showError()"&lt;/span&gt;
       &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"showError() ? 'email-error' : null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
@if (showError()) {
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"email-error"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter a valid email.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;aria-invalid&lt;/code&gt; says the field has an error, &lt;code&gt;aria-describedby&lt;/code&gt; ties the explanation to it, and &lt;code&gt;role="alert"&lt;/code&gt; announces it when it renders. axe won't ask for any of the three. It only knows the label exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The toggle that looks operable but does nothing
&lt;/h2&gt;

&lt;p&gt;The ARIA here is correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// …no keyboard handler.&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;axe sees a valid &lt;code&gt;switch&lt;/code&gt; with a valid &lt;code&gt;aria-checked&lt;/code&gt; state and a tabindex. It can't know that pressing Space does nothing. Finding that out means running the control, and axe only reads it. A mouse user can toggle it. A keyboard user can focus it and then can't change it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;switch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[attr.aria-checked]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;checked()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabindex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.space)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle(); $event.preventDefault()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(keydown.enter)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle()&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better option is a native &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; with &lt;code&gt;aria-pressed&lt;/code&gt; for the state. A native button handles Space and Enter itself, so it can't have this bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The live region that never speaks
&lt;/h2&gt;

&lt;p&gt;You add an &lt;code&gt;aria-live&lt;/code&gt; region that says "12 results" after filtering, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (announcement()) {
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ announcement() }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;axe sees a valid live region. But screen readers only announce changes to a live region that was already in the DOM when the change happened. Here the &lt;code&gt;@if&lt;/code&gt; adds the element and its text at the same moment, so nothing is announced. The region has to exist empty first and then receive the text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt; &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"polite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ announcement() }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the CDK's &lt;code&gt;LiveAnnouncer&lt;/code&gt;, which keeps a persistent region for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;announcer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LiveAnnouncer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announcer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;announce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; results`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;polite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is my favourite example, because the broken version looks more correct than the one that works. A tool can't tell them apart. A screen reader tells you straight away.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The chart a screen reader can't read
&lt;/h2&gt;

&lt;p&gt;You render an SVG line chart. Contrast meets AA, there's a &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, and axe passes. To a screen-reader user it's an empty image. SVG internals aren't exposed as data, and no automated rule can say "this picture holds information that appears nowhere else on the page".&lt;/p&gt;

&lt;p&gt;The fix is to give the same data in a form assistive technology can read, such as a visually hidden table behind the chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;caption&amp;gt;&lt;/span&gt;Monthly revenue, Jan–Dec&lt;span class="nt"&gt;&amp;lt;/caption&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Month&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&lt;/span&gt;Revenue&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- one row per point --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the biggest blind spot in automated testing. It's also why charts are among the hardest things to make accessible on the front end.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to test by hand
&lt;/h2&gt;

&lt;p&gt;In all five cases axe was right: the HTML was well-formed. Each bug was in something a snapshot can't see: order, interaction, timing or meaning.&lt;/p&gt;

&lt;p&gt;So keep axe in CI. It's a good regression check for the third of issues it covers, and it's free. Then add two manual checks, which take a couple of minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put the mouse down and Tab through the page. Can you reach and operate everything? Does focus go somewhere sensible after each action?&lt;/li&gt;
&lt;li&gt;Turn on a screen reader, either VoiceOver (⌘+F5) or NVDA, and listen. Does what you hear match what's on screen?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those two checks cover a lot of what axe can't. If you only run the automated check, you know your HTML is tidy, but not whether people can use what you built.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I built &lt;a href="https://ngbracket.com/?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=axe-post" rel="noopener noreferrer"&gt;NgBracket&lt;/a&gt; because of this gap. It's a set of Angular component packs built to support WCAG 2.2 AA. Every documented component is checked with axe in CI, I test each new component with the keyboard, and a suite of automated NVDA tests runs against the demo app. The &lt;a href="https://ngbracket.com/accessibility?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=axe-post" rel="noopener noreferrer"&gt;conformance report&lt;/a&gt; lists how each criterion is tested and the known limitations. You can &lt;a href="https://ngbracket.com/?utm_source=blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=axe-post" rel="noopener noreferrer"&gt;start with the free packs&lt;/a&gt; without a card.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introduction to Vitest and Angular</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Sun, 13 Oct 2024 21:49:59 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/introduction-to-vitest-and-angular-1j3a</link>
      <guid>https://dev.to/duncanfaulkner/introduction-to-vitest-and-angular-1j3a</guid>
      <description>&lt;p&gt;The Angular team deprecated Karma a few versions ago and are currently working on ways to provide an alternative 3rd party unit testing frameworks. Currently the options talked about so far are Web Test Runner (likely to be the default), this is a browser based unit test runner similar in many ways to Karma. The other option being discussed is Jest, this can be installed now but can be a little tricky sometimes to get set up and some known issues when Angular is set up to use ES builds (the default in Angular 18 onward). &lt;/p&gt;

&lt;p&gt;The Angular team are looking at using the CLI for the installation process of these unit testing frameworks, similar to when selecting a CSS framework when creating a new project. This is all still very early on in the development cycle and may be a little way off before it becomes developer preview (currently still experimental) and no mention (as yet) of how this would work with an existing project.&lt;/p&gt;

&lt;p&gt;Now that newer versions of Angular use Vite as a development build server and the default in Angular 18, it is now possible to use Vitest in our Angular projects. The installation process is straight forward to set up and feels less problematic than setting up Jest from my recent experience. The great news here is that Vitest has a similar syntax to Karma and Jest, so the learning curve should be fairly small.&lt;/p&gt;

&lt;p&gt;In this post I'm going to walk through setting up and configuring Vitest in an Angular 18 application and replacing Karma . I won't be writing unit tests in this post, I'll leave that for another post, this post is about setting up Angular to use &lt;code&gt;Vitest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm going to start a new project for this post but this will also work on an existing project.&lt;/p&gt;

&lt;p&gt;In a directory type the following in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng new my-vitest-app  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-vitest-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm i
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new Angular application called &lt;code&gt;my-vitest-app&lt;/code&gt;, it'll change into that directory and then run &lt;code&gt;npm install&lt;/code&gt; with our application created we can now remove &lt;code&gt;Karma&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the terminal, type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need to uninstall &lt;code&gt;@types/jasmine&lt;/code&gt; and &lt;code&gt;jasmine-core&lt;/code&gt; as  Vitest uses Jasmine under the hood, we can use &lt;code&gt;it&lt;/code&gt; and &lt;code&gt;describe&lt;/code&gt; from Jasmine so we don't need to import anything in our test files. It is possible to use &lt;code&gt;it&lt;/code&gt; and &lt;code&gt;describe&lt;/code&gt; from Vitest but in doing so we won't be able to use functions like &lt;code&gt;fakeAysnc&lt;/code&gt; in our unit tests.&lt;/p&gt;

&lt;p&gt;The easiest way to get Vitest installed in our project is to use a plugin and AnalogJS has just what we need to setup and configure Vitest for us.&lt;/p&gt;

&lt;p&gt;From the terminal type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @analogjs/platform &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Followed by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng g @analogjs/platform:setup-vitest &lt;span class="nt"&gt;--project&lt;/span&gt; my-vitest-app        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once complete the following files will be generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.mts&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;reference types="vitest" /&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;angular&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vite-plugin-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://vitejs.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vite.config.mts&lt;/code&gt; will be in the root of the project, this sets up the configuration for Vitest, what environment to use, the default is &lt;code&gt;jsdom&lt;/code&gt;, you can change this to &lt;code&gt;happy-dom&lt;/code&gt; (you will need to install &lt;code&gt;happy-dom&lt;/code&gt;). The &lt;code&gt;test-setup.ts&lt;/code&gt; will be in the &lt;code&gt;src&lt;/code&gt; directory and this sets up the test environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vitest-angular/setup-zone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser-dynamic/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;getTestBed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;initTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a&lt;code&gt;zoneless&lt;/code&gt; application then the &lt;code&gt;test-setup.ts&lt;/code&gt; will be slightly different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@analogjs/vitest-angular/setup-snapshots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser-dynamic/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTestBed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core/testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;getTestBed&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;initTestEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;BrowserDynamicTestingModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;platformBrowserDynamicTesting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;angular.json&lt;/code&gt; the &lt;code&gt;test&lt;/code&gt; section will be replaced with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"builder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@analogjs/vitest-angular:test"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally the &lt;code&gt;tsconfig.spec.json&lt;/code&gt; will be update to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./tsconfig.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./out-tsc/spec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"jasmine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"vitest/globals"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2016"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.d.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/test-setup.ts"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;added&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run Vitest tests we need to change the scripts section, in our applications &lt;code&gt;package.json&lt;/code&gt; file add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="w"&gt;   
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;npm run test&lt;/code&gt; will run all of our test files our project.&lt;/p&gt;

&lt;p&gt;If you don't want to install Volta you can skip this section.&lt;/p&gt;

&lt;p&gt;To run &lt;code&gt;Vitest&lt;/code&gt; from the terminal we need to install Vitest, we have a couple of options, globally using &lt;code&gt;npm i -g vitest&lt;/code&gt;, I tend to use Volta, this is a tool chain manager, similar to Node Version Manager (NVM) but Volta handles other JavaScript tooling other than just Node. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you currently have a version of node installed, you will need to uninstall this first before install Volta, once Volta is installed we can install multiple versions of node without removing the previous versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To install Volta head over to &lt;code&gt;https://volta.sh/&lt;/code&gt; and get the appropriate installer package for your operating system, I'm using Ubuntu and all we need for this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install volta&lt;/span&gt;
curl https://get.volta.sh | bash
&lt;span class="c"&gt;# install node&lt;/span&gt;
volta &lt;span class="nb"&gt;install &lt;/span&gt;node@20.15.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As a side note, one of the nice features of Volta, is the ability to pin the version of node we are using to our &lt;code&gt;package.json&lt;/code&gt; file, meaning that Volta will always select the correct version of Node, this is real handy when working in a team of developers, to pin a version of Node to our &lt;code&gt;package.json&lt;/code&gt; file run &lt;code&gt;volta pin node@20.15.0&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With &lt;code&gt;volta&lt;/code&gt; and &lt;code&gt;node&lt;/code&gt; installed we can now install Vitest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;volta &lt;span class="nb"&gt;install &lt;/span&gt;vitest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Vitest installed we can run &lt;code&gt;volta ls&lt;/code&gt; this will show a list of the tooling that we have installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example listing from Volta&lt;/span&gt;
⚡️ Currently active tools:

    Node: v20.15.1 &lt;span class="o"&gt;(&lt;/span&gt;default&lt;span class="o"&gt;)&lt;/span&gt;
    Tool binaries available:
        vitest &lt;span class="o"&gt;(&lt;/span&gt;current @ /home/repo/vitest-app/package.json&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;With everything installed we can now run our unit tests, if we want to run a selection of tests (or a specific test) then we need to use the terminal and pass in an additional parameter, this parameter will run any test file that contains this parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run a range of unit tests&lt;/span&gt;
vitest dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also specify a file, this will only run a unit test that matches the file name, note that this doesn't support regex or glob patterns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run a specific unit test&lt;/span&gt;
vitest app.component
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above will run the unit tests and watch for changes, to run the unit test once and not watch for changes we can change the command to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run unit test(s) once then stop&lt;/span&gt;
vitest run &lt;span class="o"&gt;[&lt;/span&gt;optionally specify an additional parameter]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to run our unit tests in a CI environment we don't need for them to be in watch mode, so add a &lt;code&gt;test:ci&lt;/code&gt; to the script section of the &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to set up our build pipelines to run our tests, setting up build pipelines is out of scope for this post, so I will assume you know how to do this, if not they are plenty of posts on how to do this.&lt;/p&gt;

&lt;p&gt;The next part I want to cover is code coverage, so we've written our unit tests for our various features in our project, but have we written enough? Have we covered the important parts that need testing? This is where code coverage comes to the rescue. Code Coverage tell us how much of our feature is covered by unit tests, whether we've missed a path, for example we've covered the &lt;code&gt;if&lt;/code&gt; condition but not the &lt;code&gt;else&lt;/code&gt; condition. &lt;/p&gt;

&lt;p&gt;Let's add a new script to the scripts section of our &lt;code&gt;package.json&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;    
&lt;/span&gt;&lt;span class="nl"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"vitest run --coverage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we can run &lt;code&gt;npm run coverage&lt;/code&gt; locally and will create a &lt;code&gt;coverage&lt;/code&gt; directory for us in the root of the project and we'll get a table of files along with their code coverage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;% Stmts&lt;/th&gt;
&lt;th&gt;% Branch&lt;/th&gt;
&lt;th&gt;% Funcs&lt;/th&gt;
&lt;th&gt;% Lines&lt;/th&gt;
&lt;th&gt;Uncovered Line #s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All files&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.component.ts&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When we run this it might prompt to install a supporting package this can be either &lt;code&gt;coverage-v8&lt;/code&gt; (the default) or  &lt;code&gt;coverage-instanbul&lt;/code&gt; package, if not we can install them manually using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For v8&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitest/coverage-v8

&lt;span class="c"&gt;# For istanbul&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @vitest/coverage-istanbul
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can set the &lt;code&gt;provider&lt;/code&gt; type in the &lt;code&gt;coverage&lt;/code&gt; section in the &lt;code&gt;vite.config.mts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run &lt;code&gt;code coverage&lt;/code&gt; in our CI environment we need to adjust our &lt;code&gt;test:ci&lt;/code&gt; script to include a reporter, here I've used &lt;code&gt;junit&lt;/code&gt; and &lt;code&gt;junit.xml&lt;/code&gt; and there is a handy plugin for &lt;code&gt;Azure Devops&lt;/code&gt; to read the &lt;code&gt;junit.xml&lt;/code&gt; file which will import the file and create a nice dashboard for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;script:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"test:ci"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run --reporter=default --reporter=junit --outputFile=reports/junit.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;Azure devops&lt;/code&gt; we can add the &lt;code&gt;PublishTestResults&lt;/code&gt; task in our build pipelines and would have the task run after our unit tests have completed. &lt;/p&gt;

&lt;p&gt;When we run our unit tests the results will be displayed in the terminal (unless running in a CI environment), but if you prefer to see the results in a browser this is also possible, first we need to install the ui tooling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @vitest/ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need to match the version of &lt;code&gt;@vitest/ui&lt;/code&gt; to the same version of &lt;code&gt;vitest&lt;/code&gt; installed on your system, once installed we can start it by running &lt;code&gt;vitest --ui&lt;/code&gt; from the terminal, if we add the &lt;code&gt;html&lt;/code&gt; to the &lt;code&gt;reporters&lt;/code&gt; array in our &lt;code&gt;vite.config.ts&lt;/code&gt; file, this will create an &lt;code&gt;html&lt;/code&gt; directory with all the files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;angular&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsdom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;setupFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/test-setup.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// add html into this array.&lt;/span&gt;
      &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;define&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import.meta.vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post we looked at installing and setting up Vitest, a unit test runner in for Angular applications based on Vite, we also added code coverage to our test runs to see how much code coverage our unit tests cover, we also learnt how we can set this up in our CI in Azure Devops. &lt;/p&gt;

</description>
      <category>typescript</category>
      <category>testing</category>
      <category>softwaredevelopment</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Using Mat-Icon Component – Part Two</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Mon, 27 Jan 2020 13:27:00 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/using-mat-icon-component-part-two-3fe8</link>
      <guid>https://dev.to/duncanfaulkner/using-mat-icon-component-part-two-3fe8</guid>
      <description>&lt;h2&gt;
  
  
  Blog Post #002
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Duncan Faulkner – January 2020
&lt;/h2&gt;

&lt;p&gt;I hadn’t intended to write about this just yet, but I got a chance to investigate this sooner than I had thought. I was refactoring part of a project that was using the Mat-Icon component, with an icon.module.ts file as I’d discussed in the previous post.&lt;/p&gt;

&lt;p&gt;I wanted to see if I could improve on the code and reduce the amount of repeated code by iterating over a file and registering the icons.&lt;/p&gt;

&lt;p&gt;I started with a JSON file and populated it with some image names and added the HttpClientModule to use an http.get() method to read the JSON file. All good so far, the file is reading and looping with image names being returned and passed into the matIconRegistery.addSvgIcon() method.&lt;/p&gt;

&lt;p&gt;I thought I might get some issues, but it all seemed to work fine, except the application was erroring, the issue was that the application was loading before the call to the JSON file had returned with the results, so even though I was getting images and registering icons, the app had already tried to use the icons and they weren’t there at that point, and the images were being returned after the app had started.&lt;/p&gt;

&lt;p&gt;OK, so may be this is a timing issue and it doesn’t work in the icon.module.ts file, so I moved code to the app.component.ts file. Nope, the results were the same. This was not the result I was expecting and it was a little odd because the icon.module.ts file was loading images before.&lt;/p&gt;

&lt;p&gt;I’m going to revisit this at a later date, as I would like to understand what the issue is and why it was behaving the way it did, my guess is it has something to do with the http.get() request and the timing around that.&lt;/p&gt;

&lt;p&gt;But as I needed to get this refactoring completed I needed to look for another solution.&lt;/p&gt;

&lt;p&gt;After a bit of research I found an answer and that was to create a service to handle the iterating and registering of icons. Instead of a JSON file to store a list of images the solution was an enum, with a key value structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;Icons&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;power&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep things simple I’ve made the key and the image name the same.&lt;/p&gt;

&lt;p&gt;The service is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Icons&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../image.enum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IconService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;sanitizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Domsanitizer&lt;/span&gt;
&lt;span class="p"&gt;){}&lt;/span&gt;

&lt;span class="nf"&gt;registerIcons&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/images/icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
       &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.svg`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt; 
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the app.component.ts file call the registerIcons() function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// imports&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IconService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./icon.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;iconService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IconService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerIcons&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it, not what I had in mind when I started this, but it does deliver what I wanted to do, and that was to reduce the amount of code to register icons. Though I guess the enum could get pretty lengthy if there are a lot of images.&lt;/p&gt;

&lt;p&gt;I have to thank Stefanos Kouroupis -dev.to – for his article and code on this as I wouldn’t have thought about using a service for this and this has changed my thinking. I wanted to include here it as a follow up to my previous post for completeness, you can see his full article here &lt;a href="https://dev.to/elasticrash/using-custom-made-svg-icons-through-the-angular-material-library-2pif"&gt;https://dev.to/elasticrash/using-custom-made-svg-icons-through-the-angular-material-library-2pif&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will add a github repo of the code in a day or two.&lt;/p&gt;

&lt;p&gt;Original article published at: &lt;a href="https://anglebrackets.io/index.php/2020/01/25/using-mat-icon-component-part-two/" rel="noopener noreferrer"&gt;https://anglebrackets.io/index.php/2020/01/25/using-mat-icon-component-part-two/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angularmaterial</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Angular Materials Mat-Icon component</title>
      <dc:creator>Duncan Faulkner</dc:creator>
      <pubDate>Thu, 14 Nov 2019 18:34:22 +0000</pubDate>
      <link>https://dev.to/duncanfaulkner/angular-materials-mat-icon-component-mff</link>
      <guid>https://dev.to/duncanfaulkner/angular-materials-mat-icon-component-mff</guid>
      <description>&lt;h3&gt;
  
  
  Using local SVG images.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Blog post #001
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Duncan Faulkner - October 2019
&lt;/h4&gt;

&lt;p&gt;When developing an Angular application with Angular Material, there comes a point when we need to add icons on our components, or buttons etc... Angular Material has the Mat-Icon component for doing this. This component works with web fonts like Font-Awesome for instance, simply by adding the name of the image required and an image is displayed. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;For example:&lt;/em&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;mat-icon&amp;gt;home&amp;lt;/mat-icon&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;Requires Angular application and Angular Material is installed/configured and a reference to a web font like Font-Awesome library is all set up&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But what about if you have custom icons that are not part of a web font? And would like to makes changes to the icon, for example: change the colour on hover or on a specific condition at run time?&lt;/p&gt;

&lt;p&gt;In a recent project I had a bespoke set of SVG icons, the Angular web application was to be installed on a server that didn't have access to the internet, so the images had to be local. I wanted to use the Mat-Icon component out of the box &lt;em&gt;(in an early version of the project I had a custom icon component)&lt;/em&gt;. I still wanted to be able to change the colours of the icons at various stages throughout the application based on certain conditions as well as on hover. This post covers how I achieved that.&lt;/p&gt;

&lt;h3&gt;
  
  
  SVG Icons
&lt;/h3&gt;

&lt;p&gt;There are a number of different ways to register an icon with the Mat-Icon component, this post discusses  &lt;code&gt;addSvgIcon&lt;/code&gt;, the others are &lt;code&gt;addSvgIconInNamespace, addSvgIconLiteral or addSvgIconLiteralInNamespace&lt;/code&gt; and are all methods of &lt;code&gt;MatIconRegistry&lt;/code&gt; and I might cover these in more detail in a future post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an Angular project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; &lt;em&gt;In this post, I'm not going to step through the creation of an Angular application as there are so many already online. Plus I tend to create Nx workspaces for all my Angular projects as I prefer this project layout. I plan to do a blog post on this very soon, for the moment though see &lt;a href="https://nx.dev/angular/getting-started/what-is-nx" rel="noopener noreferrer"&gt;Getting started with Narwhal's Nx Workspaces&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In the newly created Angular project, create a &lt;code&gt;shared directory&lt;/code&gt; and add a new file named &lt;code&gt;material.module.ts&lt;/code&gt;. I like to separate Angular Material imports into their own module, I also create a separate module for other 3rd party components, just makes it easier to import later, especially using the Nx workspace layout and feature folders.&lt;/p&gt;

&lt;p&gt;In the material.module.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Material Module example.&lt;/span&gt;
&lt;span class="c1"&gt;// All other Angular Material component imports here&lt;/span&gt;
&lt;span class="c1"&gt;// but the important ones are...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/material/icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Other material imports removed for brevity,&lt;/span&gt;
    &lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Other material exports removed for brevity,&lt;/span&gt;
    &lt;span class="nx"&gt;MatIconModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;entryComponents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MatIconRegistry&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;class&lt;/span&gt;  &lt;span class="nc"&gt;MaterialModule&lt;/span&gt;  &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MatIconModule is the module for the component, and the MatIconRegistry is a service to register and display icons. Add a reference to the  &lt;code&gt;material.module.ts&lt;/code&gt; in the &lt;code&gt;app.module.ts&lt;/code&gt;. Don't forget to export it as well, otherwise it won't be available and Angular will not know anything about the Angular Material components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Include the material module in app.module.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;BrowserModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;NgModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;AppComponent&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;BrowserAnimationsModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser/animations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="nx"&gt;MaterialModule&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./shared/material.module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;declarations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;BrowserAnimationsModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nx"&gt;MaterialModule&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="kd"&gt;class&lt;/span&gt;  &lt;span class="nc"&gt;AppModule&lt;/span&gt;  &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the Angular Material components set up and configured we need to register the icons, before we can use them. For the moment we are just going to add these to the &lt;code&gt;app.component.ts&lt;/code&gt; to get up and running, we'll look at a better method later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First Example&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistery&lt;/span&gt;
    &lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/home.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/add.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// or we could do this, and chain the addsvgIcon methods.&lt;/span&gt;
    &lt;span class="c1"&gt;// we'll use this method going forward in this post&lt;/span&gt;
    &lt;span class="c1"&gt;// {&lt;/span&gt;
    &lt;span class="c1"&gt;//      this.matIconRegistry.addSvgIcon('home','assets/img/icon/home.svg')&lt;/span&gt;
    &lt;span class="c1"&gt;//      .addSvgIcon('add','assets/img/icon/add.svg');&lt;/span&gt;
    &lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to the &lt;code&gt;app.component.html&lt;/code&gt; page, we'll discuss this in more detail in a bit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- First HTML example --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-icon&lt;/span&gt; &lt;span class="na"&gt;svgIcon=&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mat-icon&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;mat-icon&lt;/span&gt; &lt;span class="na"&gt;svgIcon=&lt;/span&gt;&lt;span class="s"&gt;"add"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/mat-icon&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we are not going to see much in the browser as we have an issue with the image URL. If you open the browsers console section, you will see the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Error: unsafe value used in a resource URL context&lt;/code&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So what does this error mean? A brief explanation from the Angular Material website says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To prevent an Cross Site Scripting (XSS), SVG URL's and HTML strings passed to MatIconRegistry must be marked as trusted by the Angular's DomSanitizer service. Icons are fetched via XmlHttpRequest and must have their URL's on the same domain as the containing page or configured to allow cross-domain access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So lets add the DomSanitizer and fix this issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Second Example - with the DomSanitizer&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.scss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DomSanitizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MatIconRegistery&lt;/span&gt;
    &lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matIconRegistry&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/home.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSvgIcon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domSanitizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bypassSecurityTrustResourceUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assets/img/icon/add.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// add other icons here....;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call to &lt;code&gt;bypassSecurityTrustResourceUrl&lt;/code&gt; takes a URL as a parameter and sanitizes it so that an attacker cannot inject a 'JavaScript:` URL for example, see official documentation on &lt;a href="https://angular.io/api/platform-browser/DomSanitizer#description" rel="noopener noreferrer"&gt;DomSanitizer&lt;/a&gt;. &lt;br&gt;
Now that we have this in place we should now see two icons in the browser.&lt;/p&gt;

&lt;p&gt;If we have a lot of icons to add this means lots of typing and lots of repetitive code, so lets refactor this some more. Start by removing all of this code (including the constructor) from the app.component.ts. This shouldn't really be in the app.component.ts file. Lets create another new module in the shared directory and call it &lt;code&gt;icon.module.ts&lt;/code&gt; and then add the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
// Third Example - icon module&lt;br&gt;
import  {  NgModule  }  from  '@angular/core';&lt;br&gt;
import  {  DomSanitizer,  SafeResourceUrl  }  from  '@angular/platform-browser';&lt;br&gt;
import  {  MaterialModule  }  from  '../shared/material.module';&lt;br&gt;
import  {  MatIconRegistry  }  from  '@angular/material/icon';&lt;br&gt;
@NgModule({&lt;br&gt;
declarations:  [],&lt;br&gt;
imports:  [MaterialModule],&lt;br&gt;
exports:  [],&lt;br&gt;
providers:  []&lt;br&gt;
})&lt;br&gt;
export  class  IconModule  {&lt;br&gt;
private  path:  string  =  '../../assets/images'; // change this &lt;br&gt;
constructor(&lt;br&gt;
private  domSanitizer:  DomSanitizer,&lt;br&gt;
public  matIconRegistry:  MatIconRegistry&lt;br&gt;
)  {&lt;br&gt;
this.matIconRegistry&lt;br&gt;
.addSvgIcon('home',  this.setPath(&lt;/code&gt;${this.path}/home.svg&lt;code&gt;))&lt;br&gt;
.addSvgIcon('add',  this.setPath(&lt;/code&gt;${this.path}/file-plus.svg&lt;code&gt;));&lt;br&gt;
}&lt;br&gt;
private  setPath(url:  string):  SafeResourceUrl  {&lt;br&gt;
    return  this.domSanitizer.bypassSecurityTrustResourceUrl(url);&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Overall, that's not too bad, we are only writing out the domSanitizer code once in the &lt;code&gt;private&lt;/code&gt; method but more importantly, all the code is out of the &lt;code&gt;app.component.ts&lt;/code&gt; file and is now a self contained module. If there are a lot of icons to add then this file will get a bit long, but the typing has gotten shorter (well, a little shorter at least). You could change the &lt;code&gt;constructor&lt;/code&gt;to iterate through a &lt;code&gt;.json&lt;/code&gt; file of image names, the path wouldn't change and could be a &lt;code&gt;const&lt;/code&gt;, which would only mean maintaining a &lt;code&gt;.json&lt;/code&gt; file with any new images. I might look at doing that in a follow up post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Don't forget to add this new icon.module.ts to the app.module.ts otherwise it won't work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Mat-Icon component
&lt;/h2&gt;

&lt;p&gt;So how do we use the mat-icon component? As seen earlier in this post we add the following code to our app.components.html page.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
This is a very simple example is showing how to put a &lt;code&gt;home&lt;/code&gt; and an &lt;code&gt;add&lt;/code&gt; icon on a page/component. This is not too dissimilar to how we would use this component with web fonts, but we are now using the &lt;code&gt;svgIcon&lt;/code&gt; input property. The value we give to this input is the first parameter used in our call to register the &lt;code&gt;.addSvgIcon('home', ...)&lt;/code&gt; in this case &lt;code&gt;home&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we have an icon in place, how do we change the colour of the icon when someone hovers over it for example. &lt;/p&gt;

&lt;h2&gt;
  
  
  Change the  icon colour
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Home icon&lt;/strong&gt;.&lt;br&gt;
Example icon copy this into a file with the SVG extension.&lt;br&gt;
&lt;code&gt;&lt;/code&gt;&lt;code&gt;svg&lt;br&gt;
&amp;lt;svg version="1.1" width="24" height="24" viewBox="0 0 24 24"&amp;gt;&amp;lt;path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" fill="#00FFFF" /&amp;gt;&amp;lt;/svg&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
In the above XML, I've removed all the namespaces etc... The main part here is the &lt;code&gt;fill="#00FFFF"&lt;/code&gt; this sets the colour for the image, in this case to AquaMarine. &lt;/p&gt;

&lt;p&gt;If the &lt;code&gt;fill="#00..."&lt;/code&gt; property is not there and you want a different colour to the default black, then you can add it to the path as above, it's optional.&lt;/p&gt;

&lt;p&gt;I usually add the &lt;code&gt;fill&lt;/code&gt; property and set it to white and then change them in SCSS as and when required (but because this example had a white the background I used another colour). &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`html&lt;br&gt;
!&amp;lt;-- Second HTML example --&amp;gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!&amp;amp;lt;-- other code omitted for brevity --&amp;amp;gt; 
&amp;lt;a&amp;gt;

&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Add a class to the mat-icon html tag, as above and then add the SCSS, for the class. So when a user hovers over the button the colour of the icon changes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;scss&lt;br&gt;
.btn-icon  {&lt;br&gt;
    &amp;amp;:hover  {&lt;br&gt;
        path {&lt;br&gt;
            fill:  rgba(#00ffff,  1);&lt;br&gt;
            }&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Note&lt;/strong&gt; One thing I did forget to add to the app.component.ts above, was the following line: &lt;br&gt;
&lt;code&gt;encapsulation:  ViewEncapsulation.None&lt;/code&gt;, without this then the hover affect doesn't work. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;typescript&lt;br&gt;
@Component({&lt;br&gt;
selector:  'mat-icon-demo-root',&lt;br&gt;
templateUrl:  './app.component.html',&lt;br&gt;
styleUrls:  ['./app.component.scss'],&lt;br&gt;
encapsulation:  ViewEncapsulation.None,&lt;br&gt;
})&lt;br&gt;
export  class  AppComponent  {&lt;br&gt;
    title  =  'mat-icon-demo';&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Enjoy! &lt;/p&gt;

&lt;p&gt;There is a github repo for this blog post at:&lt;br&gt;
&lt;a href="https://github.com/Anglebrackets-io/mat-icon-demo" rel="noopener noreferrer"&gt;https://github.com/Anglebrackets-io/mat-icon-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>angularmaterial</category>
      <category>typescript</category>
      <category>svg</category>
    </item>
  </channel>
</rss>
