<?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: Alexander Thalhammer</title>
    <description>The latest articles on DEV Community by Alexander Thalhammer (@lxt).</description>
    <link>https://dev.to/lxt</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1730783%2F7e7043e6-efa4-4619-a957-68057f3eb313.jpg</url>
      <title>DEV Community: Alexander Thalhammer</title>
      <link>https://dev.to/lxt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lxt"/>
    <language>en</language>
    <item>
      <title>Why Angular ARIA in v21 is pretty neat</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Mon, 01 Dec 2025 21:48:45 +0000</pubDate>
      <link>https://dev.to/lxt/why-angular-aria-in-v21-is-pretty-neat-1652</link>
      <guid>https://dev.to/lxt/why-angular-aria-in-v21-is-pretty-neat-1652</guid>
      <description>&lt;p&gt;&lt;em&gt;Angular ARIA&lt;/em&gt; is a collection of &lt;strong&gt;headless, accessible directives&lt;/strong&gt; that implement common &lt;strong&gt;WAI-ARIA patterns&lt;/strong&gt;. The directives handle &lt;strong&gt;keyboard interactions&lt;/strong&gt;, &lt;a href="https://www.angulararchitects.io/blog/aria-roles-attributes/" rel="noopener noreferrer"&gt;&lt;strong&gt;ARIA attributes&lt;/strong&gt;&lt;/a&gt;, &lt;strong&gt;focus management&lt;/strong&gt;, and &lt;strong&gt;screen reader support&lt;/strong&gt;. All you have to do is provide the &lt;strong&gt;HTML&lt;/strong&gt; structure, &lt;strong&gt;CSS&lt;/strong&gt; styling, and &lt;strong&gt;business logic&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;You feel like you've already read this before? Perfectly possible, because I've just copied and pasted the paragraph from the official &lt;em&gt;&lt;a href="https://angular.dev/guide/aria/overview#what-is-angular-aria" rel="noopener noreferrer"&gt;Angular ARIA docs&lt;/a&gt;&lt;/em&gt;. Why should I reinvent the wheel, right? It perfectly summarizes the concept.&lt;/p&gt;

&lt;p&gt;So the &lt;em&gt;Angular team&lt;/em&gt; just (together with &lt;em&gt;v21&lt;/em&gt; on &lt;em&gt;Nov 19th, 2025&lt;/em&gt;) released a brand-new collection of components – I mean directives – that implement common web patterns while letting you choose your own HTML, styling (CSS, SCSS, or even Tailwind), and business logic (I'd suggest TypeScript).&lt;/p&gt;

&lt;p&gt;This is pretty much the opposite approach of &lt;a href="https://material.angular.dev/" rel="noopener noreferrer"&gt;&lt;em&gt;Angular Material&lt;/em&gt;&lt;/a&gt;, which is an opinionated Plug &amp;amp; Play Design System. Both are built on top of the &lt;a href="https://www.angulararchitects.io/blog/angular-cdk-accessibility/" rel="noopener noreferrer"&gt;&lt;em&gt;Angular CDK&lt;/em&gt; A11y&lt;/a&gt; package. So what do we get here?&lt;/p&gt;

&lt;h2&gt;
  
  
  Components
&lt;/h2&gt;

&lt;p&gt;In the first release with &lt;em&gt;v21&lt;/em&gt;, the following &lt;strong&gt;12 components / directives / UI patterns&lt;/strong&gt; are available:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;search and select&lt;/th&gt;
&lt;th&gt;navigation / menu&lt;/th&gt;
&lt;th&gt;content organization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/autocomplete" rel="noopener noreferrer"&gt;Autocomplete&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/menu" rel="noopener noreferrer"&gt;Menu&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/accordion" rel="noopener noreferrer"&gt;Accordion&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/listbox" rel="noopener noreferrer"&gt;Listbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/menubar" rel="noopener noreferrer"&gt;Menubar&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/tabs" rel="noopener noreferrer"&gt;Tabs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://angular.dev/guide/aria/select" rel="noopener noreferrer"&gt;Select&lt;/a&gt; &amp;amp; &lt;a href="https://angular.dev/guide/aria/multiselect" rel="noopener noreferrer"&gt;Multiselect&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/toolbar" rel="noopener noreferrer"&gt;Toolbar&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/tree" rel="noopener noreferrer"&gt;Tree&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/combobox" rel="noopener noreferrer"&gt;Combobox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://angular.dev/guide/aria/grid" rel="noopener noreferrer"&gt;Grid&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt; &lt;br&gt;
There are – of course – plans to extend this in the future 😏&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this neat?
&lt;/h2&gt;

&lt;p&gt;Using the &lt;em&gt;Angular ARIA&lt;/em&gt; directives is a great way to build &lt;em&gt;Angular apps&lt;/em&gt; by &lt;strong&gt;offloading some of the heavy lifting&lt;/strong&gt; to the &lt;em&gt;Angular team&lt;/em&gt; while still allowing &lt;strong&gt;full customization&lt;/strong&gt; and &lt;strong&gt;user-interface branding&lt;/strong&gt;. BTW: Check out &lt;a href="https://www.youtube.com/watch?v=ihOlHa3I7eI" rel="noopener noreferrer"&gt;Jessica's talk at ViteConf on YT&lt;/a&gt; for other things in &lt;em&gt;NG v21&lt;/em&gt; that are neat.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to use Angular ARIA?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You &lt;strong&gt;know&lt;/strong&gt; how to style things (at least Senior in CSS)&lt;/li&gt;
&lt;li&gt;You build a custom &lt;strong&gt;Design System&lt;/strong&gt; or an &lt;strong&gt;enterprise component library&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You don't use any Design System and instead handcraft everything yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When to avoid Angular ARIA?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You &lt;strong&gt;don't know&lt;/strong&gt; how to style (Junior in CSS – join my &lt;a href="https://www.angulararchitects.io/en/training/angular-styling-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;NG Styling Workshop&lt;/strong&gt;&lt;/a&gt; 🎨)&lt;/li&gt;
&lt;li&gt;You use a &lt;strong&gt;Design System&lt;/strong&gt; or an &lt;strong&gt;enterprise component library&lt;/strong&gt; (should be covered there)&lt;/li&gt;
&lt;li&gt;You don't care about accessibility (come on – don't be a jerk!)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Angular Styling Best Choices Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Level&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Angular Material&lt;/strong&gt; 🔌&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;3rd-party DS&lt;/strong&gt; 🎨&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Angular ARIA&lt;/strong&gt; ♿&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Custom UI&lt;/strong&gt; 🛠️&lt;/th&gt;
&lt;th&gt;
&lt;strong&gt;Custom DS&lt;/strong&gt; 🏢&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🎨 Very opinionated&lt;/td&gt;
&lt;td&gt;⚡ Plug &amp;amp; Play&lt;/td&gt;
&lt;td&gt;🤷 Depends on you&lt;/td&gt;
&lt;td&gt;🤷 Depends on you&lt;/td&gt;
&lt;td&gt;🤷 Depends on you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accessibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⚡ Plug &amp;amp; Play&lt;/td&gt;
&lt;td&gt;🙂 Usually okay&lt;/td&gt;
&lt;td&gt;⭐ Best Choice&lt;/td&gt;
&lt;td&gt;🤷 Depends on you&lt;/td&gt;
&lt;td&gt;🤷 Depends on you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Effort&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;⭐ Very low&lt;/td&gt;
&lt;td&gt;🙂 Medium&lt;/td&gt;
&lt;td&gt;😬 High&lt;/td&gt;
&lt;td&gt;😅 Only if 1 app&lt;/td&gt;
&lt;td&gt;💀 Boss will kill ya&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CSS Skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🟢 None&lt;/td&gt;
&lt;td&gt;🟡 Junior&lt;/td&gt;
&lt;td&gt;🔴 Senior&lt;/td&gt;
&lt;td&gt;🔴 Senior&lt;/td&gt;
&lt;td&gt;🟣 Master&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🔒 Hard&lt;/td&gt;
&lt;td&gt;🤷 Depends&lt;/td&gt;
&lt;td&gt;✔ Included&lt;/td&gt;
&lt;td&gt;♾️ No limits&lt;/td&gt;
&lt;td&gt;⭐ Best Choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NG Updates&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🚫 Don’t customize&lt;/td&gt;
&lt;td&gt;😬 Sometimes painful&lt;/td&gt;
&lt;td&gt;🙂 Smooth&lt;/td&gt;
&lt;td&gt;😎 Very smooth&lt;/td&gt;
&lt;td&gt;😵 A lot of work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use it for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;🚀 Prototypes &amp;amp; demos&lt;/td&gt;
&lt;td&gt;💸 Low-budget/legacy&lt;/td&gt;
&lt;td&gt;🌱 Greenfield&lt;/td&gt;
&lt;td&gt;🎨 Hobbies&lt;/td&gt;
&lt;td&gt;🏢 Enterprise apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt; &lt;br&gt;
Stay tuned for my next blog post comparing popular &lt;strong&gt;3rd party Design Systems&lt;/strong&gt; for Angular vs. &lt;em&gt;Angular Material&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workshops
&lt;/h2&gt;

&lt;p&gt;If you want to deep-dive into &lt;em&gt;Angular&lt;/em&gt;, we offer a variety of workshops – in English and German.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; 📈 (including Design Systems &amp;amp; ARIA)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-styling-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;NG Styling Workshop&lt;/strong&gt;&lt;/a&gt; 🎨 (including Design Systems)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt; ♿ (including WAI ARIA)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt; 🚀 (because it's neat)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;em&gt;Angular ARIA&lt;/em&gt; delivers headless, accessible building blocks for fully customizable &lt;em&gt;Angular components&lt;/em&gt;. It shifts complexity – keyboard handling, ARIA attributes, focus management – to the &lt;em&gt;Angular team&lt;/em&gt;, making it ideal for custom Design Systems and tailored UIs without relying on heavy, opinionated frameworks.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Comment it &lt;a href="https://www.linkedin.com/posts/thalhammer_angular-aria-initial-version-released-angulararchitects-activity-7401368955434545152-_bms" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://bsky.app/profile/lxt.bsky.social/post/3m6xfgslozs25" rel="noopener noreferrer"&gt;bsky&lt;/a&gt;, &lt;a href="https://x.com/LX_T/status/1995603392560033812" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://www.reddit.com/r/angular/comments/1odc1ni/comment/nrrzc6k/" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>angular</category>
      <category>design</category>
    </item>
    <item>
      <title>Enhancing A11y with Angular CDK</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Thu, 05 Jun 2025 10:00:00 +0000</pubDate>
      <link>https://dev.to/lxt/enhancing-a11y-with-angular-cdk-9l6</link>
      <guid>https://dev.to/lxt/enhancing-a11y-with-angular-cdk-9l6</guid>
      <description>&lt;p&gt;The &lt;a href="https://material.angular.dev/cdk/a11y/overview" rel="noopener noreferrer"&gt;&lt;strong&gt;@angular/cdk/a11y&lt;/strong&gt;&lt;/a&gt; package is part of the &lt;em&gt;Angular Component Dev Kit (CDK)&lt;/em&gt; and provides a collection of services, directives, SASS mixins, and other utilities to improve the Accessibility (A11y) of your &lt;em&gt;Angular apps&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;These tools are invaluable when building reusable, presentation-focused components with screen-reader announcements, robust keyboard and focus support, high-contrast theming, and more. They’re especially useful if you’re creating an &lt;em&gt;Angular Component Library&lt;/em&gt; or a &lt;em&gt;Design System&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Below are some of my favorites, along with brief code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  LiveAnnouncer for Screen Readers
&lt;/h2&gt;

&lt;p&gt;The first service we want to highlight is the &lt;strong&gt;&lt;a href="https://material.angular.dev/cdk/a11y/overview#liveannouncer" rel="noopener noreferrer"&gt;LiveAnnouncer&lt;/a&gt;&lt;/strong&gt;. It allows you to announce messages to screen readers, which is particularly useful for providing feedback after user interactions. It's so simple: I often replace my &lt;code&gt;console.log&lt;/code&gt; calls with announcements.&lt;/p&gt;

&lt;p&gt;Here's an example of that:&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;liveAnnouncer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &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;flights&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;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// console.log('Found ' + this.flights().length + ' flights');&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;liveAnnouncer&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Found &lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;flights&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; flights&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// console.log('No flights found');&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;liveAnnouncer&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No flights found&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;Make sure to test your announcements with the use of a &lt;strong&gt;&lt;a href="https://www.angulararchitects.io/blog/accessibility-testing-tools" rel="noopener noreferrer"&gt;screen reader&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus Tools
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Angular CDK&lt;/em&gt; also includes several tools supporting your focus management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus Trap
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;&lt;strong&gt;&lt;a href="https://material.angular.dev/cdk/a11y/overview#focustrap" rel="noopener noreferrer"&gt;FocusTrap&lt;/a&gt;&lt;/strong&gt; Directive&lt;/em&gt; allows you to trap focus within a specific element, ensuring that keyboard navigation remains within that element until the user explicitly exits it.&lt;/p&gt;

&lt;p&gt;This is particularly useful for modal dialogs or pop-ups:&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;dialog&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"awesome-dialog"&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!–&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Yay&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;focus&lt;/span&gt; &lt;span class="na"&gt;won&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt; &lt;span class="na"&gt;leave&lt;/span&gt; &lt;span class="na"&gt;this&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want focus to jump in automatically, enable the &lt;strong&gt;&lt;code&gt;cdkTrapFocusAutoCapture&lt;/code&gt;&lt;/strong&gt; flag:&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;dialog&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"awesome-dialog"&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt; &lt;span class="na"&gt;[cdkTrapFocusAutoCapture]=&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;&lt;/span&gt;&lt;span class="err"&gt;!–&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Yay&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;focus&lt;/span&gt; &lt;span class="na"&gt;won&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt; &lt;span class="na"&gt;leave&lt;/span&gt; &lt;span class="na"&gt;this&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this flag set to &lt;code&gt;true&lt;/code&gt;, the focus will automatically be captured when the dialog opens. However, it may not always be the best choice to have an element automatically capture focus, so we can avoid this with a trick:&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;dialog&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"awesome-dialog"&lt;/span&gt; &lt;span class="na"&gt;cdkTrapFocus&lt;/span&gt; &lt;span class="na"&gt;[cdkTrapFocusAutoCapture]=&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;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"awesome-dialog__title"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;cdkFocusInitial&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;!–&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Yay&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;focus&lt;/span&gt; &lt;span class="na"&gt;won&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt; &lt;span class="na"&gt;leave&lt;/span&gt; &lt;span class="na"&gt;this&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt; &lt;span class="na"&gt;--&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding &lt;strong&gt;&lt;code&gt;cdkFocusInitial&lt;/code&gt;&lt;/strong&gt; the title will be secretly focused initially. Since it has tabindex &lt;code&gt;-1&lt;/code&gt;, it won't be focusable by keyboard navigation afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus Regions
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;&lt;strong&gt;&lt;a href="https://material.angular.dev/cdk/a11y/overview#regions" rel="noopener noreferrer"&gt;Regions&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt; are a powerful tool that allows you to define a specific area of your application where focus should be managed. This is particularly useful for complex components like dropdowns or menus, where you want to ensure that focus remains within the component while it is open.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cdkFocusRegionStart&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cdkFocusRegionEnd&lt;/code&gt;&lt;/strong&gt; and optionally&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cdkFocusInitial&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of how to use the &lt;em&gt;&lt;strong&gt;Regions&lt;/strong&gt;&lt;/em&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;nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"awesome"&lt;/span&gt; &lt;span class="na"&gt;ariaCurrentWhenActive=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;cdkFocusRegionStart&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Focus region start&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"awesome"&lt;/span&gt; &lt;span class="na"&gt;ariaCurrentWhenActive=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Another focusable link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"awesome"&lt;/span&gt; &lt;span class="na"&gt;ariaCurrentWhenActive=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;cdkFocusInitial&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Initially focused&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;routerLink&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"awesome"&lt;/span&gt; &lt;span class="na"&gt;ariaCurrentWhenActive=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt; &lt;span class="na"&gt;cdkFocusRegionEnd&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Focus region end&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: To learn about the &lt;code&gt;ariaCurrentWhenActive&lt;/code&gt; please read my post on &lt;a href="https://www.angulararchitects.io/blog/accessible-angular-routes/" rel="noopener noreferrer"&gt;&lt;em&gt;Accessible Angular Routes&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Focus Monitor
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;FocusMonitor&lt;/strong&gt; is a service that allows you to monitor focus changes within your application. It can be used to track when an element gains or loses focus, which is particularly useful for debugging focus-related issues.&lt;/p&gt;

&lt;p&gt;This service can be injected into your components or services, and you can subscribe to focus changes:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DestroyRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;viewChild&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;FocusMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FocusOrigin&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/cdk/a11y&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;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-navbar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;nav #observed class="awesome-nav-cnt"&amp;gt;&amp;lt;!-- children --&amp;gt;&amp;lt;/nav&amp;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;AwesomeFocusMonitorComponent&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;destroyRef&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;DestroyRef&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;focusMonitor&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;FocusMonitor&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;observedElementRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewChild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;&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;observed&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;effect&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;observedElementRef&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="nf"&gt;observedElementRef&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// effect will run when the view is initialized&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;focusMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observedElementRef&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="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FocusOrigin&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&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;destroyRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDestroy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;focusMonitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;observedElementRef&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;This is particularly useful for debugging focus-related issues, as it allows you to see when an element gains or loses focus.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FocusOrigin&lt;/code&gt; can be one of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;'mouse'&lt;/code&gt; the element was focused with the mouse&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'keyboard'&lt;/code&gt; it was focused with the keyboard&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'touch'&lt;/code&gt; it was focused by touching on a touchscreen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'program'&lt;/code&gt; it was focused programmatically, whereas&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; indicates the element was blurred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, this is also useful to check if a touch device is being used 😏&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling utilities
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;Angular A11y package&lt;/em&gt; also includes two useful Sass mixins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hidden Elements
&lt;/h3&gt;

&lt;p&gt;Screen readers and other assistive technology skip elements that have &lt;code&gt;display: none&lt;/code&gt;, &lt;code&gt;visibility: hidden&lt;/code&gt;, &lt;code&gt;opacity: 0&lt;/code&gt;, &lt;code&gt;height: 0&lt;/code&gt;, or &lt;code&gt;width: 0&lt;/code&gt;. In some cases, you may need to visually hide an element while &lt;strong&gt;keeping it available to assistive technology&lt;/strong&gt; (e.g. Screen Readers).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@use&lt;/span&gt; &lt;span class="s1"&gt;'@angular/cdk'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;cdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;a11y-visually-hidden&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;class=&lt;/span&gt;&lt;span class="s"&gt;"awesome-toggle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cdk-visually-hidden"&lt;/span&gt; &lt;span class="nt"&gt;/&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;h3&gt;
  
  
  High Contrast Mode
&lt;/h3&gt;

&lt;p&gt;Some operating systems and/or devices include a &lt;strong&gt;High Contrast Mode&lt;/strong&gt;. The &lt;em&gt;Angular A11y package&lt;/em&gt; provides a Sass mixin that lets you define styles that only apply in high contrast mode. To create a high contrast style, just wrap it into the high-contrast mixin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scss"&gt;&lt;code&gt;&lt;span class="k"&gt;@use&lt;/span&gt; &lt;span class="s1"&gt;'@angular/cdk'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@include&lt;/span&gt; &lt;span class="nd"&gt;cdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;high-contrast&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;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;gold&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 mixin works by targeting the forced-colors media query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;For those looking to deepen their &lt;em&gt;Angular&lt;/em&gt; expertise, we offer a range of workshops – both in English and German:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;♿ &lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📈 &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; (including accessibility-related topics)&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;With these tools, you can significantly enhance the accessibility of your &lt;em&gt;Angular Apps&lt;/em&gt;. They help ensure that your components are not only functional but also user-friendly for everyone, including those with disabilities.&lt;/p&gt;

&lt;p&gt;One last thing: In this Code lab by Google, you can find some exercises on the most important &lt;em&gt;Angular CDK A11y&lt;/em&gt; tools &lt;a href="https://codelabs.developers.google.com/angular-a11y#8" rel="noopener noreferrer"&gt;https://codelabs.developers.google.com/angular-a11y#8&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  That's all folks!
&lt;/h3&gt;

&lt;p&gt;This is the last edition of our A11y series. I hope you found it helpful and learned something new about making your &lt;em&gt;Angular Apps&lt;/em&gt; more accessible and user-friendly for all of us, and that you are ready for the &lt;strong&gt;European Accessibility Act (EAA)&lt;/strong&gt;. If uncertain, please go back to the start of the &lt;strong&gt;&lt;a href="https://www.angulararchitects.io/blog/web-accessibility-in-angular/" rel="noopener noreferrer"&gt;A11y blog series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://material.angular.dev/cdk/a11y/overview" rel="noopener noreferrer"&gt;Angular CDK Accessibility – official docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/angular-a11y#8" rel="noopener noreferrer"&gt;Google codelab on A11y&lt;/a&gt; by Emma Twersky&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Building Accessible Forms with Angular</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Mon, 02 Jun 2025 10:00:00 +0000</pubDate>
      <link>https://dev.to/lxt/building-accessible-forms-with-angular-38ii</link>
      <guid>https://dev.to/lxt/building-accessible-forms-with-angular-38ii</guid>
      <description>&lt;p&gt;Accessible &lt;em&gt;Angular Forms&lt;/em&gt; are essential to ensure that all our users – including those with disabilities – can interact with our &lt;em&gt;Angular App&lt;/em&gt; effectively. By implementing forms with Accessibility (A11y) in mind, we meet legal (&lt;a href="https://www.angulararchitects.io/blog/web-accessibility-in-angular/" rel="noopener noreferrer"&gt;EAA 2025&lt;/a&gt; ♿) and ethical standards, and create a more inclusive experience. Accessible forms work better with screen readers, keyboard navigation, and assistive technologies – which not only helps users with impairments but also &lt;strong&gt;enhances the overall UX&lt;/strong&gt; of our &lt;em&gt;Angular Apps&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular Forms
&lt;/h2&gt;

&lt;p&gt;In &lt;em&gt;Angular,&lt;/em&gt; we have two types of forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template-driven Forms&lt;/strong&gt;: These are simpler and more declarative, relying on &lt;em&gt;Angular Directives&lt;/em&gt; to create forms. They are built around the &lt;code&gt;NgModel&lt;/code&gt; directive and generally used for simple forms. As the name suggests, these are implemented in the template through attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reactive Forms&lt;/strong&gt;: These are more powerful and flexible, allowing for complex form structures and dynamic validation. They are built in the component class around the &lt;code&gt;FormGroup&lt;/code&gt; and &lt;code&gt;FormControl&lt;/code&gt; classes. Most often the &lt;code&gt;FormBuilder&lt;/code&gt; service is used to create the forms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In 2025, the &lt;em&gt;Angular team&lt;/em&gt; is expected to work on &lt;strong&gt;&lt;a href="https://angular.dev/roadmap#improving-the-angular-developer-experience" rel="noopener noreferrer"&gt;Signal integration&lt;/a&gt;&lt;/strong&gt; for &lt;em&gt;Angular Forms&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;However, no matter which type of form you choose – and whether you migrate from Observables to Signals or not – the goal is to ensure that your forms are accessible to all users. This includes providing proper labels, error messages, and keyboard navigation. To keep the focus on A11y, we will use the simpler template-driven approach without any reactivity in our examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyboard Navigation &amp;amp; Tab Focus
&lt;/h2&gt;

&lt;p&gt;Keyboard navigation is a critical part of form A11y. Many users rely on the keyboard – rather than a mouse – to move through a form using the Tab, Shift + Tab, Arrow, and Enter keys. Ensuring a logical tab order, using semantic HTML elements like &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, and avoiding custom controls that break default behavior, helps users navigate efficiently. You should not change the order. However, you can add &lt;code&gt;tabindex="0"&lt;/code&gt; to include non-interactive elements or custom components, and use &lt;code&gt;tabindex="-1"&lt;/code&gt; to remove elements from the tab order.&lt;/p&gt;

&lt;p&gt;Additionally, visual focus indicators (like outlines, at least 2 if not 3px width) should be clearly visible to show which element is currently active. When done right, keyboard-friendly forms not only improve A11y but also lead to a smoother and more intuitive experience for all users 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  Form Fields
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Labels &amp;amp; type attribute
&lt;/h3&gt;

&lt;p&gt;To ensure A11y, always associate &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; elements with their corresponding form controls using the for and id attributes. This improves support for screen readers and enables better keyboard navigation – clicking a label should focus the related input. Make sure each id is unique, especially when using multiple forms on the same page. Additionally, specify the correct type for all &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements to ensure proper behavior, like submitting a form when pressing Enter on a &lt;code&gt;&amp;lt;button type="submit"&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;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;From&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;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt; &lt;span class="err"&gt;[...]&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grouping fields
&lt;/h3&gt;

&lt;p&gt;When we have a group of related inputs, especially radio buttons or checkboxes, screen readers benefit from extra semantic context by &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;legend&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;fieldset&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Flight Class&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&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;"economy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Economy&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;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"economy"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"economy"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&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;"business"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Business&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;type=&lt;/span&gt;&lt;span class="s"&gt;"radio"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"class"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"business"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"business"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides context for assistive tech. Without this, users may hear "Economy" and "Business" without understanding they are part of a group. The &lt;code&gt;&amp;lt;fieldset&amp;gt;&lt;/code&gt; element groups related controls, while the &lt;code&gt;&amp;lt;legend&amp;gt;&lt;/code&gt; provides a caption for the group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Required fields
&lt;/h3&gt;

&lt;p&gt;When a form element (like &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt;) must have a value, use the &lt;code&gt;required&lt;/code&gt; attribute. This prevents form submission unless the required fields are filled out, and helps users with assistive technologies understand which fields need valid content. For example, add the &lt;code&gt;required&lt;/code&gt; attribute to both input fields in your form, and consider adding an asterisk (*) as an additional visual indicator.&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;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Name (*)&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;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Autocomplete
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;autocomplete&lt;/code&gt; attribute is a powerful tool for improving the user experience in forms. It allows browsers to remember and suggest previously entered values, making it easier for users to fill out forms quickly. By using the &lt;code&gt;autocomplete&lt;/code&gt; attribute, you can specify the type of data expected in each field, such as &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, or &lt;code&gt;address&lt;/code&gt;. This not only enhances usability but also helps with A11y by providing clear context for screen readers and assistive technologies.&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;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"phone"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To avoid autocomplete, we should set the &lt;code&gt;autocomplete&lt;/code&gt; attribute to &lt;code&gt;off&lt;/code&gt;. This is especially useful for sensitive information like passwords or when you want to ensure that users enter fresh data.&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;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ARIA attributes
&lt;/h3&gt;

&lt;p&gt;ARIA attributes (more on them in our &lt;a href="https://www.angulararchitects.io/blog/aria-roles-attributes/" rel="noopener noreferrer"&gt;last post&lt;/a&gt;) can enhance the A11y of our &lt;em&gt;Angular Forms&lt;/em&gt; by providing additional context to assistive technologies when native HTML alone isn’t enough. Attributes like &lt;code&gt;aria-label&lt;/code&gt; or &lt;code&gt;aria-labelledby&lt;/code&gt; can offer accessible names for form controls when visible labels aren’t practical. While ARIA should never replace semantic HTML, it’s a powerful tool to bridge A11y gaps and ensure all users can understand and interact with your forms effectively.&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;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Search flights"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Search..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For validation, &lt;code&gt;aria-invalid="true"&lt;/code&gt; can indicate a validation error and &lt;code&gt;aria-describedby&lt;/code&gt; can provide additional context or instructions. For example, if a user enters an invalid email address, you can set &lt;code&gt;aria-invalid="true"&lt;/code&gt; on the input field and use &lt;code&gt;aria-describedby&lt;/code&gt; to point to an error message that explains the issue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@let hasFromErrors = flightSearchForm.controls['from'] &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; flightSearchForm.controls['from'].touched &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; flightSearchForm.controls['from'].errors;

&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"!!hasFromErrors"&lt;/span&gt; &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"hasFromErrors ? 'from_error' : null"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Speaking about error messages, let's take a look at how to handle them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error messages
&lt;/h2&gt;

&lt;p&gt;Accessible error messages help all users understand and correct form issues. Use &lt;code&gt;aria-describedby&lt;/code&gt; (as in the example above) to link inputs to their error messages, and add &lt;code&gt;aria-live="polite"&lt;/code&gt; to ensure screen readers announce them when they appear. Messages should be clear, concise, and not rely on color alone – always provide text or icons for better clarity.&lt;/p&gt;

&lt;p&gt;Personal preferences of &lt;strong&gt;error messages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only &lt;strong&gt;after user interaction&lt;/strong&gt; with the form: on blur ("touched" in &lt;em&gt;Angular&lt;/em&gt;) or after submitting.&lt;/li&gt;
&lt;li&gt;focus on the &lt;strong&gt;first invalid control&lt;/strong&gt; (see code example below) upon submitting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;inline&lt;/strong&gt; with the form fields, not at the top or bottom of the form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;after&lt;/strong&gt; the form field, not before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;in (dark) red&lt;/strong&gt; and with an icon (e.g., ❌) to make them more visible.
&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FlightSearchComponent&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="nb"&gt;document&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="c1"&gt;// for the focus&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;flightSearchForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewChild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NgForm&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;flightSearchForm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;onSearch&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;if &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;flightSearchForm&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nx"&gt;invalid&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;markFormGroupTouched&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;flightSearchForm&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;focusFirstInvalidControl&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;flightSearchForm&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// do the search&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;markFormGroupTouched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&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;for &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;key&lt;/span&gt; &lt;span class="k"&gt;of&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;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&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;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&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;markFormGroupTouched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;markAsTouched&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="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;focusFirstInvalidControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormGroup&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;for &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;key&lt;/span&gt; &lt;span class="k"&gt;of&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;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controls&lt;/span&gt;&lt;span class="p"&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;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;control&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;invalid&lt;/span&gt;&lt;span class="p"&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;invalidControl&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="nb"&gt;document&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="s2"&gt;`[name="&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="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invalidControl&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="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;break&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;#flightSearchForm&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="na"&gt;ngForm&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&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;"fromAirport"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;From (*)&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

  @let hasFromErrors = flightSearchForm.controls['from'] &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; flightSearchForm.controls['from'].touched &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; flightSearchForm.controls['from'].errors;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fromAirport"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="na"&gt;[minlength]=&lt;/span&gt;&lt;span class="s"&gt;"minLength"&lt;/span&gt;
    &lt;span class="na"&gt;[maxlength]=&lt;/span&gt;&lt;span class="s"&gt;"maxLength"&lt;/span&gt;
    &lt;span class="na"&gt;[pattern]=&lt;/span&gt;&lt;span class="s"&gt;"pattern"&lt;/span&gt;
    &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"!!hasFromErrors"&lt;/span&gt;
    &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"hasFromErrors ? 'fromErrors' : null"&lt;/span&gt;
    &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  @if (hasFromErrors) {
  &lt;span class="nt"&gt;&amp;lt;app-flight-validation-errors&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fromErrors"&lt;/span&gt; &lt;span class="na"&gt;[errors]=&lt;/span&gt;&lt;span class="s"&gt;"flightSearchForm.controls['from'].errors"&lt;/span&gt; &lt;span class="na"&gt;fieldLabel=&lt;/span&gt;&lt;span class="s"&gt;"From"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  }
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;For those looking to deepen their &lt;em&gt;Angular&lt;/em&gt; expertise, we offer a range of workshops – both in English and German:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;♿ &lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📈 &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; (including accessibility-related topics)&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Building accessible forms in &lt;em&gt;Angular&lt;/em&gt; is not just a best practice – it’s a commitment to creating better experiences for everyone. With just a few thoughtful choices, you can make your forms inclusive, intuitive, and ready for the future. For more information on &lt;em&gt;Angular&lt;/em&gt; and &lt;em&gt;Accessibility&lt;/em&gt;, check out my &lt;strong&gt;&lt;a href="https://www.angulararchitects.io/blog/web-accessibility-in-angular/" rel="noopener noreferrer"&gt;A11y blog series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>ARIA roles and attributes in Angular</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Fri, 30 May 2025 13:34:52 +0000</pubDate>
      <link>https://dev.to/lxt/aria-roles-and-attributes-in-angular-3ghl</link>
      <guid>https://dev.to/lxt/aria-roles-and-attributes-in-angular-3ghl</guid>
      <description>&lt;p&gt;ARIA (short for Accessible Rich Internet Applications) roles and attributes are used to improve Accessibility of our &lt;em&gt;Angular apps&lt;/em&gt; – especially for users who rely on assistive technologies like screen readers, voice control, or alternative input devices. Using ARIA is essential for building inclusive, user-friendly web apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  About ARIA
&lt;/h2&gt;

&lt;p&gt;ARIA was developed by the World Wide Web Consortium’s &lt;strong&gt;&lt;a href="https://www.w3.org/WAI/" rel="noopener noreferrer"&gt;Web Accessibility Initiative (WAI)&lt;/a&gt;&lt;/strong&gt; to enhance the accessibility of dynamic web content. The WAI also is the organization behind the &lt;strong&gt;Web Content Accessibility Guidelines (WCAG)&lt;/strong&gt;, which provide a comprehensive framework for A11y – more &lt;a href="https://www.angulararchitects.io/blog/web-accessibility-in-angular/" rel="noopener noreferrer"&gt;about &lt;strong&gt;WCAG&lt;/strong&gt; in the intro of this A11y blog series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g52l0ms6vsndc0ty9rg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g52l0ms6vsndc0ty9rg.png" alt="WAI" width="284" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originating in the early 2000s, ARIA was created to bridge the gaps in native HTML, ensuring that modern, interactive and single-page applications are usable by people with disabilities  ♿&lt;/p&gt;

&lt;h2&gt;
  
  
  ARIA in Angular
&lt;/h2&gt;

&lt;p&gt;In &lt;em&gt;Angular&lt;/em&gt;, ARIA roles and attributes can be easily integrated into your components. You can use them directly in your HTML view templates, just like any other HTML attribute. &lt;em&gt;Angular&lt;/em&gt; also provides &lt;strong&gt;built-in support for ARIA&lt;/strong&gt; through directives and bindings, making it easier to manage ARIA properties dynamically.&lt;/p&gt;

&lt;p&gt;When using &lt;strong&gt;static values&lt;/strong&gt;, you can simply add them to your HTML elements or components:&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;!-- Static ARIA attributes require no extra --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;X&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, when you want to bind &lt;strong&gt;ARIA attributes dynamically&lt;/strong&gt;, you should use &lt;em&gt;Angular's&lt;/em&gt; property binding syntax ("[]" square brackets) including the "attr." prefix:&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;!-- Dynamic ARIA attribute property binding with "attr." prefix --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;[attr.aria-label]=&lt;/span&gt;&lt;span class="s"&gt;"myActionLabel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ARIA vs. Semantic HTML
&lt;/h2&gt;

&lt;p&gt;While ARIA is a powerful tool for enhancing accessibility, it should be used as a &lt;strong&gt;last resort&lt;/strong&gt;. Native &lt;strong&gt;semantic HTML&lt;/strong&gt; elements and attributes are preferred whenever possible, as they provide built-in accessibility features that ARIA does not have to replicate. Always prefer native elements like &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;header&amp;gt;&lt;/code&gt; over their ARIA counterparts. For example, instead of using &lt;code&gt;role="button"&lt;/code&gt; on a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, use a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element.&lt;/p&gt;

&lt;h2&gt;
  
  
  ARIA Roles
&lt;/h2&gt;

&lt;p&gt;Speaking about ARIA roles, they define what an HTML element is or how it should behave – when native HTML elements are not applicable. They tell assistive technologies how to interpret an element and its purpose within the page. Roles are particularly useful for custom components that don’t have native semantic meaning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most commonly used ARIA roles
&lt;/h3&gt;

&lt;p&gt;Here is a not exhaustive list of some ot the most commonly used ARIA roles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;role="button"&lt;/code&gt; (prefer &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;) indicates an interactive element that triggers an action.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="main"&lt;/code&gt; (prefer &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;) the main content of your app – typically &lt;code&gt;&amp;lt;router-outlet /&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="complementary"&lt;/code&gt; (prefer &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt;) denotes content that complements – like a sidebar.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="navigation"&lt;/code&gt; (prefer &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;) denotes a section of navigation links – like your route nav.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="alert"&lt;/code&gt; used for important messages that should be immediately announced.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="dialog"&lt;/code&gt; signifies a modal or popup window.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="listbox"&lt;/code&gt; used for a widget that allows the user to select from a list of options.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="tablist"&lt;/code&gt; composes a tabbed interface.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="tab"&lt;/code&gt; represents a selectable tab in a tabbed interface.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;role="tabpanel"&lt;/code&gt; the container for the content associated with a tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find a comprehensive list of all &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles" rel="noopener noreferrer"&gt;ARIA roles&lt;/a&gt; by MDN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;This could be a &lt;code&gt;&amp;lt;app-dialog&amp;gt;&lt;/code&gt; component:&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;app-dialog&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-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"dialogTitle"&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;cdkFocusTrap&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;"dialogTitle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ dialogTitle() }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ dialogContent() }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"onClose()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Close&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/app-dialog&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this simple example, the &lt;code&gt;role="dialog"&lt;/code&gt; indicates that this is a dialog window. The &lt;code&gt;aria-labelledby&lt;/code&gt; attribute associates the dialog with its title, and &lt;code&gt;aria-modal="true"&lt;/code&gt; indicates that the dialog is modal, meaning it prevents interaction with the rest of the page until closed. Note that we also use the &lt;code&gt;cdkFocusTrap&lt;/code&gt; directive to ensure that (keyboard navigation) focus is trapped within the dialog while it is open.&lt;/p&gt;

&lt;p&gt;Another example could be a &lt;strong&gt;tab interface&lt;/strong&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;app-tablist&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tablist"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  @for (tab of tabs(); track tab.id) {
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tab"&lt;/span&gt; &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"panel_{{ tab.id }}"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tab_{{ tab.id }}"&lt;/span&gt;
            &lt;span class="na"&gt;[attr.aria-selected]=&lt;/span&gt;&lt;span class="s"&gt;"activeTab() === tab.id ? 'true' : 'false'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      {{ tab.label }}
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  } @empty {
    No tabs available.
  }
&lt;span class="nt"&gt;&amp;lt;/app-tablist&amp;gt;&lt;/span&gt;
@for (tab of tabs(); track tab.id) {
  &lt;span class="nt"&gt;&amp;lt;app-tab&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tabpanel"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"tab_{{ tab.id }}"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"panel_{{ tab.id }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ tab.content }}
  &lt;span class="nt"&gt;&amp;lt;/app-tab&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we have a tabbed interface. The &lt;code&gt;role="tablist"&lt;/code&gt; indicates that this is a list of tabs. Each tab has the &lt;code&gt;role="tab"&lt;/code&gt; and is associated with its corresponding panel using &lt;code&gt;aria-controls&lt;/code&gt;. The &lt;code&gt;aria-selected&lt;/code&gt; attribute indicates which tab is currently selected. The panels have the &lt;code&gt;role="tabpanel"&lt;/code&gt; and are associated with their respective tabs using &lt;code&gt;aria-labelledby&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ARIA Attributes
&lt;/h2&gt;

&lt;p&gt;ARIA attributes provide additional details about an HTML element’s state, properties or relationships. They enhance the &lt;strong&gt;semantic meaning&lt;/strong&gt; of your &lt;em&gt;Angular&lt;/em&gt; components and other HTML elements, especially when default HTML does not fully describe an element’s behavior.&lt;/p&gt;

&lt;p&gt;We distinguish between states and properties. States are dynamic and can change over time, while properties are static and describe the element's characteristics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most commonly used ARIA attributes
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Widget attributes (states and properties)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-disabled&lt;/code&gt; (state/property): indicates whether an element is disabled or not – not necessary if native &lt;code&gt;disabled&lt;/code&gt; is used.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-required&lt;/code&gt; (property): indicates that user input is required before submitting a form – not necessary if native &lt;code&gt;required&lt;/code&gt; is used.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-expanded&lt;/code&gt; (state): communicates whether an element, such as a collapsible menu, is expanded or collapsed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-hidden&lt;/code&gt; (state): indicates whether an element should be exposed to assistive technologies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-invalid&lt;/code&gt; (state): indicates whether the value of an input field is valid or invalid.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Live region attributes (states)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-live&lt;/code&gt;: specifies how updates to content should be announced to the user (e.g., &lt;code&gt;assertive&lt;/code&gt; for error messages).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-busy&lt;/code&gt;: indicates whether an element is currently being updated (e.g., loading spinner).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Drag-and-Drop attributes (states)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-grabbed&lt;/code&gt;: indicates whether an element is currently being dragged (e.g., &lt;code&gt;true&lt;/code&gt; when dragging).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Relationship attributes (properties)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aria-label&lt;/code&gt;: provides a text alternative for elements that may not have visible text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-labelledby&lt;/code&gt;: identifies an element (or elements) that labels the current element.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-describedby&lt;/code&gt;: identifies an element (or elements) that describes the current element.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aria-controls&lt;/code&gt; (state): references the element(s) whose content is controlled by the current element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here is again the full list of all &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes" rel="noopener noreferrer"&gt;ARIA attributes&lt;/a&gt; by MDN.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;p&gt;A toggle button using &lt;code&gt;aria-label&lt;/code&gt; and &lt;code&gt;aria-expanded&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;button&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Toggle navigation menu"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-expanded]=&lt;/span&gt;&lt;span class="s"&gt;"isMenuOpen() ? 'true' : 'false'"&lt;/span&gt;
  &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"onToggleNav()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"icon-menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this simple example, the &lt;code&gt;aria-label&lt;/code&gt; attribute provides a text alternative for the button, indicating its purpose. The &lt;code&gt;aria-expanded&lt;/code&gt; attribute indicates whether the navigation menu is currently open or closed. This is important for screen reader users, as it helps them understand the state of the button.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;aria-live&lt;/code&gt; combined with &lt;code&gt;role="alert"&lt;/code&gt; for an error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@let showFromErrors =
  flightSearchForm.controls.from.errors &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  flightSearchForm.controls.from.touched;

&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
  &lt;span class="err"&gt;[...]&lt;/span&gt;
  &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fromAirport"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-invalid]=&lt;/span&gt;&lt;span class="s"&gt;"!!showFromErrors"&lt;/span&gt;
  &lt;span class="na"&gt;[attr.aria-describedby]=&lt;/span&gt;&lt;span class="s"&gt;"showFromErrors ? 'fromAirportErrors' : null"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
@if (showFromErrors) {
  &lt;span class="nt"&gt;&amp;lt;app-flight-validation-errors&lt;/span&gt;
    &lt;span class="na"&gt;aria-live=&lt;/span&gt;&lt;span class="s"&gt;"assertive"&lt;/span&gt;
    &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"alert"&lt;/span&gt;
    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fromAirportErrors"&lt;/span&gt;
    &lt;span class="na"&gt;fieldLabel=&lt;/span&gt;&lt;span class="s"&gt;"From"&lt;/span&gt;
    &lt;span class="na"&gt;[errors]=&lt;/span&gt;&lt;span class="s"&gt;"flightSearchForm.controls.from.errors"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;aria-live&lt;/code&gt; attribute is set to &lt;code&gt;assertive&lt;/code&gt;, which means that the screen reader will announce the error message immediately when it appears. The &lt;code&gt;role="alert"&lt;/code&gt; indicates that this is an important message. Additionally, the &lt;code&gt;aria-invalid&lt;/code&gt; attribute is used to indicate that the input field has validation errors and the &lt;code&gt;aria-describedby&lt;/code&gt; attribute is used to associate the error message with the input field. Note that the &lt;code&gt;aria-describedby&lt;/code&gt; attribute is set to &lt;code&gt;null&lt;/code&gt; when there are no errors. This ensures that it's not present in the rendered DOM when not needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;For those looking to deepen their &lt;em&gt;Angular&lt;/em&gt; expertise, we offer a range of workshops – both in English and German:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;♿ &lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📈 &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; (including accessibility-related topics)&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Implementing &lt;strong&gt;ARIA&lt;/strong&gt; in &lt;em&gt;Angular&lt;/em&gt; is essential for building web apps that are both accessible and inclusive. By leveraging &lt;strong&gt;ARIA roles and attributes&lt;/strong&gt; appropriately, developers can bridge gaps where native HTML falls short, ensuring that all users have a positive experience. This approach not only meets accessibility standards but also lays the foundation for user-centric and future-proof design.&lt;/p&gt;

&lt;p&gt;In the next edition of our &lt;a href="https://www.angulararchitects.io/blog/web-accessibility-in-angular/" rel="noopener noreferrer"&gt;&lt;strong&gt;A11y blog series&lt;/strong&gt;&lt;/a&gt;, we'll cover &lt;a href="https://www.angulararchitects.io/blog/accessible-angular-forms/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessible Angular Forms&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.dev/accessibility-in-angular-e84f73a223f" rel="noopener noreferrer"&gt;Accessibility in Angular Applications&lt;/a&gt; by Zama Khan Mohammed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amir-saeed.medium.com/accessibility-in-angular-17-bae5de7c2803" rel="noopener noreferrer"&gt;Accessibility in Angular 17&lt;/a&gt; by Amir Saeed&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular.dev/api/router/RouterLinkActive#ariaCurrentWhenActive" rel="noopener noreferrer"&gt;ariaCurrentWhenActive – official docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles" rel="noopener noreferrer"&gt;ARIA roles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes" rel="noopener noreferrer"&gt;ARIA attributes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Accessible Angular Routes</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Tue, 27 May 2025 15:29:04 +0000</pubDate>
      <link>https://dev.to/lxt/accessible-angular-routes-53b</link>
      <guid>https://dev.to/lxt/accessible-angular-routes-53b</guid>
      <description>&lt;p&gt;This article explains how to use &lt;em&gt;Angular Router&lt;/em&gt; features to achieve quick wins in improving &lt;strong&gt;Accessibility (A11y)&lt;/strong&gt;. It is the third edition of our &lt;strong&gt;A11y blog series&lt;/strong&gt;. If you want to enhance your &lt;em&gt;Angular A11y&lt;/em&gt; skills, please make sure to read the other articles in this series as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page Titles
&lt;/h2&gt;

&lt;p&gt;Does your &lt;em&gt;Angular App&lt;/em&gt; look like this if you open it in multiple tabs?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frj2d8ew5nyz5jqdlgz2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frj2d8ew5nyz5jqdlgz2b.png" alt="Angular App without Page Titles" width="800" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it does, you should definitely consider adding unique page titles for each route. The very easy-to-use &lt;code&gt;Route.title&lt;/code&gt; feature shipped with &lt;a href="https://x.com/twerske/status/1488313309644214272" rel="noopener noreferrer"&gt;Angular v14&lt;/a&gt; in 2022, yet many &lt;em&gt;Angular Developers&lt;/em&gt; are still not using it 😱. Please take a look and adopt it in your &lt;em&gt;Angular Apps&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route.title
&lt;/h3&gt;

&lt;p&gt;Use this built-in &lt;code&gt;Router&lt;/code&gt; feature to automatically update the page &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; after each successful navigation, enhancing both accessibility and UX for all of us. To enable it in your primary &lt;code&gt;&amp;lt;router-outlet /&amp;gt;&lt;/code&gt;, you only need to set the &lt;code&gt;title&lt;/code&gt; property in your routes configuration array:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Look how easy it is to use&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DemoComponent&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;This will update the page title in the browser tab and make it accessible to screen readers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs09rszrqu5p89uzk0pqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs09rszrqu5p89uzk0pqe.png" alt="Page Title Demo" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In larger &lt;em&gt;Angular Apps&lt;/em&gt;, setting page titles can become inconsistent due to the lack of a common prefix or suffix for routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Page Title Strategy
&lt;/h3&gt;

&lt;p&gt;To address this, you can extend &lt;em&gt;Angular's&lt;/em&gt; abstract &lt;code&gt;TitleStrategy&lt;/code&gt; class and implement your custom &lt;strong&gt;page title strategy&lt;/strong&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="c1"&gt;// [imports]&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PageTitleStrategy&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TitleStrategy&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;title&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;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;updateTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouterStateSnapshot&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageTitle&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="nf"&gt;buildTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageTitle&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageTitle&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; – Demo&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;else&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Link to Demo below!&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;Then add the custom title strategy to your &lt;code&gt;app.config.ts&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="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;PageTitleStrategy&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="s2"&gt;./page-tite-strategy&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;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withIncrementalHydration&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nf"&gt;provideExperimentalZonelessChangeDetection&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TitleStrategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageTitleStrategy&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// add this line&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;h3&gt;
  
  
  Dynamic Page Titles using Angular Router Params
&lt;/h3&gt;

&lt;p&gt;To create dynamic page titles using &lt;em&gt;Angular Router&lt;/em&gt; parameters, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we set up the &lt;code&gt;withComponentInputBinding()&lt;/code&gt; function to bind the route parameters to your component inputs:
&lt;/li&gt;
&lt;/ol&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withIncrementalHydration&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="nf"&gt;provideExperimentalZonelessChangeDetection&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;withComponentInputBinding&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="c1"&gt;// add feature to router&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TitleStrategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageTitleStrategy&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;ol&gt;
&lt;li&gt;Then, we add the parameter to the route definition:
&lt;/li&gt;
&lt;/ol&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// [...]&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;demo/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Demo #id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// note that this will be ignored and replaced by the dynamic title&lt;/span&gt;
    &lt;span class="na"&gt;loadComponent&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./demo/demo.component&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now, we can simply add a &lt;code&gt;input&lt;/code&gt; signal to the component that will automatically receive the route param (thanks to the &lt;code&gt;withComponentInputBinding()&lt;/code&gt; feature):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// [imports &amp;amp; decorator]&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;DemoComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// [...]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;DemoComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Finally, we can use an &lt;code&gt;effect&lt;/code&gt; on our &lt;code&gt;id&lt;/code&gt; input signal to display the dynamic page title:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// [imports &amp;amp; decorator]&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;DemoComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;title&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;Title&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="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&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;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Page #&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;id&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; - Demo`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page - Demo&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="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;DemoComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when you navigate to &lt;code&gt;/demo/1&lt;/code&gt;, the page title will be set to &lt;code&gt;Page #1 - Demo&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  All Together Now
&lt;/h3&gt;

&lt;p&gt;To bring it all together – with the global page title strategy – we add another method to the &lt;code&gt;PageTitleStrategy&lt;/code&gt; class and provide it in &lt;code&gt;root&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="c1"&gt;// page-title-strategy.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;RouterStateSnapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TitleStrategy&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/router&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;inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Injectable&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;Title&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="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="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;PageTitleStrategy&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TitleStrategy&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;title&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;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;updateTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouterStateSnapshot&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;setTitle&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;buildTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerState&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageTitle&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageTitle&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&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;pageTitle&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; – Demo`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page title like a pro&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;And here is the final version of the dynamic page title &lt;code&gt;effect&lt;/code&gt; in the &lt;code&gt;DemoComponent&lt;/code&gt; using our injected &lt;code&gt;PageTitleStrategy&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="c1"&gt;// demo.component.ts&lt;/span&gt;
&lt;span class="c1"&gt;// [imports &amp;amp; decorator]&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;DemoComponent&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;pageTitleStrategy&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;PageTitleStrategy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="nf"&gt;effect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;pageTitleStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&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;id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Page #&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;id&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page&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="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;DemoComponent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can set the page title depending on the route params and have a consistent suffix for all routes in your &lt;em&gt;Angular App&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm1wf9obmlj617i79xwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkm1wf9obmlj617i79xwh.png" alt="Dynamic Page Title Demo" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty nice, huh? Check out the full source code in the &lt;code&gt;title-strategy&lt;/code&gt; branch of &lt;a href="https://github.com/L-X-T/ssr-ih-ng19-days/tree/title-strategy" rel="noopener noreferrer"&gt;my GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  RouterLinkActive
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;RouterLinkActive&lt;/code&gt; directive is another powerful and lightweight tool for indicating the active state of navigation links in your &lt;em&gt;Angular App&lt;/em&gt;. It allows you to apply CSS styles to navigation links based on their active state, making it easier to create appealing and accessible menus.&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;!-- nav.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"demo"&lt;/span&gt; &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Demo&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="nc"&gt;.component.scss&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="nc"&gt;.active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&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;--nav-link--active__color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;h3&gt;
  
  
  Fine-tuned control with &lt;code&gt;routerLinkActiveOptions&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Since &lt;em&gt;Angular v14&lt;/em&gt;, you can use the &lt;code&gt;routerLinkActiveOptions&lt;/code&gt; directive to fine-tune the active state of your links. Where &lt;code&gt;options&lt;/code&gt; will have either the shape of &lt;a href="https://angular.dev/api/router/IsActiveMatchOptions" rel="noopener noreferrer"&gt;&lt;strong&gt;IsActiveMatchOptions&lt;/strong&gt;&lt;/a&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;export&lt;/span&gt; &lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IsActiveMatchOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignored&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;matrixParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignored&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignored&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;Or, just a boolean named &lt;code&gt;exact&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To apply exact matching, add the &lt;code&gt;routerLinkActiveOptions&lt;/code&gt; directive to your link:&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;!-- nav.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
    &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/demo"&lt;/span&gt;
  &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;
  &lt;span class="na"&gt;[routerLinkActiveOptions]=&lt;/span&gt;&lt;span class="s"&gt;"{ exact: true }"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Demo
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Indicate current page &lt;code&gt;ariaCurrentWhenActive&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A final quick win in &lt;em&gt;A11y in Angular&lt;/em&gt; is to highlight the &lt;strong&gt;current page link&lt;/strong&gt; in the nav for screen readers via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current" rel="noopener noreferrer"&gt;aria-current&lt;/a&gt;: We need to add the &lt;code&gt;aria-current="page"&lt;/code&gt; attribute. This can easily be done using the &lt;a href="https://angular.dev/best-practices/a11y#active-links-identification" rel="noopener noreferrer"&gt;&lt;strong&gt;ariaCurrentWhenActive&lt;/strong&gt;&lt;/a&gt; input on the &lt;code&gt;routerLinkActive&lt;/code&gt; directive by setting its value to "page":&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;!-- nav.component.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
    &lt;span class="na"&gt;routerLink=&lt;/span&gt;&lt;span class="s"&gt;"/demo"&lt;/span&gt;
    &lt;span class="na"&gt;routerLinkActive=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;
    &lt;span class="na"&gt;[routerLinkActiveOptions]=&lt;/span&gt;&lt;span class="s"&gt;"{ exact: true }"&lt;/span&gt;
    &lt;span class="na"&gt;ariaCurrentWhenActive=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Demo
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;For those looking to deepen their &lt;em&gt;Angular&lt;/em&gt; expertise, we offer a range of workshops – both in English and German:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;♿ &lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📈 &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; (including accessibility related topics)&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Implementing &lt;em&gt;Angular's&lt;/em&gt; built-in router features is a simple yet powerful way to boost your A11y 🚀&lt;/p&gt;

&lt;p&gt;By leveraging dynamic page titles, a global title strategy, and fine-tuning active link indicators with &lt;code&gt;RouterLinkActive&lt;/code&gt; and &lt;code&gt;ariaCurrentWhenActive&lt;/code&gt;, you can create a more inclusive UX that benefits everyone – from seasoned Devs (like me 😂) to users who rely on assistive technologies. These strategies not only improve UX but also help standardize navigation and page management across all &lt;em&gt;Angular Apps&lt;/em&gt;. Back to our example from the beginning, now our browser tab bar looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg78kp6m39italf90q721.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg78kp6m39italf90q721.png" alt="Angular App with Page Titles" width="800" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think this is a great example of a quick win! Keep playing with these features and explore the rest of our A11y blog series for more insights on building accessible, high-performing web apps.&lt;/p&gt;

&lt;p&gt;In the next edition of our &lt;strong&gt;A11y blog series&lt;/strong&gt;, we'll cover more &lt;strong&gt;&lt;a href="https://www.angulararchitects.io/blog/aria-roles-attributes/" rel="noopener noreferrer"&gt;ARIA roles and attributes&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/L-X-T/ssr-ih-ng19-days/tree/title-strategy" rel="noopener noreferrer"&gt;GitHub repo of demo&lt;/a&gt;, by Alexander Thalhammer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.dev/angular-v14-is-now-available-391a6db736af" rel="noopener noreferrer"&gt;Angular v14 – official blog post&lt;/a&gt; by Emma Twersky&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://x.com/twerske/status/1488277224008478721" rel="noopener noreferrer"&gt;Tweet about Route title&lt;/a&gt; by Emma Twersky&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/brandontroberts/setting-page-titles-natively-with-the-angular-router-393j"&gt;Page Titles With The Router&lt;/a&gt; by Brandon Roberts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://briantree.se/angular-tutorial-router-link-and-accessibility/" rel="noopener noreferrer"&gt;Router Link Accessibility Features&lt;/a&gt; by Brian Treese&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/thisdotmedia/make-it-accessible-navigation-in-angular-2gee"&gt;Make it accessible: Navigation in Angular&lt;/a&gt; by Daniel Marin&lt;/li&gt;
&lt;li&gt;&lt;a href="https://angular.dev/api/router/RouterLinkActive#ariaCurrentWhenActive" rel="noopener noreferrer"&gt;ariaCurrentWhenActive – official docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Accessibility Testing Tools for Angular</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Mon, 21 Apr 2025 15:50:52 +0000</pubDate>
      <link>https://dev.to/lxt/accessibility-testing-tools-for-angular-249g</link>
      <guid>https://dev.to/lxt/accessibility-testing-tools-for-angular-249g</guid>
      <description>&lt;p&gt;Let me introduce you to my curated selection of &lt;strong&gt;Accessibility Testing Tools&lt;/strong&gt; for &lt;em&gt;Angular Apps&lt;/em&gt;. This article is the second edition of our &lt;strong&gt;A11y blog series&lt;/strong&gt;. If you want to learn more about &lt;em&gt;Accessibility in Angular&lt;/em&gt;, please check out the other articles in this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Automated testing can never replace manual testing (at least so far, in 2025). There are tools that can identify some or many A11y issues, but no tool can certify that a web app is fully accessible. Manual testing ensures that you test for a breadth of A11y concepts that include logical content order and feature parity.&lt;/p&gt;

&lt;p&gt;However, automated testing is fast and - at least to me - some kind of entertaining work 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Chromium Browser
&lt;/h2&gt;

&lt;p&gt;First, please stop using the lame &lt;em&gt;Firefox&lt;/em&gt; (slow open-source browser &lt;a href="https://de.wikipedia.org/wiki/Mozilla_Firefox#Finanzierung_und_Werbung" rel="noopener noreferrer"&gt;sponsored mainly by Google&lt;/a&gt;). I strongly recommend using a &lt;em&gt;&lt;strong&gt;Chromium&lt;/strong&gt;&lt;/em&gt; based browser! Safari is okay for people who love their golden Apple cage. Here are some browsers that I can recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;&lt;a href="https://vivaldi.com/" rel="noopener noreferrer"&gt;Vivaldi&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt; (Privacy &amp;amp; Linux)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;&lt;a href="https://brave.com/" rel="noopener noreferrer"&gt;Brave&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt; (Privacy &amp;amp; Ad blocker)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;&lt;a href="https://arc.net/" rel="noopener noreferrer"&gt;Arc&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt; (for something completely different) and even&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;&lt;strong&gt;Edge&lt;/strong&gt;&lt;/em&gt; (for Windows machines without Admin privileges)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personally, I prefer using a clean &lt;em&gt;Google Chrome&lt;/em&gt; on my &lt;em&gt;Macs&lt;/em&gt; and my &lt;em&gt;Pixel&lt;/em&gt;. Just like I prefer a clean &lt;em&gt;Google Android&lt;/em&gt; and have been happily sticking to &lt;em&gt;Nexus&lt;/em&gt; (&lt;a href="https://en.wikipedia.org/wiki/Nexus_4" rel="noopener noreferrer"&gt;4&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Nexus_5" rel="noopener noreferrer"&gt;5, 5&lt;/a&gt;) and later &lt;em&gt;Pixel&lt;/em&gt; (&lt;a href="https://en.wikipedia.org/wiki/Pixel_3a" rel="noopener noreferrer"&gt;3a&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Pixel_6a" rel="noopener noreferrer"&gt;6a&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Pixel_9" rel="noopener noreferrer"&gt;9 Pro soon 😎&lt;/a&gt; - &lt;a href="https://www.derstandard.at/story/3000000233178/pixel-9-pro-im-test-das-ist-die-zukunft-der-smartphones" rel="noopener noreferrer"&gt;review in German&lt;/a&gt;) for over a decade now. However, if you don't want to share your data with Google - those &lt;em&gt;Chromium&lt;/em&gt; browsers listed above (except &lt;em&gt;Edge&lt;/em&gt;) are the better choice for sure.&lt;/p&gt;

&lt;p&gt;While the Spaces feature of &lt;em&gt;Arc&lt;/em&gt; is pretty interesting, I just use different &lt;em&gt;Chrome&lt;/em&gt; users for my different jobs. E.g., one of them for everything related to &lt;em&gt;Angular Architects&lt;/em&gt; - like reading this post 🤓&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvqco3m73ut0oju59bg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvqco3m73ut0oju59bg2.png" alt="Chrome built-in A11y Tree" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did you notice that &lt;em&gt;Chromium&lt;/em&gt; DevTools have a built-in &lt;strong&gt;Accessibility Tree view&lt;/strong&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  Chromium Extensions
&lt;/h3&gt;

&lt;p&gt;Before we talk about a selection of my favorite &lt;strong&gt;Accessibility Tools&lt;/strong&gt;, please &lt;strong&gt;add&lt;/strong&gt; the following &lt;strong&gt;extensions&lt;/strong&gt; to your browser (at least temporarily):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/angular-devtools/ienfalfjdbdpebioblfackkekamfmbnh" rel="noopener noreferrer"&gt;Angular&lt;/a&gt; Dev Tools (should be there already since you're here 😏)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh" rel="noopener noreferrer"&gt;WAVE&lt;/a&gt; A11y Testing Tool, 500k users&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd" rel="noopener noreferrer"&gt;axe DevTools&lt;/a&gt; A11y Testing Tool, 300k users&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/accessibility-insights-fo/pbjjkligggfmakdaogkfomddhfmpjeni" rel="noopener noreferrer"&gt;Accessibility Insights&lt;/a&gt; A11y Testing Tool, 100k users&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/read-aloud-a-text-to-spee/hdhinadidafjejdhmfkjgnolgimiaplp" rel="noopener noreferrer"&gt;Read Aloud&lt;/a&gt; Screen Reader (OS-based Screen Readers listed at the end of the post are better, though)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.googe.com/detail/headingsmap/flbjommegcjonpdmenkdiocclhjacmbi" rel="noopener noreferrer"&gt;HeadingsMap&lt;/a&gt; (partially related to A11y, for heading structure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkx60acv77mxch7lwlm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkx60acv77mxch7lwlm7.png" alt="My AA user's chromium extensions" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may need to restart your browser for the just-added extensions to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt; that three of them are used exclusively for A11y testing. Once you've found your favorite A11y weapon of choice, you might want to remove the other two. My personal preference will be mentioned below!&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Identify issues and how to know what to fix
&lt;/h3&gt;

&lt;p&gt;The A11y Testing Tools allow you to quickly and easily check things like the presence of alt text on an image or the contrast ratio of a text color. You can think of these tools as linters; they can recognize that alt text is present, but you must manually check that the content is logical and provides value.&lt;/p&gt;

&lt;p&gt;Some tools might find a lot of errors or warnings on your app or web property. Please don't feel overwhelmed in such a case and start improving your A11y step-by-step. Here is a customizable quick reference to &lt;strong&gt;Web Content Accessibility Guidelines (WCAG) 2.2&lt;/strong&gt; requirements (success criteria) and techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/quickref/" rel="noopener noreferrer"&gt;https://www.w3.org/WAI/WCAG22/quickref/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu9a10pi5xycb430x3oz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu9a10pi5xycb430x3oz.png" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll need to find a prioritization for your issues. The WCAG's levels (A, AA and AAA) will help you get started.&lt;/p&gt;

&lt;p&gt;Now, finally, the &lt;strong&gt;selection&lt;/strong&gt; of my favorite Accessibility Testing Tools 🥳&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular ESLint (only for Angular apps)
&lt;/h3&gt;

&lt;p&gt;You can use the Angular ESLint package to lint your code for automatable A11y attributes. In eslint.json, make sure to add the a11y ruleset plugin &lt;code&gt;@angular-eslint/template/accessibility&lt;/code&gt; to your HTML templates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;overrides&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="cm"&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;files&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="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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;extends&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plugin:@angular-eslint/template/recommended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="cm"&gt;/* add the following line */&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plugin:@angular-eslint/template/accessibility&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;rules&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="cm"&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 add Angular ESLint to your own Angular app, simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @angular-eslint/schematics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: With the newest version, the plugin &lt;code&gt;@angular-eslint/template/accessibility&lt;/code&gt; is &lt;strong&gt;included by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For more information, see the latest &lt;a href="https://github.com/angular-eslint/angular-eslint/tree/main/packages/eslint-plugin-template" rel="noopener noreferrer"&gt;Angular ESLint&lt;/a&gt; rules on GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  WAVE Chrome Extension
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;If you haven't done so already, install the &lt;a href="https://chromewebstore.google.com/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh" rel="noopener noreferrer"&gt;WAVE&lt;/a&gt; Chrome extension.&lt;/li&gt;
&lt;li&gt;Click on the Wave icon to run the WAVE Ally test on the current page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl60iu4hmflfcf52k5l0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl60iu4hmflfcf52k5l0.png" alt="WAVE extension result" width="378" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  WAVE Web Service
&lt;/h4&gt;

&lt;p&gt;If you want to check a public web property, you can also try the &lt;a href="https://wave.webaim.org/" rel="noopener noreferrer"&gt;WAVE&lt;/a&gt; Web Accessibility Evaluation Tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Axe Chrome Dev Tools
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;If you haven't done so already, install the &lt;a href="https://chromewebstore.google.com/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd" rel="noopener noreferrer"&gt;axe DevTools&lt;/a&gt; extension.&lt;/li&gt;
&lt;li&gt;Open your DevTools and select the axe DevTools tab.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Scan all of my page&lt;/code&gt; to run an axe Ally scan on the current page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1s10ff418nle1avnuhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1s10ff418nle1avnuhi.png" alt="Axe Dev Tools result" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lighthouse Dev Tools
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your DevTools and select the Lighthouse tab.&lt;/li&gt;
&lt;li&gt;The options &lt;code&gt;Navigation&lt;/code&gt; and &lt;code&gt;Mobile&lt;/code&gt; (or &lt;code&gt;Desktop&lt;/code&gt;) are okay.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;Accessibility&lt;/code&gt; checkbox and deselect all other checkboxes.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Generate report&lt;/code&gt; to run a Lighthouse A11y audit.&lt;/li&gt;
&lt;li&gt;Check the results of your audit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6542s5jbutf1icslv2kp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6542s5jbutf1icslv2kp.png" alt="Lighthouse Dev Tools result" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  PageSpeed Insights Web Service
&lt;/h4&gt;

&lt;p&gt;Note: For public properties, you can also use &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;Google's PageSpeed Insights&lt;/a&gt; to run the lighthouse A11y test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility Insights Dev Tools
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;If you haven't done so already, install the &lt;a href="https://chromewebstore.google.com/detail/accessibility-insights-fo/pbjjkligggfmakdaogkfomddhfmpjeni" rel="noopener noreferrer"&gt;Accessibility Insights&lt;/a&gt; extension.&lt;/li&gt;
&lt;li&gt;Open it by clicking on the extension icon and selecting Accessibility Insights.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;FastPass&lt;/code&gt; to run a quick first test of your page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Run A11y Tests
&lt;/h3&gt;

&lt;p&gt;If you haven't done so already, run some automated tests on your machine now 💻&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual (Auditory) Testing
&lt;/h2&gt;

&lt;p&gt;Besides manual testing, you want to do &lt;strong&gt;auditory accessibility tests&lt;/strong&gt;. You want to turn on your machine's built-in screen reader and navigate through your (Angular) app with keyboard navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen Reader
&lt;/h3&gt;

&lt;p&gt;You can use any one of these screen readers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nvda.bhvd.de" rel="noopener noreferrer"&gt;NVDA&lt;/a&gt; (Windows, free &amp;amp; recommended for A11y testers)&lt;/li&gt;
&lt;li&gt;Jaws (Windows, expansive &amp;amp; favorite choice of people with handicapped sight)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.microsoft.com/en-us/windows/complete-guide-to-narrator-e4397a0d-ef4f-b386-d8ae-c172f109bdb1" rel="noopener noreferrer"&gt;Narrator&lt;/a&gt; (Windows, built-in)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/VoiceOver" rel="noopener noreferrer"&gt;Voice Over&lt;/a&gt; (Mac/iOS, built-in but great)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.google.com/accessibility/android/answer/6283677?hl=en&amp;amp;sjid=14818511639127973246-EU" rel="noopener noreferrer"&gt;Talkback&lt;/a&gt; (Android, built-in but great)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.a11yproject.com/posts/getting-started-with-orca/" rel="noopener noreferrer"&gt;Orca&lt;/a&gt; (Linux, free)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chromewebstore.google.com/detail/read-aloud-a-text-to-spee/hdhinadidafjejdhmfkjgnolgimiaplp" rel="noopener noreferrer"&gt;Read Aloud&lt;/a&gt; Chrome Extension (not so great, IMHO)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;If you want to learn Angular, we offer a variety of workshops – both in English and German.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt; ♿&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; 📈 (including accessibility related topics)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt; 🚀&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Tools are always a matter of personal preference and taste. So, I'd like to invite you to try out some of the recommended tools and choose your own favorite one. Nevertheless, my favorite of all is the &lt;strong&gt;&lt;em&gt;WAVE&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://wave.webaim.org/extension/" rel="noopener noreferrer"&gt;extension&lt;/a&gt; and &lt;a href="https://wave.webaim.org/" rel="noopener noreferrer"&gt;web service&lt;/a&gt;. Happy testing!&lt;/p&gt;

&lt;p&gt;In the next edition of our &lt;strong&gt;A11y blog series&lt;/strong&gt;, we'll show you how to use the &lt;a href="https://www.angulararchitects.io/blog/accessible-angular-routes/" rel="noopener noreferrer"&gt;&lt;em&gt;Angular Router&lt;/em&gt;&lt;/a&gt; for better accessibility.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Web Accessibility (A11y) in Angular – Introduction</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Mon, 07 Apr 2025 19:42:24 +0000</pubDate>
      <link>https://dev.to/lxt/web-accessibility-a11y-in-angular-introduction-5fa8</link>
      <guid>https://dev.to/lxt/web-accessibility-a11y-in-angular-introduction-5fa8</guid>
      <description>&lt;p&gt;This article will get you started into &lt;strong&gt;Accessibility in Angular&lt;/strong&gt;, right on time for the &lt;em&gt;European Accessibility Act (EAA)&lt;/em&gt; which will become effective on &lt;em&gt;June 28th 2025&lt;/em&gt;, but more on that later. We'll cover the history of Web Accessibility, the &lt;em&gt;Web Content Accessibility Guidelines (WCAG)&lt;/em&gt;, and of course the &lt;em&gt;EAA&lt;/em&gt; itself. So make sure to follow it to be prepared for the EAA and to learn how to make your &lt;em&gt;Angular Apps&lt;/em&gt; more accessible.&lt;/p&gt;

&lt;p&gt;The article is intended for developers, product owners, and anyone interested in making their &lt;em&gt;Angular Apps&lt;/em&gt; more accessible. It's also the first edition kicking of our &lt;strong&gt;A11y blog series&lt;/strong&gt;, which will cover topics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Accessibility Introduction&lt;/strong&gt; (this post): An overview of the history, statistics and regulatory framework of Web Accessibility.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/blog/accessibility-testing-tools/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Testing Tools&lt;/strong&gt;&lt;/a&gt;: My top picks for testing your Angular app for accessibility issues.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/blog/accessible-angular-routes/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessible Angular Routes&lt;/strong&gt;&lt;/a&gt;: Quick wins to enhance your app's accessibility using the &lt;em&gt;Angular Router&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/blog/aria-roles-attributes/" rel="noopener noreferrer"&gt;&lt;strong&gt;ARIA roles and attributes&lt;/strong&gt;&lt;/a&gt;: Best practices for implementing WAI-ARIA roles and attributes in &lt;em&gt;Angular&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/blog/accessible-angular-forms/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessible Angular Forms&lt;/strong&gt;&lt;/a&gt;: Guidelines for building &lt;em&gt;Angular&lt;/em&gt; forms that everyone can use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhancing A11y with CDK&lt;/strong&gt;: Exploring some elements of the &lt;em&gt;Angular CDK&lt;/em&gt; a11y package.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Brief History of Web Accessibility
&lt;/h2&gt;

&lt;p&gt;Tim Berners-Lee, the inventor of the World Wide Web, envisioned it as a platform for &lt;strong&gt;universal access&lt;/strong&gt; regardless of &lt;strong&gt;physical or cognitive abilities&lt;/strong&gt;.&lt;br&gt;
The term &lt;strong&gt;accessibility&lt;/strong&gt; (short A11y, like I18n) refers to the design of products, devices, services, or environments for people with disabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disability Statistics
&lt;/h3&gt;

&lt;p&gt;While estimates suggest that one in four individuals in the EU has some form of disability, finding exact data is challenging. Therefore, we focus on &lt;strong&gt;Germany&lt;/strong&gt; using the &lt;a href="https://www.gbe-bund.de/gbe/isgbe.information?p_uid=gast&amp;amp;p_aid=97012225&amp;amp;p_sprache=E&amp;amp;p_thema_id=3637&amp;amp;p_thema_id2=3600&amp;amp;p_thema_id3=4800&amp;amp;p_thema_id4=4800" rel="noopener noreferrer"&gt;numbers of the &lt;em&gt;Statistische Bundesamt&lt;/em&gt;&lt;/a&gt; (Federal Statistical Office of Germany) from 2023:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7.86m people (9.4% of the pop.) in Germany have a &lt;strong&gt;severe disability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;1.85m people (2.2% of the pop.) have problems with &lt;strong&gt;cerebral disorders&lt;/strong&gt;, mental and psychological disabilities&lt;/li&gt;
&lt;li&gt;1.63m people (2.0% of the pop.) have problems with &lt;strong&gt;mobility impairments&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;329k people (0.4% of the pop.) are considered &lt;strong&gt;blind or have severe vision impairment&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;324k people (0.4% of the pop.) are considered &lt;strong&gt;deaf or have severe hearing impairment&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although this data is specific to Germany, they offer insight into the broader prevalence of disabilities across Europe.&lt;br&gt;
However, I think it's important to note that good A11y practices &lt;strong&gt;benefit&lt;/strong&gt; not only people with disabilities but &lt;strong&gt;everybody&lt;/strong&gt;.&lt;br&gt;
For example, captions on videos help non-native speakers and those in noisy environments.&lt;br&gt;
Here is an image by Microsoft showing the benefits of A11y for all kinds of &lt;strong&gt;circumstances&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yjxex0e0806fdtp41u8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yjxex0e0806fdtp41u8.webp" alt="A11y for everyone" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  W3C and the Web Accessibility Initiative (WAI)
&lt;/h3&gt;

&lt;p&gt;In 1997, the World Wide Web Consortium (W3C) launched the &lt;strong&gt;Web Accessibility Initiative&lt;/strong&gt; (WAI) to systematically address accessibility issues.&lt;br&gt;
The WAI develops guidelines, called &lt;strong&gt;Web Content Accessibility Guidelines&lt;/strong&gt; (WCAG) – version 1.0 released in 1999 – and continues to provide resources to make the web more accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Content Accessibility Guidelines (WCAG)
&lt;/h2&gt;

&lt;p&gt;The WCAG guidelines offer an internationally recognized framework for creating accessible web content and applications. The latest version, WCAG 2.2, was published in December 2023. Both WCAG 2.1 and 2.2 are widely adopted in regulatory standards across Europe and North America.&lt;/p&gt;

&lt;p&gt;For a deep dive, check out the &lt;a href="https://www.w3.org/WAI/WCAG22/quickref/" rel="noopener noreferrer"&gt;WCAG 2.2 Quick Reference&lt;/a&gt;. Here are some key points:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Four Principles
&lt;/h3&gt;

&lt;p&gt;The WCAG 2.2 guidelines are built and structured on four core principles that form the foundation for creating accessible digital content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👀 &lt;strong&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/quickref/#principle1" rel="noopener noreferrer"&gt;Perceivable&lt;/a&gt;&lt;/strong&gt;: Information must be presented in ways that users can perceive.&lt;/li&gt;
&lt;li&gt;🕹️ &lt;strong&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/quickref/#principle2" rel="noopener noreferrer"&gt;Operable&lt;/a&gt;&lt;/strong&gt;: Interface components must be operable across various devices and inputs.&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/quickref/#principle3" rel="noopener noreferrer"&gt;Understandable&lt;/a&gt;&lt;/strong&gt;: Content and navigation should be clear and easy to comprehend.&lt;/li&gt;
&lt;li&gt;🛡️ &lt;strong&gt;&lt;a href="https://www.w3.org/WAI/WCAG22/quickref/#principle4" rel="noopener noreferrer"&gt;Robust&lt;/a&gt;&lt;/strong&gt;: Content must work reliably with current and future technologies, including assistive devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm8n9262ycor66h77l9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm8n9262ycor66h77l9a.png" alt="The Four Principles" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Illustration &lt;a href="https://periscope.com/news/covid-19-accessibility-and-e-comm/" rel="noopener noreferrer"&gt;source&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Levels of Conformance
&lt;/h3&gt;

&lt;p&gt;WCAG defines three levels of conformance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🥉 &lt;strong&gt;Level A:&lt;/strong&gt; The minimum level. It addresses the &lt;strong&gt;essential A11y features&lt;/strong&gt; required for basic interaction. While meeting Level A ensures that essential content is accessible, some barriers may still exist.&lt;/li&gt;
&lt;li&gt;🥈 &lt;strong&gt;Level AA:&lt;/strong&gt; Building upon Level A, this level targets the &lt;strong&gt;most significant and common barriers&lt;/strong&gt;. It enhances the UX by improving readability, navigation, and overall usability, and is widely regarded as the standard for many legal and organizational requirements (EAA!).&lt;/li&gt;
&lt;li&gt;🥇 &lt;strong&gt;Level AAA:&lt;/strong&gt; Representing the highest standard of A11y, includes the most &lt;strong&gt;rigorous criteria&lt;/strong&gt;. Although achieving AAA ensures maximum accessibility, it may not be feasible for all types of content and is typically applied to specialized cases where the highest level of A11y is required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hsx3xjv9nyuwt6xbp4j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hsx3xjv9nyuwt6xbp4j.png" alt="WCAG 3 levels of conformance" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So our recommendation (well and the lawmakers also have that opinion) is to aim for &lt;strong&gt;Level AA&lt;/strong&gt; compliance, as it strikes a balance between A11y and practicality (aka affordability).&lt;/p&gt;

&lt;h2&gt;
  
  
  European Accessibility Act (EAA)
&lt;/h2&gt;

&lt;p&gt;The European Accessibility Act (EAA) is a new (actually it's from 2019) &lt;strong&gt;EU directive&lt;/strong&gt; that has to be fully implemented by &lt;em&gt;June 28th 2025&lt;/em&gt;.&lt;br&gt;
Based upon the mentioned WCAG 2.2, the EAA sets clear rules to ensure that web applications, websites, and other online services are accessible to everyone, including people with disabilities.&lt;br&gt;
In essence, digital tools must be designed to remove barriers, allowing all users to access information and services without difficulty.&lt;/p&gt;

&lt;p&gt;By focusing on web applications, the EAA pushes businesses and developers to create user-friendly, accessible online experiences.&lt;br&gt;
The law helps people with disabilities have the same opportunities and also encourages innovation in web design and development.&lt;br&gt;
In short, the EAA is a key step toward making the internet a more inclusive and in the end, more enjoyable space for all.&lt;/p&gt;

&lt;p&gt;The EAA source (hint: don't follow this link): &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32019L0882" rel="noopener noreferrer"&gt;https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32019L0882&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  National Implementation of the EU Directive
&lt;/h3&gt;

&lt;p&gt;While we won’t cover every detail here, here are some examples of national implementations of the EAA. Interestingly, even though Switzerland is not an EU member, they were the first country implementing the EAA into national law 😀&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🇨🇭 &lt;strong&gt;Switzerland&lt;/strong&gt;: Accessibility Standard 3.0 (eCH-0059) – &lt;em&gt;June 2020&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;Official Website: &lt;a href="https://www.ech.ch/en/standards/ech-0059/" rel="noopener noreferrer"&gt;ech.ch/en/standards/ech-0059/&lt;/a&gt; (German / French)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;🇩🇪 &lt;strong&gt;Germany&lt;/strong&gt; Barrierefreiheitsstärkungsgesetz (BFSG) – &lt;em&gt;July 2022&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;Private website about the BFSG: &lt;a href="https://bfsg-gesetz.de/" rel="noopener noreferrer"&gt;bfsg-gesetz.de&lt;/a&gt; (German)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;🇦🇹 &lt;strong&gt;Austria&lt;/strong&gt;: Barrierefreiheitsgesetz (BaFG) – &lt;em&gt;July 2023&lt;/em&gt;

&lt;ul&gt;
&lt;li&gt;Austrian camber of commerce: &lt;a href="https://www.wko.at/ce-kennzeichnung-normen/informationen-zum-barrierefreiheitsgesetz" rel="noopener noreferrer"&gt;About the BaFG&lt;/a&gt; (German)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Affected online services include
&lt;/h3&gt;

&lt;p&gt;The EAA impacts a variety of digital services, including&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;strong&gt;E-books&lt;/strong&gt;: Digital (e.g. academic) books that enable the circulation and consultation of a mostly textual and graphical intellectual work.&lt;/li&gt;
&lt;li&gt;🛒 &lt;strong&gt;E-commerce&lt;/strong&gt;: Retail websites and mobile applications that facilitate online transactions are required to meet accessibility standards.&lt;/li&gt;
&lt;li&gt;🏦 &lt;strong&gt;Online banking&lt;/strong&gt;: Online and mobile banking platforms must be accessible, ensuring that financial services are usable for everyone.&lt;/li&gt;
&lt;li&gt;🚌 &lt;strong&gt;Public &amp;amp; transportation services&lt;/strong&gt;: In some cases, the Act also touches on digital services in areas like transport (e.g., online ticketing and check-in services) and audiovisual media services. However, note that public sector websites and mobile apps are governed by a different EU directive (the Web Accessibility Directive).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although the EAA covers many services, there are areas where its application may still be ambiguous or unclear. For example, the EAA does not explicitly mention &lt;strong&gt;Angular&lt;/strong&gt; or &lt;strong&gt;JavaScript&lt;/strong&gt; frameworks, but it does require that certain web applications and websites are accessible to everyone.&lt;/p&gt;

&lt;p&gt;Make sure to follow our &lt;strong&gt;A11y blog series&lt;/strong&gt; to be prepared for the EAA and to learn how to make your &lt;em&gt;Angular Apps&lt;/em&gt; more accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Workshop
&lt;/h2&gt;

&lt;p&gt;For those looking to deepen their &lt;em&gt;Angular&lt;/em&gt; expertise, we offer a range of workshops – both in English and German:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;♿ &lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📈 &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; (including accessibility related topics)&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In this post, we explored the fundamentals of web accessibility — from its history and key statistics to the regulatory landscape, including the upcoming European Accessibility Act.&lt;br&gt;
Whether you’re a developer, designer, or business owner, understanding and implementing accessibility best practices is essential for creating inclusive &lt;em&gt;Angular Apps&lt;/em&gt; for everyone.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;giThub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Complete Guide for Server-Side Rendering (SSR) in Angular</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Thu, 04 Jul 2024 16:27:24 +0000</pubDate>
      <link>https://dev.to/lxt/complete-guide-for-server-side-rendering-ssr-in-angular-17me</link>
      <guid>https://dev.to/lxt/complete-guide-for-server-side-rendering-ssr-in-angular-17me</guid>
      <description>&lt;p&gt;Updated on &lt;em&gt;Mar. 23rd, 2025&lt;/em&gt; for &lt;strong&gt;Hybrid Rendering&lt;/strong&gt; &amp;amp; brandnew &lt;strong&gt;Incremental Hydration&lt;/strong&gt; (now including demo) in &lt;em&gt;Angular v19.2&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This comprehensive post includes a quick introduction to SSR, a detailed setup guide and several best practices with &lt;em&gt;Angular v19&lt;/em&gt; (released on &lt;em&gt;Nov 19th, 2024&lt;/em&gt;), enhancing the &lt;strong&gt;initial load performance&lt;/strong&gt; and thus the &lt;strong&gt;user experience&lt;/strong&gt; of modern &lt;strong&gt;web applications&lt;/strong&gt; built with &lt;em&gt;Angular&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you haven't upgraded to v19 yet, then what are you waiting for? In my humble opinion, the new &lt;strong&gt;Hybrid Rendering&lt;/strong&gt; and &lt;strong&gt;Incremental Hydration&lt;/strong&gt; features of &lt;em&gt;v19&lt;/em&gt; can already be used in production, even though they are still in &lt;em&gt;Developer Preview&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh174rn1ed2lg52u9tgka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh174rn1ed2lg52u9tgka.png" alt="Angular Features in Developer Preview (blue)" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See all Angular features in this &lt;a href="https://www.angular.courses/caniuse" rel="noopener noreferrer"&gt;Angular feature roadmap&lt;/a&gt; by &lt;a href="https://www.gerome.dev/" rel="noopener noreferrer"&gt;Gerome Grignon&lt;/a&gt;. By the way, if you want to use &lt;em&gt;Material&lt;/em&gt; and/or &lt;em&gt;CDK&lt;/em&gt; with SSR, you need at least &lt;em&gt;v18&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Angular team&lt;/em&gt; has recently (well actually for quite some time) been putting in &lt;a href="https://angular.dev/roadmap#fast-by-default" rel="noopener noreferrer"&gt;a huge effort&lt;/a&gt; and doing a fantastic job to help us improve the initial load time. SSR plays a significant role in achieving that goal for our framework of choice. Read my post from July 2023 to learn &lt;a href="https://www.angulararchitects.io/blog/why-is-initial-load-performance-so-important/" rel="noopener noreferrer"&gt;why initial load performance is so crucial&lt;/a&gt; for your &lt;em&gt;Angular&lt;/em&gt; apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Essentials
&lt;/h2&gt;

&lt;p&gt;Let's start with the basics. You can, of course, skip this section if you're already familiar with SSR, and continue with the next section about &lt;strong&gt;building&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Side Rendering (SSR)
&lt;/h3&gt;

&lt;p&gt;Server-Side Rendering (SSR) is a web development technique where the (in our case node) server generates &lt;strong&gt;the HTML content&lt;/strong&gt; of a web page (in our case with JavaScript), providing faster initial load time. This results in a smoother user experience, especially for those on slower networks (e.g. onboard a train in 🇩🇪 or 🇦🇹 – which I happen to be a lot recently 😏) or low-budget devices. Additionally, it improves SEO and crawlability for Social Media and other bots like the infamous ChatGPT.&lt;/p&gt;

&lt;p&gt;New &lt;em&gt;Angular CLI&lt;/em&gt; projects will automatically prompt SSR (since &lt;em&gt;Angular v17&lt;/em&gt;):&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 your-fancy-app-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For existing projects simply run the &lt;code&gt;ng add&lt;/code&gt; command (since &lt;em&gt;Angular v17&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng add @angular/ssr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: You might have to fix stuff manually (like adding imports of CommonJsDependencies) after adding SSR to your project 😬&lt;/p&gt;

&lt;p&gt;Follow the &lt;a href="https://angular.dev/guide/ssr#configure-server-side-rendering" rel="noopener noreferrer"&gt;angular.dev guide&lt;/a&gt; for detailed configuration. However, I'd recommend switching to the new Application Builder, which has SSR and SSG baked in (more on that in the &lt;strong&gt;build&lt;/strong&gt; section below). Let's first clarify what SSG stands for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static Site Generation (SSG)
&lt;/h3&gt;

&lt;p&gt;Static Site Generation (SSG) or Prerendering (like the Angular framework likes to call it), is the technique where HTML pages are prerendered &lt;strong&gt;at build time&lt;/strong&gt; and then served as static HTML when a URL is visited. Instead of rendering live on demand, SSG generates the HTML once and serves the same pre-built HTML to all users. This provides even faster load times and further improves the user experience. However, since the HTML is being stored on the server, this approach is limited whenever dynamic content is needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: For the use of SSG you don't need a node.js / express server. You can still ship your application from &lt;a href="https://nginx.org" rel="noopener noreferrer"&gt;nginx&lt;/a&gt; or even Apache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full-application Hydration (preview in v16, stable since v17)
&lt;/h3&gt;

&lt;p&gt;Hydration is the process where the prerendered static HTML, generated by SSR or SSG, is enhanced with interactivity on the client side. After the initial HTML is delivered and rendered in the browser, &lt;em&gt;Angular's JavaScript&lt;/em&gt; takes over to "hydrate" the static content, attaching &lt;strong&gt;event listeners&lt;/strong&gt; and thus making the page fully interactive. This approach combines the fast initial load times of SSR/SSG with the dynamic capabilities of a SPA, again leading to a better overall user experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffyncvqqe1rkzx6wjzoqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffyncvqqe1rkzx6wjzoqf.png" alt="Angular Hydration" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before &lt;em&gt;Angular's Hydration&lt;/em&gt; feature, the prerendered static DOM would have been destroyed and replaced with the client-side-rendered interactive version, potentially resulting in a layout shift or a full browser window flash aka content flicker – both leading to bad results in performance tools like &lt;a href="https://www.angulararchitects.io/blog/how-to-measure-initial-load-performance/" rel="noopener noreferrer"&gt;&lt;strong&gt;Lighthouse&lt;/strong&gt; and &lt;strong&gt;WebPageTest&lt;/strong&gt;&lt;/a&gt;. In my opinion, &lt;em&gt;Angular SSR&lt;/em&gt; was not production-ready until supporting Non-Destructive Hydration. This has changed in 2023 since this feature has already become stable in &lt;em&gt;Angular v17&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Please note: Since the introduction of &lt;strong&gt;Incremental Hydration&lt;/strong&gt; in v19 (more on that later), the classic Hydration is referred to as &lt;strong&gt;Full-application Hydration&lt;/strong&gt;. By the way, it's super easy to enable Hydration in &lt;em&gt;Angular&lt;/em&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// use v16 full-app hydration&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 still using &lt;em&gt;NgModules&lt;/em&gt; (for reasons), it becomes:&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;NgModule&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="nf"&gt;provideClientHydration&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;h3&gt;
  
  
  Deferrable Views (preview in v17, stable since v18) with &lt;a class="mentioned-user" href="https://dev.to/defer"&gt;@defer&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Deferrable Views&lt;/strong&gt;, also called &lt;code&gt;@defer&lt;/code&gt; blocks, are &lt;em&gt;Angular's&lt;/em&gt; native primitive for declaratively defer loading components of your application. It can be used as an alternative to the router-based lazy loading through &lt;code&gt;loadComponent()&lt;/code&gt; (used with Standalone Components) or &lt;code&gt;loadChildren()&lt;/code&gt; (used with another routes definition array and formerly used with &lt;code&gt;NgModules&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@defer&lt;/code&gt; block allows you to specify when a component should be loaded and rendered, which can be based on various triggers like &lt;code&gt;on idle&lt;/code&gt;, &lt;code&gt;on viewport&lt;/code&gt;, &lt;code&gt;on hover&lt;/code&gt;, &lt;code&gt;on interaction&lt;/code&gt; etc. This is a great way to improve the initial load performance of your application by deferring the loading of non-critical components – for example everything below-the-fold – until they are needed. It can also be used to lazyload heavy libraries (like charts or complex tables). Learn more about the &lt;code&gt;@defer&lt;/code&gt; block in my &lt;a href="https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: While &lt;strong&gt;Deferrable Views&lt;/strong&gt; work completely independently of SSR, they can be used in combination with SSR to enforce &lt;strong&gt;client-side rendering (CSR)&lt;/strong&gt; for certain components. This is especially useful for user-dependent content, such as user-specific lists or prices. The &lt;code&gt;@defer&lt;/code&gt; block will render the placeholder on the server and then load the real content in the browser once it has been triggered.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deferrable Views Demo
&lt;/h4&gt;

&lt;p&gt;You can find a demo in the &lt;code&gt;deferrable views&lt;/code&gt; branch in &lt;a href="https://github.com/L-X-T/ssr-ih-ng19-days" rel="noopener noreferrer"&gt;this repository&lt;/a&gt; on GitHub 😏&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Replay (in preview since v18, but battle-proven by Google)
&lt;/h3&gt;

&lt;p&gt;This example was taken from the official &lt;a href="https://blog.angular.dev/event-dispatch-in-angular-89d868d2351c" rel="noopener noreferrer"&gt;&lt;em&gt;Angular blog&lt;/em&gt;&lt;/a&gt;. Consider an app that contains a click button 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;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"onClick()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Previously, the event handler &lt;code&gt;(click)="onClick()"&lt;/code&gt; would only be called once your application has finished Hydration in the client. With Event Replay enabled, &lt;strong&gt;&lt;a href="https://github.com/google/jsaction" rel="noopener noreferrer"&gt;JSAction&lt;/a&gt;&lt;/strong&gt; is listening at the root element of the app. The library will capture events that (natively) bubble up to the root and replay them once Hydration is complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx27hp4mjrai66qt48r1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx27hp4mjrai66qt48r1.png" alt="Event Replay" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If implemented, &lt;em&gt;Angular&lt;/em&gt; apps will stop ignoring events before Hydration is complete and allow users to &lt;strong&gt;interact with the page while it's still loading&lt;/strong&gt;. There is no need for developers to do anything special beyond enabling this feature.&lt;/p&gt;

&lt;p&gt;And again, it's super comfy to enable Event Replay in your app 🤩&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;withEventReplay&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// use hydration with v18 event replay&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;strong&gt;Note&lt;/strong&gt;: At the time of writing, this feature is still in Developer Preview, so use it cautiously. However, I believe it's perfectly ready for production usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hybrid Rendering (in preview since v19)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Angular v19&lt;/em&gt; will introduce &lt;strong&gt;Hybrid Rendering&lt;/strong&gt; to meet modern web demands following &lt;a href="https://github.com/angular/angular/discussions/56785" rel="noopener noreferrer"&gt;this RFC&lt;/a&gt; in the &lt;em&gt;Angular GitHup repo&lt;/em&gt;. It allows providing additional route information for the server. Details like rendering modes and response headers will provide finer control for &lt;strong&gt;SSR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can now select the &lt;strong&gt;page rendering mode&lt;/strong&gt; per route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSR&lt;/strong&gt;: The page renders on the server during a request (best for dynamic content that updates quickly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSG&lt;/strong&gt;: The page renders during build time and is served as a static asset (best for UX
&amp;amp; performance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSR&lt;/strong&gt;: The page renders in the browser (best for user-based content)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do this we need to add a &lt;code&gt;serverConfig&lt;/code&gt; in &lt;code&gt;app.config.server.ts&lt;/code&gt; looking like this:&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="cm"&gt;/* src/app/app.config.server.ts */&lt;/span&gt;
&lt;span class="c1"&gt;// imports [...]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverAppConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideServerRendering&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;provideServerRoutesConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverRoutes&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;const&lt;/span&gt; &lt;span class="nx"&gt;serverConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mergeApplicationConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;serverAppConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be used in our &lt;code&gt;main.server.ts&lt;/code&gt; instead of the client &lt;code&gt;appConfig&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="cm"&gt;/* src/main.server.ts */&lt;/span&gt;
&lt;span class="c1"&gt;// imports [...]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bootstrap&lt;/span&gt; &lt;span class="o"&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="nf"&gt;bootstrapApplication&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="nx"&gt;serverConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can specify the &lt;code&gt;renderMode&lt;/code&gt; for each &lt;code&gt;serverRoute&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="cm"&gt;/* src/app/app.routes.server.ts */&lt;/span&gt;
&lt;span class="c1"&gt;// imports [...]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerRoute&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ssr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ssg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Prerender&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;csr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&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;strong&gt;Note&lt;/strong&gt;: The same routes need to be used in &lt;code&gt;app.routes.ts&lt;/code&gt; to become fully functional.&lt;/p&gt;

&lt;p&gt;Additionally, some intelligent features like server side &lt;strong&gt;301 redirects&lt;/strong&gt; or &lt;strong&gt;404 not found&lt;/strong&gt; errors can be added to the server config.&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="cm"&gt;/* src/app/app.routes.server.ts */&lt;/span&gt;
&lt;span class="c1"&gt;// imports [...]&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerRoute&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// [...],&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redirect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&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;Cache-Control&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;no-cache&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;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RenderMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&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 want to play around with this, check out &lt;a href="https://github.com/jeanmeche/ssr-v19" rel="noopener noreferrer"&gt;this v19-ssr demo&lt;/a&gt; by ma man &lt;a href="https://github.com/JeanMech" rel="noopener noreferrer"&gt;Matthieu Riegler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: At the time of writing, this feature is still in Developer Preview, so implement it cautiously as the API may still change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incremental Hydration (in preview since v19)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Partial Hydration&lt;/strong&gt;, now called &lt;strong&gt;Incremental Hydration&lt;/strong&gt;, announced at &lt;a href="https://ng-conf.org/" rel="noopener noreferrer"&gt;ng-conf&lt;/a&gt; and &lt;a href="https://io.google/2024/explore/7deddebc-3cae-4285-b2a9-affb5296102e/" rel="noopener noreferrer"&gt;Google I/O&lt;/a&gt; 2024, is a technique that allows incremental hydration of an app after server-side rendering, improving the initial load but also runtime performance by loading less JavaScript upfront. It builds upon the fabulous &lt;code&gt;@defer&lt;/code&gt; API, in which we all fell in love since &lt;em&gt;Angular v17&lt;/em&gt;. It's enabling &lt;em&gt;Angular&lt;/em&gt; to render the HTML content on the server and hydrate deferred blocks on the client after they have been triggered to do so.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn656jn7tzpkvaztkrp5l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn656jn7tzpkvaztkrp5l.png" alt="The FUTURE is incremental hydration" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;Angular team&lt;/em&gt; (special thanks goes out to &lt;a href="https://github.com/thePunderWoman" rel="noopener noreferrer"&gt;Jessica Janiuk&lt;/a&gt;! By the way, watch here presenation of &lt;a href="https://www.youtube.com/watch?v=v5KTJGEYLsM" rel="noopener noreferrer"&gt;Incremental Hydration in Angular v19 on YouTube&lt;/a&gt;) completed the &lt;a href="https://github.com/angular/angular/discussions/57664" rel="noopener noreferrer"&gt;RFC&lt;/a&gt; in the &lt;em&gt;Angular GitHup repo&lt;/em&gt; and is now actively prototyping this feature, with an &lt;em&gt;experimental&lt;/em&gt; release in &lt;em&gt;v19&lt;/em&gt; for all performance-critical applications out there 🥳&lt;/p&gt;

&lt;p&gt;To test it simply add &lt;code&gt;withIncrementalHydration()&lt;/code&gt; to your &lt;code&gt;app.config.ts&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withIncrementalHydration&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="s2"&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// [...]&lt;/span&gt;
    &lt;span class="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withIncrementalHydration&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;Please note: &lt;strong&gt;Event Replay&lt;/strong&gt; is automatically enabled when using &lt;code&gt;withIncrementalHydration()&lt;/code&gt;, so you can remove that provider.&lt;/p&gt;

&lt;p&gt;Next, you can set a &lt;strong&gt;Hydration Trigger&lt;/strong&gt; by choosing either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hydrate on&lt;/code&gt; with a &lt;strong&gt;trigger&lt;/strong&gt; (same as for &lt;code&gt;@defer&lt;/code&gt;, see here for a &lt;a href="https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/#triggers" rel="noopener noreferrer"&gt;full list of built-in triggers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hydrate when&lt;/code&gt; with a &lt;strong&gt;boolean&lt;/strong&gt; symbol, Signal or function as the trigger&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hydrate never&lt;/code&gt; component will get rendered on the server but &lt;strong&gt;never hydrated&lt;/strong&gt; (for static content)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  hydrate on
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@defer (on viewport; prefetch on idle; hydrate on hover) {
  &lt;span class="nt"&gt;&amp;lt;app-deferred-hydration&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component's static and SSRed HTML will be rendered before coming into the viewport, the corresponding JS bundle will be prefetched on idle (like PreloadingStrategy in the Router) and then finally the JS (all the interactivity, like event handlers) will be hydrated into the browser on hover. This is in stark contrast to the default full-application hydration, where all the components are hydrated at once.&lt;/p&gt;

&lt;h4&gt;
  
  
  hydrate when
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@defer (hydrate when isUserLoggedIn) {
  &lt;span class="nt"&gt;&amp;lt;app-deferred-hydration&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component will be rendered on &lt;code&gt;idle&lt;/code&gt; (default &lt;a class="mentioned-user" href="https://dev.to/defer"&gt;@defer&lt;/a&gt; trigger), i.e. after bootstrapping the App is complete, but it will only be hydrated once &lt;code&gt;isUserLoggedIn&lt;/code&gt; is true.&lt;/p&gt;

&lt;h4&gt;
  
  
  hydrate never
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@defer (on viewport; hydrate never) {
  &lt;span class="nt"&gt;&amp;lt;app-deferred-hydration&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The component will be rendered on &lt;code&gt;viewport&lt;/code&gt;, but it will never be hydrated - so no event handlers will be attached.&lt;/p&gt;

&lt;h4&gt;
  
  
  Incremental Hydration Demo
&lt;/h4&gt;

&lt;p&gt;Make sure to play around with this fun-tastic feature starting from my &lt;a href="https://github.com/L-X-T/ssr-ih-ng19-days" rel="noopener noreferrer"&gt;Incremental Hydration Demo&lt;/a&gt; on GitHub 😏&lt;/p&gt;

&lt;h4&gt;
  
  
  Give back some love
&lt;/h4&gt;

&lt;p&gt;Since this feature is still in &lt;em&gt;experimental&lt;/em&gt; make sure to help further improve it by providing useful feedback to the &lt;a href="https://github.com/angular/angular/discussions/57664" rel="noopener noreferrer"&gt;RFC&lt;/a&gt; in the &lt;em&gt;Angular GitHup repo&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build
&lt;/h2&gt;

&lt;p&gt;Since &lt;em&gt;Angular v17&lt;/em&gt; we have two options for building our &lt;em&gt;Angular app&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular's new Application Builder (all-in-one)
&lt;/h3&gt;

&lt;p&gt;As mentioned, I'd recommend switching to the new Application Builder using &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;esbuild&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Vite&lt;/strong&gt;&lt;/a&gt;. The advantage of using esbuild over Webpack is that it offers faster build times and more efficient and fine-grained bundling. The significantly smaller bundle also leads to better initial load performance – with or without SSR! Vite is a faster development server supporting extremely fast Hot Module Replacement (HMR).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb13fq6ouw1tiukq4jxw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb13fq6ouw1tiukq4jxw3.png" alt="Vite + esbuild" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, both &lt;strong&gt;SSR&lt;/strong&gt; and &lt;strong&gt;Prerendering (SSG)&lt;/strong&gt; are enabled by default as mentioned in this screenshot from the &lt;em&gt;Angular Docs&lt;/em&gt; showing a table of the &lt;em&gt;Angular Builders&lt;/em&gt; (note that the &lt;code&gt;@angular-devkit/build-angular:server&lt;/code&gt; is missing here):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jgreqk7xkx1x3tli2m0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jgreqk7xkx1x3tli2m0.png" alt="Angular Builder" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply run &lt;code&gt;ng b&lt;/code&gt; to trigger a &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;server&lt;/code&gt; build in one step. &lt;em&gt;Angular&lt;/em&gt; will automatically process the Router configuration(s) to find all unparameterized routes and prerender them for you. If you want, you can add parameterized routes via &lt;a href="https://angular.dev/guide/prerendering#prerendering-parameterized-routes" rel="noopener noreferrer"&gt;a txt file&lt;/a&gt;. To migrate, read my &lt;a href="https://www.angulararchitects.io/blog/angular-17-update-control-flow-app-builder-migration/" rel="noopener noreferrer"&gt;automated App Builder migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  If still using Webpack (for reasons)
&lt;/h3&gt;

&lt;p&gt;If – for any reason – you're still committed to using &lt;strong&gt;Webpack&lt;/strong&gt; to build your web app, you need the browser builder to be configured in your &lt;code&gt;angular.json&lt;/code&gt; (might be in &lt;code&gt;project.json&lt;/code&gt; if you're using Nx). This will, of course, be added automatically once you run &lt;code&gt;ng add @angular/ssr&lt;/code&gt;.&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;"server"&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;"@angular-devkit/build-angular:server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"outputPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist/your-fancy-app-name/server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"server.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tsConfig"&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.server.json"&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;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;Note: The referenced &lt;code&gt;server.ts&lt;/code&gt; lies in the project's root and is the entry point of your server application. With this dedicated server builder, there is also a dedicated &lt;code&gt;tsconfig.server.json&lt;/code&gt; (whereas the new Application Builder recommended previously merges the two tsconfig files for more convenience) 🤓&lt;/p&gt;

&lt;p&gt;Now let's quickly have a look at the build scripts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: If you haven't started using &lt;code&gt;pnpm&lt;/code&gt;, you're missing out. However, of course, both &lt;code&gt;npm run ...&lt;/code&gt; and &lt;code&gt;yarn ...&lt;/code&gt; will also work instead of &lt;code&gt;pnpm ...&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev:ssr
ng run your-fancy-app-name:serve-ssr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;ng s&lt;/code&gt;, which offers live reload during development, but uses server-side rendering. Altogether, it's a bit slower than &lt;code&gt;ng s&lt;/code&gt; and won't be used a lot apart from quickly testing SSR on &lt;code&gt;localhost&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm build:ssr
ng build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ng run your-fancy-app-name:server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Builds both the &lt;code&gt;browser&lt;/code&gt; application and the &lt;code&gt;server&lt;/code&gt; script in production mode into the &lt;code&gt;dist&lt;/code&gt; folder. Use this command when you want to build the project for deployment or run performance tests. For the latter, you could use &lt;a href="https://www.npmjs.com/package/serve" rel="noopener noreferrer"&gt;serve&lt;/a&gt; or a similar tool to serve the application on your &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;You have two options for deployment. While both are technically possible, I'd recommend using the second one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using on-demand rendering mode via node server
&lt;/h3&gt;

&lt;p&gt;Starts the server for serving the application with node using SSR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm serve:ssr
node dist/your-fancy-app-name/server/main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've shown a detailed &lt;a href="https://www.angulararchitects.io/blog/how-to-use-angular-ssr-with-hydration/" rel="noopener noreferrer"&gt;example Docker container here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caution&lt;/strong&gt;: &lt;em&gt;Angular&lt;/em&gt; requires a certain &lt;em&gt;Node.js&lt;/em&gt; version to run, for details see the &lt;a href="https://angular.dev/reference/versions#actively-supported-versions" rel="noopener noreferrer"&gt;Angular version compatibility matrix&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using build time SSR with SSG (recommended)
&lt;/h3&gt;

&lt;p&gt;This option doesn't need a node environment on the server and is also way faster than the other one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm prerender
ng run your-fancy-app-name:prerender
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Used to generate an application's prerendered routes. The static HTML files of the prerendered routes will be attached to the &lt;code&gt;browser&lt;/code&gt; build, not the &lt;code&gt;server&lt;/code&gt;. Now you can deploy your &lt;code&gt;browser&lt;/code&gt; build to whatever host you want (e.g. &lt;a href="https://nginx.org/en/" rel="noopener noreferrer"&gt;&lt;strong&gt;nginx&lt;/strong&gt;&lt;/a&gt;). You're doing the same thing as without SSR with some extra directories (and &lt;code&gt;index.html&lt;/code&gt; files).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note&lt;/strong&gt;: If you're using the new (and recommended) Application Builder, you can skip these steps for building and prerendering since they're already included in &lt;code&gt;ng b&lt;/code&gt;. In other words, you have zero extra work for building including SSR &amp;amp; SSG – pretty great, huh? 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  Debug
&lt;/h2&gt;

&lt;p&gt;The first step in debugging is looking for misconfigurations in your &lt;code&gt;angular.json&lt;/code&gt; (&lt;code&gt;project.json&lt;/code&gt;) or some errors in your &lt;code&gt;server.ts&lt;/code&gt;. If both look good, there is no definite way to debug SSR and SSG issues. Feel free to &lt;a href="//mailto:alexander.thalhammer@angulararchitects.io"&gt;&lt;strong&gt;contact me&lt;/strong&gt;&lt;/a&gt; if you're experiencing any troubles.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to avoid the most common issue
&lt;/h3&gt;

&lt;p&gt;Browser-specific objects like &lt;strong&gt;document&lt;/strong&gt;, &lt;strong&gt;window&lt;/strong&gt;, &lt;strong&gt;localStorage&lt;/strong&gt;, etc., do &lt;strong&gt;NOT&lt;/strong&gt; exist on the &lt;code&gt;server&lt;/code&gt; app. Since these objects are not available in a Node.js environment, trying to access them results in errors. This can be avoided by using the document injector or by running code explicitly in the browser:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PLATFORM_ID&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="s2"&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;DOCUMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPlatformBrowser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPlatformServer&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="s2"&gt;@angular/common&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="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;platform&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;PLATFORM_ID&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="nb"&gt;document&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isPlatformBrowser&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;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Safe to use document, window, localStorage, etc. :-)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isPlatformServer&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;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Not smart to use document here, however, we can inject it ;-)&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nb"&gt;document&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;h3&gt;
  
  
  Browser-Exclusive Render Hooks
&lt;/h3&gt;

&lt;p&gt;An alternative to injecting &lt;code&gt;isPlatformBrowser&lt;/code&gt; are the two render hooks &lt;code&gt;afterNextRender&lt;/code&gt; and &lt;code&gt;afterRender&lt;/code&gt;, which can only be used within the &lt;a href="https://angular.dev/guide/di/dependency-injection-context" rel="noopener noreferrer"&gt;&lt;strong&gt;injection context&lt;/strong&gt;&lt;/a&gt; (basically field initializers or the constructor of a component):&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;afterNextRender&lt;/code&gt; hook, takes a callback function that runs &lt;strong&gt;once&lt;/strong&gt; after the &lt;strong&gt;next&lt;/strong&gt; change detection – a bit similar to the init lifecycle hooks. It's used for performing one-time initializations, such as integrating 3party libs or utilizing browser APIs:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBrowserComponent&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="nf"&gt;afterNextRender&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello my friend!&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;If you want to use this outside of the injection context, you'll have to add the injector:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBrowserComponent&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;onClick&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;you've just clicked!&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;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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;afterRender&lt;/code&gt; hook, instead, is executed after &lt;strong&gt;every upcoming&lt;/strong&gt; change detection. So use it with extra &lt;strong&gt;caution&lt;/strong&gt; – same as you would do with the &lt;code&gt;ngDoCheck&lt;/code&gt; and &lt;code&gt;ng[Content|View]Checked&lt;/code&gt; hooks because we know that Change Detection will be triggered a lot in our &lt;em&gt;Angular&lt;/em&gt; app – at least until we go &lt;strong&gt;zoneless&lt;/strong&gt;, but that story that will be presented in yet another blog post 😎&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBrowserComponent&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="nf"&gt;afterRender&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cd just finished work!&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;If you'd like to deep dive into these hooks, I recommend reading this &lt;a href="https://netbasal.com/exploring-angulars-afterrender-and-afternextrender-hooks-7133612a0287" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; by Netanel Basal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular Hydration in DevTools
&lt;/h3&gt;

&lt;p&gt;The awesome &lt;em&gt;Angular&lt;/em&gt; collaborator &lt;a href="https://x.com/Jean__Meche" rel="noopener noreferrer"&gt;Matthieu Riegler&lt;/a&gt; has recently added &lt;strong&gt;hydration debugging&lt;/strong&gt; support to the &lt;em&gt;Angular's DevTools&lt;/em&gt;! Which are, besides all Chromium derivatives, also available for Firefox, but then why would somebody still use that Boomer browser? 😏&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffblbzmskldtrmfnvrtli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffblbzmskldtrmfnvrtli.png" alt="Hydration in Angular DevTools" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the 💧 for hydrated components. Even though this feature was announced in the &lt;em&gt;Angular v18&lt;/em&gt; update, it also works in past versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other SSR Debugging Best Practices
&lt;/h3&gt;

&lt;p&gt;Here is a collection of some more opinionated debugging recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevTools&lt;/strong&gt;: Besides the updated &lt;em&gt;Angular DevTools&lt;/em&gt; tab, inspect your HTML with the &lt;strong&gt;Elements&lt;/strong&gt; tab and your API requests with the &lt;strong&gt;Network&lt;/strong&gt; tab. BTW, you should also simulate a slow connection here when performance testing your app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Console&lt;/strong&gt;: I personally like to log everything into my &lt;strong&gt;Console&lt;/strong&gt;. Not interested in a logger lib since I'm fine with console.log() and maybe some other levels. Any console logs will be printed into the terminal where &lt;code&gt;ng b&lt;/code&gt; or &lt;code&gt;pnpm dev:ssr&lt;/code&gt; or &lt;code&gt;pnpm serve:ssr&lt;/code&gt; has been run. We don't need to talk about logging into the browser's console on production, or do we?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt;: Start your SSR server with the --inspect flag to get more information: &lt;code&gt;node --inspect dist/server/main.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetching&lt;/strong&gt;: Ensure all necessary data is available at render time. Use &lt;em&gt;&lt;a href="https://angular.dev/api/core/TransferState?tab=description" rel="noopener noreferrer"&gt;Angular's TransferState&lt;/a&gt;&lt;/em&gt; to transfer data from the server to the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt;: Make sure all routes are correctly configured and match on both the &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;server&lt;/code&gt; builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environments&lt;/strong&gt;: Ensure environment variables are correctly set up for both &lt;code&gt;browser&lt;/code&gt; and &lt;code&gt;server&lt;/code&gt; builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3rd-party Libs&lt;/strong&gt;: As always, be very careful about what you include in your project. Some libraries might not be implemented correctly and thus not work in an SSR context. Use conditional imports or platform checks to handle these cases or, even better, get rid of those libs in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all I have got so far. If you've got anything to add, feel super free to &lt;a href="//mailto:alexander.thalhammer@angulararchitects.io"&gt;&lt;strong&gt;contact me&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disable Hydration for Components
&lt;/h3&gt;

&lt;p&gt;Some components may not work properly with hydration enabled due to some issues, like DOM Manipulation. As a workaround, you can add the &lt;code&gt;ngSkipHydration&lt;/code&gt; attribute to a component's tag to skip hydrating the entire component.&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;app-example&lt;/span&gt; &lt;span class="na"&gt;ngSkipHydration&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can set &lt;code&gt;ngSkipHydration&lt;/code&gt; as a host binding.&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="na"&gt;ngSkipHydration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DryComponent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please use this carefully and thoughtfully. It is intended as a last-resort workaround. Components that have to skip hydration should be considered bugs that need to be fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Fetch API instead of XHR
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://web.dev/articles/introduction-to-fetch" rel="noopener noreferrer"&gt;Fetch API&lt;/a&gt;&lt;/strong&gt; offers a modern, promise-based approach to making HTTP requests, providing a cleaner and more readable syntax compared to the well-aged &lt;strong&gt;XMLHttpRequest&lt;/strong&gt;. Additionally, it provides better error handling and more powerful features such as support for streaming responses and configurable request options. It's also recommended to be used with SSR by the &lt;em&gt;&lt;a href="https://stackoverflow.com/questions/77512654/angular-detected-that-httpclient-is-not-configured-to-use-fetch-apis-angul/77512684#77512684" rel="noopener noreferrer"&gt;Angular team&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To enable it, simply add &lt;code&gt;withFetch()&lt;/code&gt; to your &lt;code&gt;provideHttpClient()&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withFetch&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 still using &lt;em&gt;NgModules&lt;/em&gt; (for reasons), this becomes:&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;NgModule&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="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withFetch&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;h3&gt;
  
  
  Configure SSR API Request Cache
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;Angular HttpClient&lt;/em&gt; will cache all outgoing network requests when running on the server. The responses are serialized and transferred to the browser as part of the server-side HTML. In the browser, &lt;em&gt;HttpClient&lt;/em&gt; checks whether it has data in the cache and if so, reuses that instead of making a new HTTP request during the initial load. &lt;em&gt;HttpClient&lt;/em&gt; stops using the cache once an application becomes stable in the browser.&lt;/p&gt;

&lt;p&gt;By default, HttpClient caches all &lt;code&gt;HEAD&lt;/code&gt; and &lt;code&gt;GET&lt;/code&gt; requests that don't contain &lt;strong&gt;Authorization&lt;/strong&gt; or &lt;strong&gt;Proxy-Authorization&lt;/strong&gt; headers. You can override those settings by using &lt;code&gt;withHttpTransferCacheOptions&lt;/code&gt; when providing hydration:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&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="nf"&gt;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;withEventReplay&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nf"&gt;withHttpTransferCacheOptions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// to filter&lt;/span&gt;
        &lt;span class="na"&gt;includeHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="c1"&gt;// to include headers&lt;/span&gt;
        &lt;span class="na"&gt;includePostRequests&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="c1"&gt;// to include POST&lt;/span&gt;
        &lt;span class="na"&gt;includeRequestsWithAuthHeaders&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;// to include with auth&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;h3&gt;
  
  
  Use Hydration support in Material 18 and CDK 18 💧
&lt;/h3&gt;

&lt;p&gt;Starting with &lt;em&gt;Angular Material 18&lt;/em&gt;, all components and primitives are fully SSR and Hydration compatible. For information, read this &lt;a href="https://blog.angular.dev/material-3-experimental-support-in-angular-17-2-8e681dde650e" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;. On how to upgrade your &lt;em&gt;Angular Material&lt;/em&gt; app, consult the docs on &lt;a href="https://material.angular.io/guide/material-2-theming#how-to-migrate-an-app-from-material-2-to-material-3" rel="noopener noreferrer"&gt;migrate from Material 2 to Material 3&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combine SSR for static &amp;amp; CSR for user content 🤯
&lt;/h3&gt;

&lt;p&gt;As already mentioned above, with &lt;em&gt;&lt;strong&gt;Angular v17 Deferrable Views&lt;/strong&gt;&lt;/em&gt; you can easily mix SSR/SSG with CSR 🎉&lt;/p&gt;

&lt;p&gt;The usage is pretty straightforward: All &lt;code&gt;@defer&lt;/code&gt; components will render their &lt;code&gt;@placeholder&lt;/code&gt; on the server and the real content will be loaded and rendered once they have been triggered (by &lt;em&gt;on&lt;/em&gt; or &lt;em&gt;when&lt;/em&gt;) in the browser. Learn more about &lt;a href="https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/" rel="noopener noreferrer"&gt;how to use and trigger Deferrable Views&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are some primitive &lt;strong&gt;examples&lt;/strong&gt; of how to combine SSR and CSR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static pages: Use SSR (SSG)&lt;/li&gt;
&lt;li&gt;Static content with live updates: Use deferred components for the live content and SSR for the rest&lt;/li&gt;
&lt;li&gt;Product list with prices depending on the user: Defer price components and use SSR for the rest&lt;/li&gt;
&lt;li&gt;List with items depending on the user: Defer the list component and use SSR for the rest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So basically, everywhere you need CSR (for user-dependent content), you need to &lt;code&gt;@defer&lt;/code&gt; those parts. Use the &lt;code&gt;@placeholder&lt;/code&gt; (and &lt;code&gt;@loading&lt;/code&gt;) to show spinners or equivalents to inform the user that something is still being loaded. Also, make sure to reserve the right amount of space for the deferred components – avoid layout shifts at all costs!&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO and Social Media Crawling 🔍
&lt;/h3&gt;

&lt;p&gt;If you want to look good on Google and/or social media platforms, make sure to implement all the necessary &lt;strong&gt;meta tags&lt;/strong&gt; in SSR. For a comprehensive list, including some tools and tips, &lt;a href="https://moz.com/blog/meta-data-templates-123" rel="noopener noreferrer"&gt;jump here&lt;/a&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SeoComponent&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;title&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;Title&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;meta&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;Meta&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="c1"&gt;// set SEO metadata&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My fancy page/route title. Ideal length 60-70 chars&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;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTag&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My fancy meta description. Ideal length 120-150 characters.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use SSR &amp;amp; SSG within AnalogJS 🚀
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://analogjs.org/" rel="noopener noreferrer"&gt;AnalogJS&lt;/a&gt; is &lt;em&gt;the&lt;/em&gt; meta-framework built on top of &lt;em&gt;Angular&lt;/em&gt; – like &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; (React), &lt;a href="https://nuxt.com/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; (VueJS), &lt;a href="https://start.solidjs.com/" rel="noopener noreferrer"&gt;SolidStart&lt;/a&gt; (Solid). Analog supports SSR during development and building for production. If you want to know more, read the announcement of &lt;a href="https://dev.to/analogjs/announcing-analogjs-10-19an"&gt;version 1.0&lt;/a&gt; by &lt;a href="https://x.com/brandontroberts" rel="noopener noreferrer"&gt;Brandon Roberts&lt;/a&gt; or wait for my &lt;strong&gt;upcoming blog post&lt;/strong&gt; 😏&lt;/p&gt;

&lt;h3&gt;
  
  
  Angular SSR &amp;amp; SSG featuring I18n
&lt;/h3&gt;

&lt;p&gt;Since the &lt;em&gt;Angular I18n&lt;/em&gt; only works during built-time, it's fairly limited. Therefore, we recommend using &lt;a href="https://jsverse.github.io/transloco/" rel="noopener noreferrer"&gt;Transloco&lt;/a&gt; (or &lt;a href="https://github.com/ngx-translate/core" rel="noopener noreferrer"&gt;NGX-Translate&lt;/a&gt;). When adding Transloco by running &lt;code&gt;ng add @jsverse/transloco&lt;/code&gt;, you'll be prompted for SSR usage. However, you can also manually add the necessary changes for SSR (see &lt;a href="https://jsverse.github.io/transloco/docs/ssr-support" rel="noopener noreferrer"&gt;Transloco Docs&lt;/a&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="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="s2"&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;TranslocoHttpLoader&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;TranslocoLoader&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;http&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;HttpClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;getTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Translation&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/assets/i18n/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&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="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;== provide base URL for each env&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will SSR everything in the default language and then switch to the user's language (if different) in the browser. While this generally works, &lt;strong&gt;it's definitely not ideal to see the text being swapped&lt;/strong&gt;. Furthermore, we need to ensure there are &lt;strong&gt;no layout shifts&lt;/strong&gt; upon switching! If you come up with any ideas on how to improve this, please &lt;a href="//mailto:alexander.thalhammer@angulararchitects.io"&gt;&lt;strong&gt;contact me&lt;/strong&gt;&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  SSR and Hydration with Native Federation
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Angular Architects'&lt;/strong&gt; native federation package now finally supports SSR for the shell app (starting with v18.2.3). This allows you to use &lt;strong&gt;Module Federation&lt;/strong&gt; together with SSR and SSG in your &lt;em&gt;Angular&lt;/em&gt; app. &lt;a href="https://github.com/angular-architects/module-federation-plugin/blob/main/libs/native-federation/README.md" rel="noopener noreferrer"&gt;&lt;strong&gt;Native Federation&lt;/strong&gt;&lt;/a&gt; has the same API as the webpack Module Federation but uses browser-native &lt;a href="https://www.angulararchitects.io/blog/import-maps-the-next-evolution-step-for-micro-frontends-article/" rel="noopener noreferrer"&gt;Import Maps&lt;/a&gt; and is thus also working with &lt;code&gt;esbuild&lt;/code&gt; – &lt;em&gt;Angular's&lt;/em&gt; new Application Builder.&lt;/p&gt;

&lt;p&gt;Learn more about this approach and how to get started in &lt;a href="https://devm.io/angular/microfrontend-module-federation-ssr-hydration" rel="noopener noreferrer"&gt;this post&lt;/a&gt; by &lt;em&gt;the master of module federation &lt;strong&gt;Manfred Steyer&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caution with PWA
&lt;/h3&gt;

&lt;p&gt;Be careful if you are using &lt;em&gt;Angular SSR&lt;/em&gt; in combination with the &lt;em&gt;Angular PWA&lt;/em&gt; service worker because the behavior deviates from default SSR. The initial request will be server-side rendered as expected. However, subsequent requests are handled by the service worker and thus client-side rendered.&lt;/p&gt;

&lt;p&gt;Most of the time that's what you want. Nevertheless, if you want a fresh request you can use the &lt;code&gt;freshness&lt;/code&gt; option as &lt;em&gt;Angular PWA&lt;/em&gt; &lt;code&gt;navigationRequestStrategy&lt;/code&gt;. This approach will try a network request and fall back to the cached version of &lt;em&gt;index.html&lt;/em&gt; when offline. For more information, consult the &lt;em&gt;&lt;a href="https://angular.dev/ecosystem/service-workers/config#navigationrequeststrategy" rel="noopener noreferrer"&gt;Angular Docs&lt;/a&gt;&lt;/em&gt; and read this &lt;a href="https://stackoverflow.com/questions/56383569/how-to-make-angular-universal-and-pwa-work-together/56400078#56400078" rel="noopener noreferrer"&gt;response on Stack Overflow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;The next step will be &lt;strong&gt;streamed SSR&lt;/strong&gt; for zoneless apps. To see what’s planned for upcoming Angular releases, check the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angular.dev/roadmap#future-work-explorations-and-prototyping" rel="noopener noreferrer"&gt;Angular roadmap on angular.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Workshop
&lt;/h2&gt;

&lt;p&gt;If you want to deep dive into Angular, we offer a variety of workshops – both in English and German.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop&lt;/strong&gt;&lt;/a&gt; 🚀&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; 📈 (including performance related topics)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/en/training/angular-accessibility-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Accessibility Workshop&lt;/strong&gt;&lt;/a&gt; ♿&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In summary, implementing Server-Side Rendering (SSR) in &lt;em&gt;Angular&lt;/em&gt; — along with Static Site Generation (SSG), hydration, and event replay — significantly enhances the initial load performance of your &lt;em&gt;Angular&lt;/em&gt; applications. With the new Incremental Hydration feature, &lt;em&gt;Angular&lt;/em&gt; has taken a significant step toward becoming the framework of choice for building high-performance web applications – something it has not traditionally been known for.&lt;/p&gt;

&lt;p&gt;This advancement results in a better user experience, particularly on slower networks or less capable devices, and it also improves your web app’s SEO and crawlability. By following the guidelines and best practices outlined in this guide, you can boost your apps’ load performance with minimal effort. Furthermore, the new Application Builder simplifies the process of building and deploying your projects.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;&lt;strong&gt;contact me&lt;/strong&gt;&lt;/a&gt; for further questions or join our &lt;a href="https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/" rel="noopener noreferrer"&gt;&lt;strong&gt;Performance Workshop 🚀&lt;/strong&gt;&lt;/a&gt; or the &lt;a href="https://www.angulararchitects.io/en/training/angular-best-practices/" rel="noopener noreferrer"&gt;&lt;strong&gt;Best Practices Workshop&lt;/strong&gt;&lt;/a&gt; 📈to learn more about performance optimization for &lt;em&gt;Angular&lt;/em&gt; apps.&lt;/p&gt;

&lt;p&gt;This blog post was written by &lt;a href="https://alex.thalhammer.name/" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. Follow me on &lt;a href="https://github.com/L-X-T" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://twitter.com/LX_T" rel="noopener noreferrer"&gt;X&lt;/a&gt; or &lt;a href="https://at.linkedin.com/in/thalhammer" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/blog/why-is-initial-load-performance-so-important/" rel="noopener noreferrer"&gt;Why is Initial Load Performance so Important?&lt;/a&gt; by Alexander Thalhammer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.dev/angular-v16-is-here-4d7a28ec680d" rel="noopener noreferrer"&gt;Angular v16 – official blog post&lt;/a&gt; by Minko Gechev&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/blog/angular-17-update-control-flow-app-builder-migration/" rel="noopener noreferrer"&gt;Angular Update Guide to V17 incl. migrations&lt;/a&gt; by Alexander Thalhammer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/" rel="noopener noreferrer"&gt;Angular v17’s Deferrable Views&lt;/a&gt; by Alexander Thalhammer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe" rel="noopener noreferrer"&gt;Angular v18 – official blog post&lt;/a&gt; by Minko Gechev&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://netbasal.com/exploring-angulars-afterrender-and-afternextrender-hooks-7133612a0287" rel="noopener noreferrer"&gt;Angular’s after(Next)Render hooks&lt;/a&gt; by Netanel Basal&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.angular.dev/event-dispatch-in-angular-89d868d2351c" rel="noopener noreferrer"&gt;Angular Event Replay blog post&lt;/a&gt; by Jatin Ramanathan &amp;amp; Tom Wilkinson&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=v5KTJGEYLsM" rel="noopener noreferrer"&gt;Angular Incremental Hydration on YouTube&lt;/a&gt; by Jessica Janiuk&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.angulararchitects.io/blog/how-to-use-angular-ssr-with-hydration/" rel="noopener noreferrer"&gt;Angular SSR Docker example&lt;/a&gt; by Alexander Thalhammer&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>performance</category>
      <category>ssr</category>
    </item>
  </channel>
</rss>
