<?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>Agentic Engineering: What Do AI Coding Tools Do With Your Code?</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Fri, 05 Jun 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/lxt/agentic-engineering-what-do-ai-coding-tools-do-with-your-code-2mjn</link>
      <guid>https://dev.to/lxt/agentic-engineering-what-do-ai-coding-tools-do-with-your-code-2mjn</guid>
      <description>&lt;p&gt;In the &lt;a href="https://www.angulararchitects.io/blog/best-llms-for-angular/" rel="noopener noreferrer"&gt;first post&lt;/a&gt; of this five-part series, I wrote about the LLMs I currently like to use for &lt;em&gt;Angular&lt;/em&gt; development. In the &lt;a href="https://www.angulararchitects.io/blog/ai-apps-harnesses-for-angular/" rel="noopener noreferrer"&gt;second post&lt;/a&gt;, I looked at the apps and harnesses around those models: Codex, Claude desktop app, Cursor, Antigravity, VS Code, WebStorm, and a few more. In the &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;third post&lt;/a&gt;, I focused on money: subscriptions, API costs, enterprise plans, token usage, reasoning levels, and cost control. I originally planned to squeeze all of this into that costs post, but it quickly became too much for one article, so both topics now get their own. This fourth part is about the less comfortable question behind all of that:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do we actually share when we use agentic coding tools?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where pricing, privacy, enterprise rules, EU regulations, and the very common sentence "We are not allowed to use AI here" all meet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is our code safe?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before I dive in, the usual disclaimer: this is not legal advice, not procurement advice, and not a replacement for your company's security review by an expert. It is my practical view as an &lt;em&gt;Angular&lt;/em&gt; builder and coach who cares about using agentic coding professionally without pretending that data protection is somebody else's problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: Do Not Ban AI, Approve Safe Paths
&lt;/h2&gt;

&lt;p&gt;If you only have thirty seconds, my practical view is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;personal subscriptions are great for learning, but not automatically fine for proprietary code&lt;/li&gt;
&lt;li&gt;"not used for training" does not mean "not retained"&lt;/li&gt;
&lt;li&gt;secrets, production data, customer data, and regulated personal data need clear rules&lt;/li&gt;
&lt;li&gt;agents don't just read code, they run commands and can be tricked into leaking it, so limit them&lt;/li&gt;
&lt;li&gt;companies should approve a few safe tools instead of forcing unofficial workarounds&lt;/li&gt;
&lt;li&gt;every generated &lt;strong&gt;diff still needs human review&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  This Is Not a Legal Guide
&lt;/h2&gt;

&lt;p&gt;Same disclaimer as in the previous posts: this is not a scientific benchmark and not a universal buying guide. It is my current opinion as of June 2026, based on my own work with &lt;em&gt;Angular&lt;/em&gt; projects, workshops, experiments, and too much time spent reading vendor documentation.&lt;/p&gt;

&lt;p&gt;And these details change fast. Vendors change plans, defaults, retention settings, enterprise controls, and subprocessors, sometimes within weeks. While I was writing this series, 3 new models got released (Gemini 3.5 Flash, Composer 2.5 and Opus 4.8). The next model will be out next week I guess (we're waiting for GPT 5.6, right?). Google is folding &lt;a href="https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/" rel="noopener noreferrer"&gt;Gemini CLI and Gemini Code Assist for individual users into Antigravity CLI&lt;/a&gt;, while enterprise access remains unchanged. So please check the official pages again before you decide anything for real, and read this as a practical field report.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do We Actually Share?
&lt;/h2&gt;

&lt;p&gt;This was the part where I had to do the most research because it is easy to have a strong opinion here without knowing the details. When you use an AI coding agent, you might share much more than the prompt in the chat box.&lt;/p&gt;

&lt;p&gt;Depending on the tool and settings, the provider or tool vendor may receive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your prompt&lt;/li&gt;
&lt;li&gt;selected code snippets&lt;/li&gt;
&lt;li&gt;open and neighboring files&lt;/li&gt;
&lt;li&gt;search results from your repository&lt;/li&gt;
&lt;li&gt;diffs&lt;/li&gt;
&lt;li&gt;terminal commands and outputs&lt;/li&gt;
&lt;li&gt;stack traces&lt;/li&gt;
&lt;li&gt;package names and dependency versions&lt;/li&gt;
&lt;li&gt;file paths&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;browser state&lt;/li&gt;
&lt;li&gt;issue, pull request, and review text&lt;/li&gt;
&lt;li&gt;telemetry about accepted suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not automatically mean that the provider trains on all of that. Training is a separate question. But AI coding is still a real data-processing workflow.&lt;/p&gt;

&lt;p&gt;For an &lt;em&gt;Angular&lt;/em&gt; project, this can include product logic, internal architecture, API names, feature flags, business rules, test fixtures, local logs, screenshots, and sometimes personal data.&lt;/p&gt;

&lt;p&gt;So the basic rule is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not send secrets, production data, customer data, or regulated personal data to AI tools unless your legal and technical setup explicitly allows it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents can see terminal output and files. If your &lt;code&gt;.env&lt;/code&gt; files, logs, database dumps, screenshots, or test fixtures contain sensitive data, the agent might see them too – researchers have shown that Codex, Claude Code, and Cursor &lt;a href="https://codex.danielvaughan.com/2026/05/10/codex-cli-secrets-defence-env-leakage-agent-vault-runtime-injection/" rel="noopener noreferrer"&gt;walk the working directory and can read &lt;code&gt;.env&lt;/code&gt; files&lt;/a&gt; that &lt;code&gt;.gitignore&lt;/code&gt; would normally keep out of sight.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Than Reading: Breach and Industrial Espionage
&lt;/h2&gt;

&lt;p&gt;Here is the part that worries me more than training. These tools are not passive chat boxes. They &lt;strong&gt;run commands&lt;/strong&gt;, hit the &lt;strong&gt;network&lt;/strong&gt;, and call &lt;strong&gt;MCP servers&lt;/strong&gt;, plugins, and connectors. Codex documents an &lt;a href="https://developers.openai.com/codex/concepts/sandboxing" rel="noopener noreferrer"&gt;OS-level sandbox&lt;/a&gt; with &lt;a href="https://developers.openai.com/codex/agent-approvals-security" rel="noopener noreferrer"&gt;approval modes&lt;/a&gt;, and Claude Code ships &lt;a href="https://www.anthropic.com/engineering/claude-code-sandboxing" rel="noopener noreferrer"&gt;sandboxing&lt;/a&gt; built on &lt;code&gt;bubblewrap&lt;/code&gt; and &lt;code&gt;seatbelt&lt;/code&gt;. Good engineering – but a sandbox is a fence, not a law of physics.&lt;/p&gt;

&lt;p&gt;Simon Willison calls the dangerous mix the &lt;strong&gt;"lethal trifecta"&lt;/strong&gt;: access to private data, exposure to untrusted content, and a way to send data back out. A coding agent in your repo has all three by default. And the fences leak: one team &lt;a href="https://ona.com/stories/how-claude-code-escapes-its-own-denylist-and-sandbox" rel="noopener noreferrer"&gt;showed Claude Code escaping its own denylist&lt;/a&gt; and disabling its sandbox, while researchers have documented &lt;a href="https://arxiv.org/abs/2601.17549" rel="noopener noreferrer"&gt;protocol-level holes in MCP&lt;/a&gt; that enable &lt;a href="https://www.practical-devsecops.com/mcp-security-vulnerabilities/" rel="noopener noreferrer"&gt;prompt injection and tool poisoning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So let me say the quiet part out loud. For an enterprise &lt;em&gt;Angular&lt;/em&gt; project, the real fear is not that the model learned some generic pattern. It is &lt;strong&gt;data breach and industrial espionage&lt;/strong&gt;: your architecture, business rules, unreleased features, and customer data ending up where they should not.&lt;/p&gt;

&lt;p&gt;It happens to the labs too. In April 2026, Anthropic &lt;a href="https://www.theregister.com/2026/04/01/claude_code_source_leak_privacy_nightmare/" rel="noopener noreferrer"&gt;accidentally exposed Claude Code's own source code&lt;/a&gt; through a source-map file in an npm package – a &lt;a href="https://www.netzwoche.ch/news/2026-04-02/anthropic-veroeffentlicht-versehentlich-quellcode-seines-ki-entwicklertools" rel="noopener noreferrer"&gt;human configuration error&lt;/a&gt;, not an attack, and no customer data was involved. But the lesson is uncomfortable: if the company that builds the agent can leak code by accident, your proprietary code is also just data on someone else's servers, under their rules. Add a prompt-injection vector – a poisoned dependency, a malicious issue, a booby-trapped page the agent reads – and the same agent that sees your code can be &lt;strong&gt;tricked into sending it out&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The takeaway is not panic. It is &lt;strong&gt;threat modelling&lt;/strong&gt;: where does my code live, who can reach it, how long is it kept, and what can the agent be talked into doing? That is why constraining the agent's hands – command execution, network, MCP, connectors – matters as much as the training checkbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Found in Vendor Docs
&lt;/h2&gt;

&lt;p&gt;The exact wording differs between vendors, plans, products, and regions, so I would not treat the links below as a permanent legal answer. But they show why teams have to distinguish between training, retention, product telemetry, enterprise terms, and the actual tool context an agent can see.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consumer Plans vs Business Plans
&lt;/h3&gt;

&lt;p&gt;This is the most important distinction I found. Consumer plans often have data controls, but you need to check and configure them. Business, enterprise, and API products usually have stronger defaults, especially around model training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI's&lt;/strong&gt; help page on &lt;a href="https://help.openai.com/en/articles/5722486-how-your-data-is-used-to-improve-model-performance" rel="noopener noreferrer"&gt;how data is used to improve model performance&lt;/a&gt; says individual services such as ChatGPT and Codex may use content to train models unless you opt out, but that it does not train on business products, ChatGPT Enterprise, and the API by default. The &lt;a href="https://help.openai.com/en/articles/11369540-codex-and-chatgpt-plan-usage-limits" rel="noopener noreferrer"&gt;Codex plan usage article&lt;/a&gt; says the same for Codex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anthropic's&lt;/strong&gt; &lt;a href="https://code.claude.com/docs/en/data-usage" rel="noopener noreferrer"&gt;Claude Code data usage page&lt;/a&gt; makes a similar distinction. Free, Pro, and Max users can allow data use for model improvement; for Team, Enterprise, API, third-party platforms, and Claude Gov, Anthropic says it does not train generative models on Claude Code prompts or code under commercial terms unless the customer opts in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor's&lt;/strong&gt; &lt;a href="https://cursor.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; says Privacy Mode, when enabled in settings or by a team admin, keeps code data from being stored by model providers or used for training. The &lt;a href="https://cursor.com/security" rel="noopener noreferrer"&gt;security page&lt;/a&gt; adds that it is available to anyone, enabled by default for team members, and backed by technical controls and contractual terms such as zero data retention with model providers; the &lt;a href="https://cursor.com/data-use" rel="noopener noreferrer"&gt;data-use overview&lt;/a&gt; spells out what is and isn't stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google's&lt;/strong&gt; consumer Gemini story also differs from the Google Cloud one. The &lt;a href="https://support.google.com/gemini/answer/13594961?hl=en" rel="noopener noreferrer"&gt;Gemini Apps Privacy Hub&lt;/a&gt; warns users not to enter confidential information they would not want reviewed or used to improve services. In contrast, the &lt;a href="https://docs.cloud.google.com/gemini/docs/discover/data-governance" rel="noopener noreferrer"&gt;Gemini for Google Cloud data governance docs&lt;/a&gt; say it does not use prompts or responses to train models, and the &lt;a href="https://docs.cloud.google.com/gemini/docs/codeassist/security-privacy-compliance" rel="noopener noreferrer"&gt;Gemini Code Assist Standard/Enterprise security docs&lt;/a&gt; describe coding context as Customer Data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt; just made this split concrete. In March 2026, GitHub changed its defaults so interaction data from Free, Pro, and Pro+ users can train its models unless you opt out, while Business and Enterprise stay excluded. &lt;a href="https://www.helpnetsecurity.com/2026/03/26/github-copilot-data-privacy-policy-update/" rel="noopener noreferrer"&gt;Independent reporting&lt;/a&gt; spelled out what that means for regulated industries, and the &lt;a href="https://copilot.github.trust.page/" rel="noopener noreferrer"&gt;Copilot Trust Center&lt;/a&gt; holds the current details. Same lesson, fresh example.&lt;/p&gt;

&lt;p&gt;So my practical conclusion is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A personal €20 subscription is great for learning and experimentation, but it is not automatically the right setup for proprietary enterprise code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maybe it is allowed in your company after opt-out and review. Maybe it is not. But you should not guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Training Is Not the Same as No Retention
&lt;/h3&gt;

&lt;p&gt;Another thing that is easy to mix up: &lt;strong&gt;"not used for training" does not mean "not retained"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Providers may still retain data for abuse monitoring, security, debugging, billing, legal obligations, or product functionality. Some enterprise plans allow custom retention, some API setups offer &lt;a href="https://openai.com/enterprise-privacy/" rel="noopener noreferrer"&gt;zero data retention&lt;/a&gt;, and some consumer products keep conversation history unless you delete it or use temporary modes.&lt;/p&gt;

&lt;p&gt;For example, Anthropic's &lt;a href="https://privacy.claude.com/en/articles/10023548-how-long-do-you-store-my-data" rel="noopener noreferrer"&gt;privacy center&lt;/a&gt; says consumer users who do not allow data use for model improvement have 30-day retention, while those who do have 5-year retention.&lt;/p&gt;

&lt;p&gt;OpenAI says individual users can opt out of training, and Temporary Chat will not appear in history, create memories, or be used to train models, though feedback may still be used.&lt;/p&gt;

&lt;p&gt;Google Cloud says Gemini Code Assist Standard and Enterprise are stateless Google Cloud services and do not store prompts and responses in Google Cloud unless you configure logging.&lt;/p&gt;

&lt;p&gt;For a serious company rollout, I would ask every vendor boring but important questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is our code used for training by default?&lt;/li&gt;
&lt;li&gt;Can we opt out organization-wide?&lt;/li&gt;
&lt;li&gt;What is retained, where, and for how long?&lt;/li&gt;
&lt;li&gt;Can we configure retention or use zero data retention?&lt;/li&gt;
&lt;li&gt;Do you offer EU or regional processing?&lt;/li&gt;
&lt;li&gt;Which subprocessors are involved?&lt;/li&gt;
&lt;li&gt;Are human reviewers involved?&lt;/li&gt;
&lt;li&gt;How are prompts, responses, diffs, screenshots, terminal outputs, and telemetry treated?&lt;/li&gt;
&lt;li&gt;Can admins disable connectors, browser access, MCP servers, cloud agents, or external network access?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a vendor cannot answer these questions clearly, I would not roll it out broadly in a regulated company.&lt;/p&gt;

&lt;h2&gt;
  
  
  EU and Regulatory Considerations
&lt;/h2&gt;

&lt;p&gt;Since I work in Europe, I also care about the EU side. For many companies, GDPR is the main topic; depending on the use case, works councils, vendor management, data residency, contractual confidentiality, and the EU AI Act can also become relevant.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai" rel="noopener noreferrer"&gt;EU AI Act&lt;/a&gt; entered into force in 2024 and applies gradually. The European Commission's &lt;a href="https://ai-act-service-desk.ec.europa.eu/en/ai-act/timeline/timeline-implementation-eu-ai-act" rel="noopener noreferrer"&gt;implementation timeline&lt;/a&gt; shows obligations for general-purpose AI models started in 2025, and the Commission's enforcement powers start August 2, 2026 – not far away. For a normal &lt;em&gt;Angular&lt;/em&gt; developer using a coding assistant, &lt;strong&gt;GDPR and confidentiality are probably more immediate than the AI Act&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the DACH region, the practical questions are usually a data processing agreement (Auftragsverarbeitung) and EU data residency – German write-ups like this &lt;a href="https://www.proliance.ai/blog/ki-tools-dsgvo-vergleich-chatgpt-copilot-gemini" rel="noopener noreferrer"&gt;DSGVO comparison of ChatGPT, Copilot, and Gemini&lt;/a&gt; and this &lt;a href="https://compound.law/de-DE/tools/claude-code-dsgvo/" rel="noopener noreferrer"&gt;Claude Code AVV walkthrough&lt;/a&gt; are a decent starting point. But if your company builds AI features into products, uses agents in regulated workflows, or lets AI systems influence decisions, the AI Act becomes much more relevant.&lt;/p&gt;

&lt;p&gt;For coding agents inside a company, I would keep the policy practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;classify which repositories may be used with which AI tools&lt;/li&gt;
&lt;li&gt;ban secrets and production data in AI context&lt;/li&gt;
&lt;li&gt;require approved business, team, enterprise, or API terms for proprietary code&lt;/li&gt;
&lt;li&gt;document approved vendors and subprocessors&lt;/li&gt;
&lt;li&gt;require human review for generated code&lt;/li&gt;
&lt;li&gt;educate developers on prompt privacy and tool permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal should not be bureaucracy, we have got enough of that.&lt;br&gt;
The goal should be to &lt;strong&gt;make AI usage possible&lt;/strong&gt; without pushing developers into unofficial workarounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Green, Yellow, Red
&lt;/h2&gt;

&lt;p&gt;If I had to explain the policy to developers, I would use a very simple traffic-light model.&lt;br&gt;
Not because it covers every legal edge case, but because people actually remember it.&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%2F2onhlaif5hmv1gb2gwl9.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%2F2onhlaif5hmv1gb2gwl9.png" alt="A Simple Traffic-light Rule for Agentic Coding Tools" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most real enterprise &lt;em&gt;Angular&lt;/em&gt; work lives in &lt;strong&gt;the yellow zone&lt;/strong&gt;. That is why a blanket ban is too blunt, but "just use whatever" is too naive.&lt;/p&gt;

&lt;h2&gt;
  
  
  "We Are Not Allowed to Use AI"
&lt;/h2&gt;

&lt;p&gt;This is the part where I will be a bit opinionated.&lt;/p&gt;

&lt;p&gt;I hear "we are not allowed to use AI" more and more often. Sometimes that is justified: if a company has no vendor review, no DPA, no data policy, no security story, and developers paste sensitive code into random tools, somebody should stop that. But a blanket "no AI" policy is not a serious long-term strategy. It has a hidden cost: the company becomes slower while competitors learn to use these tools safely. Long-term, I don't think that is survivable.&lt;/p&gt;

&lt;p&gt;I believe professional software teams &lt;strong&gt;need agentic coding to stay competitive&lt;/strong&gt;. Not because AI magically replaces good engineers. It does not. But because the baseline speed of software work is changing.&lt;/p&gt;

&lt;p&gt;If one team can modernize code, write tests, review changes, and explore refactorings faster, the other team cannot keep working exactly as before and expect no consequences.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Angular&lt;/em&gt; teams this is especially relevant. Enterprise &lt;em&gt;Angular&lt;/em&gt; projects often have large, complex codebases, old RxJS flows, inconsistent test coverage, slow migrations, and a lot of technical debt – a lot of boring (I personally love it!) but important modernization work. Agentic coding helps with exactly that – if we keep strong human-in-the-loop review and a sophisticated &lt;a href="https://github.com/L-X-T/ng-b357/blob/main/style-guide/style-guide.md" rel="noopener noreferrer"&gt;&lt;em&gt;Angular&lt;/em&gt; Coding Style Guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I would not argue for "let everybody use everything". I would argue for this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not ban AI. Approve a small number of safe paths and teach people to use them professionally.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the better security posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Company Policy
&lt;/h2&gt;

&lt;p&gt;If I had to write a first version of an AI coding policy for an &lt;em&gt;Angular&lt;/em&gt; company, I would keep it short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use approved tools only (might have to approve them first, a lot of work, but AI can help 😏).&lt;/li&gt;
&lt;li&gt;Use business, team, enterprise, or approved API terms for proprietary code.&lt;/li&gt;
&lt;li&gt;Use personal consumer accounts only for learning, public code, or explicitly approved use cases.&lt;/li&gt;
&lt;li&gt;Do not send secrets, credentials, production data, customer data, or sensitive personal data.&lt;/li&gt;
&lt;li&gt;Human review of every generated diff.&lt;/li&gt;
&lt;li&gt;Treat AI-generated tests as useful, but not as proof that the implementation is correct.&lt;/li&gt;
&lt;li&gt;Control command execution, browser, network, MCP, plugin, and connector access.&lt;/li&gt;
&lt;li&gt;Track cost and usage during rollout.&lt;/li&gt;
&lt;li&gt;Review the policy every quarter because the tools change too quickly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is not perfect, but it is better than either "everything is forbidden" or "everybody can use whatever they want".&lt;/p&gt;

&lt;h2&gt;
  
  
  My Personal Verdict
&lt;/h2&gt;

&lt;p&gt;For individual developers, my recommendation is simple: use personal subscriptions for learning, experiments, public code, and approved professional work. But do not assume the same subscription is automatically fine for proprietary enterprise code.&lt;/p&gt;

&lt;p&gt;For companies, I would not start with a blanket ban. I would start with the safest option developers will actually use: one or two approved business/enterprise tools, a clear data policy, and a serious pilot.&lt;/p&gt;

&lt;p&gt;For policy work, I would not try to predict every possible AI use case. I would define a few safe paths, educate developers, and review the setup every quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Engineering Workshop
&lt;/h2&gt;

&lt;p&gt;This is also why I don't think about privacy and policy in isolation: the model, the app, my &lt;em&gt;Angular&lt;/em&gt; Guardrails, my &lt;em&gt;Angular&lt;/em&gt; Coding Style Guide, the &lt;em&gt;Angular&lt;/em&gt; Skills, and the review workflow belong together.&lt;/p&gt;

&lt;p&gt;If you want to learn how to use agentic coding safely with proprietary &lt;em&gt;Angular&lt;/em&gt; code – the data flow, the guardrails, and a policy your developers will actually follow – make sure to join our &lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;, both in English and German.&lt;/p&gt;

&lt;p&gt;In this workshop, advanced &lt;em&gt;Angular&lt;/em&gt; developers learn how to move from vibe coding to traceable Agentic Engineering workflows: AI-ready project setup, guardrails, spec-first and plan-first workflows, UX and component prototyping, code review, testing, and brownfield refactoring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;&lt;/a&gt; – 2 days, remote&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;AI coding tools are not just chat boxes. They can read files, search repositories, inspect diffs, run commands, see terminal output, connect to other systems and resolve nasty merge conflicts. That is what makes them useful, but it is also why privacy and governance matter.&lt;/p&gt;

&lt;p&gt;For individual developers, the practical rule is simple: do not send secrets, production data, customer data, or regulated personal data unless the setup explicitly allows it. For companies, the practical rule is also simple: &lt;strong&gt;do not only say no&lt;/strong&gt;. Approve a few safe paths, define clear boundaries, and make the official workflow better than the unofficial workaround.&lt;/p&gt;

&lt;p&gt;Because the teams that learn to use agentic coding professionally will not only write code faster.&lt;br&gt;
They modernize faster, test faster, review faster, and learn faster – while the teams that refuse to adapt slowly fall behind, until falling behind turns into going broke.&lt;/p&gt;

&lt;p&gt;In the next and final post of this series, I'll pull all the threads together – models, harnesses, costs, and privacy – into the agentic coding setup I would actually choose today.&lt;/p&gt;

&lt;p&gt;Thank you for reading 🙏 this blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. For feedback, remarks or questions, please reach out to me ❤️&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
      <category>angular</category>
    </item>
    <item>
      <title>Agentic Engineering: What Does AI Coding Really Cost?</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Thu, 04 Jun 2026 12:12:26 +0000</pubDate>
      <link>https://dev.to/lxt/agentic-engineering-what-does-ai-coding-really-cost-4kjk</link>
      <guid>https://dev.to/lxt/agentic-engineering-what-does-ai-coding-really-cost-4kjk</guid>
      <description>&lt;p&gt;In my &lt;a href="https://www.angulararchitects.io/blog/best-llms-for-angular/" rel="noopener noreferrer"&gt;first post&lt;/a&gt; of this five-part series, I wrote about the LLMs I currently like to use for &lt;em&gt;Angular&lt;/em&gt; development. In the &lt;a href="https://www.angulararchitects.io/blog/ai-apps-harnesses-for-angular/" rel="noopener noreferrer"&gt;second post&lt;/a&gt;, I looked at the apps and harnesses around those models: Codex, Claude desktop app, Cursor, Antigravity, VS Code, WebStorm, and a few more. This third part is about the annoying but important topic: money.&lt;/p&gt;

&lt;p&gt;Actually, money is only one part of the story. The other part is what data we share with these tools and which setups companies can actually use. I first had all of that in this post too, but it became too much for one article. So this part stays focused on costs, and I will cover the data and policy side in the &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;next post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And yeah, spoiler: if one of the subsidized subscriptions (or in my case all four of them) works for your setup, that is by far &lt;strong&gt;the best deal&lt;/strong&gt; you can currently get. Before I dive in, let me be clear about one thing: I am not trying to sell you anything here, except, well, my brand new &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;Agentic Engineering Workshop&lt;/a&gt; 😅. So this post is primarily about informing you about the options that actually exist, so you can make your own decision.&lt;/p&gt;

&lt;p&gt;One thought runs through this whole post, so let me say it up front: what matters in the end is not the price per token. It is the &lt;strong&gt;cost per accepted, reviewed, merged change&lt;/strong&gt;. More on that later.&lt;/p&gt;

&lt;p&gt;There is also a bigger reason I keep writing about this. In my opinion, too many European teams are still too slow to adopt agentic coding seriously – and that gap will only get bigger. So please help me spread the word, and share these posts with your friends and colleagues!&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Still Not a Benchmark
&lt;/h2&gt;

&lt;p&gt;Same disclaimer as in the previous two posts: this is not a scientific benchmark and not a universal buying guide. It is my current opinion as of June 1, 2026, based on my own work with &lt;em&gt;Angular&lt;/em&gt; projects, workshops, experiments, and a lot of time spent playing around in Codex and the Claude desktop app.&lt;/p&gt;

&lt;p&gt;I also spend some time in Cursor because it is a serious candidate – even more so since &lt;a href="https://apnews.com/article/582e7606e695320a299e4902dbb2704f" rel="noopener noreferrer"&gt;Elon is going to buy Cursor&lt;/a&gt; later this year (I bet he will), and I want to keep checking where it is going. Antigravity, on the other hand, is more of a tool I test from time to time. I don't use it seriously for my daily &lt;em&gt;Angular&lt;/em&gt; work yet, because it still does not feel mature enough compared with Codex, Claude desktop app, and lately also Cursor.&lt;/p&gt;

&lt;p&gt;The subscription prices themselves have been pretty stable so far. What changes more often is what you actually get: model access, &lt;strong&gt;included usage&lt;/strong&gt;, rate limits, fast modes, and sometimes the hidden routing behind the scenes. For API and enterprise setups, longer agent runs can also change the real cost directly.&lt;/p&gt;

&lt;p&gt;So if you read this in a few months, check the linked pricing pages again. The included usage and enterprise/API details might have changed. One more note on the numbers: the providers list their prices in US dollars, so the euro figures in this post are approximate, not the exact amounts on your invoice. But the general shape of the cost problem will probably stay the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;subsidized &lt;strong&gt;subscriptions are amazing value&lt;/strong&gt; when they fit your setup&lt;/li&gt;
&lt;li&gt;API pricing is much more transparent, but can get &lt;strong&gt;expensive quickly&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;enterprise plans cost more, but also solve a different problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So please read this as a practical field report, not as procurement advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: Start With €40 or €60, Upgrade Only Where You Hit Limits
&lt;/h2&gt;

&lt;p&gt;If you are an individual developer, freelancer, trainer, consultant, or working in a setup where these tools are available, I would not start with one tool. If you have no access yet, I would buy at least the €20 subscription from OpenAI and the €20 subscription from Anthropic. That is €40/month, and for me that is currently the most useful starting point.&lt;/p&gt;

&lt;p&gt;If you can afford another €20, I would also add Cursor for a month. Then you are at €60/month and you can compare not only the models, but also the apps around them: Codex, Claude desktop app, and Cursor. That is much more useful than reading another benchmark table.&lt;/p&gt;

&lt;p&gt;The current pattern is roughly this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;€40/month&lt;/strong&gt;: OpenAI Plus and Anthropic Pro, the minimum setup I would recommend for serious experiments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;€60/month&lt;/strong&gt;: add Cursor Pro if you want to compare a third strong app workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;+€80/month per provider&lt;/strong&gt;: upgrade OpenAI or Anthropic from the €20 tier to the €100 tier when that specific tool becomes your bottleneck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;+€100/month more after that&lt;/strong&gt;: move the same provider from the €100 tier to the €200 tier when you really need the 20x-style heavy-user tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor upgrades&lt;/strong&gt;: separate ladder, with €20 Pro, €60 Pro+, €200 Ultra, and €40/user/month Teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise&lt;/strong&gt;: different billing, contracts, usage pools, overages, and usually much more expensive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are new to agentic coding, don't worry about the higher tiers on day one. Start with the base subscriptions, learn the apps, and upgrade only where you actually hit limits. That is what I did too.&lt;/p&gt;

&lt;p&gt;Today my setup is heavier: OpenAI and Anthropic on the €100 tier, Cursor and Google both on the €20 tier. I don't need all of them every day, but I want to compare them quickly because this space changes so fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Subscriptions Are Such a Good Deal
&lt;/h2&gt;

&lt;p&gt;These subscriptions are not just API pricing with a nicer UI. They are subsidized product bundles, and agentic coding involves a lot of hidden work: file reads, searches, tool calls, tests, terminal output, diffs, edits, and context compaction. At raw API prices, you would notice that quickly.&lt;/p&gt;

&lt;p&gt;That is why subscriptions are so attractive. The providers want developers in their ecosystem, and right now we benefit from that. But the catch is important: these plans are usually meant for humans using the product, not as a cheap backend for your company, CI pipeline, or SaaS product. In larger companies, personal consumer subscriptions also do not match procurement, billing, reporting, and cost control.&lt;/p&gt;

&lt;p&gt;So the real first question is not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which model is cheapest per token?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The real first cost question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we use the subsidized app subscription, or do we need a business, enterprise, or API setup?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That one question can easily change the cost calculation by a &lt;strong&gt;factor of 10&lt;/strong&gt; or more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The €20 Tier Is for Getting Started
&lt;/h2&gt;

&lt;p&gt;For me, the €20 tier is not only about "cheap access to a model". It is the entry ticket into the whole product around the model. That is why I would not buy only one subscription if I had no access yet. I would buy OpenAI and Anthropic first, and maybe Cursor too.&lt;/p&gt;

&lt;p&gt;The important comparison is not only GPT versus Claude versus Composer or Gemini. The important comparison is Codex versus Claude desktop app versus Cursor. These are the super apps where the real workflow happens: repository search, file editing, tool calls, terminal output, diffs, review, cloud tasks, local tasks, and all the little product decisions that make an agent useful or annoying.&lt;/p&gt;

&lt;p&gt;So my recommendation is simple: spend €40 or €60 for one month and run the &lt;strong&gt;same real &lt;em&gt;Angular&lt;/em&gt; tasks through the tools&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;migrate a component to signal-based &lt;code&gt;input()&lt;/code&gt; and &lt;code&gt;output()&lt;/code&gt;, and rewrite a template from &lt;code&gt;*ngIf&lt;/code&gt; and &lt;code&gt;*ngFor&lt;/code&gt; to &lt;code&gt;@if&lt;/code&gt; and &lt;code&gt;@for&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add tests for a service with a few dependencies&lt;/li&gt;
&lt;li&gt;ask for a code review of a real pull request&lt;/li&gt;
&lt;li&gt;try a Git workflow like rebasing one branch onto another and resolving a pile of merge conflicts&lt;/li&gt;
&lt;li&gt;describe a bug by explaining the current behavior and the desired behavior, and ask the agent to find and fix it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then look at the diffs, the review experience, the terminal usage, the verification, and how much steering you had to do. After two or three evenings, you will usually know much more than any pricing table can tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current Pricing Picture
&lt;/h2&gt;

&lt;p&gt;Let's look at that pricing table anyway, but only briefly. Again, please check the official pages before you make a real decision, because these numbers are moving targets.&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%2F64uf6j76ppbu2pq2vxqc.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%2F64uf6j76ppbu2pq2vxqc.png" alt="Costs of Coding Agent Subscription" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing special to see here. Roughly all three Frontier Labs – OpenAI, Anthropic, and Cursor (SpaceXAI) – have the same pricing, with the minor exception of Cursor offering you the 3x plan for €60.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAI / Codex
&lt;/h3&gt;

&lt;p&gt;For Codex, check the &lt;a href="https://developers.openai.com/codex/pricing/" rel="noopener noreferrer"&gt;Codex pricing page&lt;/a&gt; and &lt;a href="https://chatgpt.com/pricing/" rel="noopener noreferrer"&gt;ChatGPT pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic / Claude
&lt;/h3&gt;

&lt;p&gt;For Claude Code, check Anthropic's &lt;a href="https://claude.com/pricing" rel="noopener noreferrer"&gt;Claude pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cursor
&lt;/h3&gt;

&lt;p&gt;For Cursor, check the &lt;a href="https://cursor.com/pricing" rel="noopener noreferrer"&gt;Cursor pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google / Gemini / Antigravity
&lt;/h3&gt;

&lt;p&gt;For Google, check the &lt;a href="https://gemini.google/us/subscriptions/" rel="noopener noreferrer"&gt;Gemini subscriptions page&lt;/a&gt; and the &lt;a href="https://blog.google/products-and-platforms/products/google-one/google-ai-subscriptions/" rel="noopener noreferrer"&gt;Google AI subscription update&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copilot
&lt;/h3&gt;

&lt;p&gt;Meh, I would not build a 2026 setup around Copilot anymore. And the pricing story is getting weaker too: GitHub has &lt;a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/" rel="noopener noreferrer"&gt;moved Copilot to usage-based billing&lt;/a&gt; today (June 1, 2026), replacing premium request units with AI Credits. So Microsoft's heavy subsidizing of frontier-model usage inside Copilot is over. On top of that, the frontier LLMs are being limited more and more in Copilot. But the bigger point is simple: Copilot is just &lt;strong&gt;not the best&lt;/strong&gt; harness for agentic coding. If you're forced to use it in your company, try starting a revolution. In the end, you might just be securing your employer's survival. Hopefully this post series can support you with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Is a Different Game
&lt;/h2&gt;

&lt;p&gt;It is easy to look at a €20 subscription and ask why a company should pay so much more. I get that reaction, but enterprise is not "more Plus". It is a different cost model: seats, pooled usage, overages, central billing, usage reports, admin controls, support, and predictable invoices.&lt;/p&gt;

&lt;p&gt;The real question is what happens when 50 developers use the tool every day. Tight limits can turn cheap plans into overages, waiting time, retrying, and cleanup.&lt;/p&gt;

&lt;p&gt;So I would start with a small pilot. Give a few strong developers two paid setups for four weeks, use them on real &lt;em&gt;Angular&lt;/em&gt; work, and measure cost, accepted changes, review time, PR cycle time, and limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Usage: Paying the Real Cost
&lt;/h2&gt;

&lt;p&gt;Subscriptions and enterprise plans hide cost. API usage does not. The official &lt;a href="https://developers.openai.com/api/docs/pricing" rel="noopener noreferrer"&gt;OpenAI API pricing page&lt;/a&gt; and &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Claude API pricing docs&lt;/a&gt; currently list USD prices. Converted roughly for comparison, &lt;a href="https://openai.com/index/introducing-gpt-5-5/" rel="noopener noreferrer"&gt;GPT 5.5&lt;/a&gt; is about €5 per million input tokens and €30 per million output tokens, while &lt;a href="https://www.anthropic.com/news/claude-opus-4-8" rel="noopener noreferrer"&gt;Claude Opus 4.8&lt;/a&gt; is about €5 input and €25 output.&lt;/p&gt;

&lt;p&gt;Per million tokens that sounds manageable, and output tokens are usually much more expensive than input tokens.&lt;/p&gt;

&lt;p&gt;Here is the current API price snapshot I would use for agentic coding discussions. Most official pricing pages still quote USD, so treat this as a practical comparison and check billing currency, VAT, and enterprise discounts before you make a procurement decision.&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%2Fzox6xiuiue1k1ivpqrol.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%2Fzox6xiuiue1k1ivpqrol.png" alt="LLM API Model Pricing" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All prices are per million tokens, sourced from each provider's API pricing. The table is deliberately simple: no batch discount, no extra tool-call fees, no VAT, no currency conversion, no enterprise discount.&lt;/p&gt;

&lt;p&gt;For IDE-heavy teams, BYOK (bring your own key) can be interesting, no matter whether you live in &lt;a href="https://www.jetbrains.com/webstorm/" rel="noopener noreferrer"&gt;WebStorm&lt;/a&gt; or &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;. On the JetBrains side, the &lt;a href="https://www.jetbrains.com/help/ai-assistant/licensing-and-subscriptions.html" rel="noopener noreferrer"&gt;JetBrains AI plans&lt;/a&gt; and &lt;a href="https://junie.jetbrains.com/docs/byok.html" rel="noopener noreferrer"&gt;Junie BYOK docs&lt;/a&gt; allow it, and most VS Code AI extensions offer the same idea: keep the IDE, connect provider keys, pay the provider.&lt;/p&gt;

&lt;p&gt;But the catch is how many tokens an agent actually moves per task – and that is the real cost driver I look at next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Usage Is the Real Cost Driver
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them" rel="noopener noreferrer"&gt;OpenAI's help page on tokens&lt;/a&gt; says one token is roughly four characters of English text. For a coding agent, 200,000 input tokens are not some crazy edge case. Repository instructions, tool schemas, file excerpts, diffs, terminal output, and chat history pile up fast. &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing#tool-use-pricing" rel="noopener noreferrer"&gt;Anthropic's pricing docs&lt;/a&gt; say the quiet part too: tool names, schemas, calls, and results are billed.&lt;/p&gt;

&lt;p&gt;At current API prices, one turn with 200,000 input tokens and 20,000 output tokens costs roughly €1.60 on GPT 5.5 and €1.50 on Claude Opus 4.8. Fine once. Annoying after 20 turns. Expensive if the agent starts doing laps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching" rel="noopener noreferrer"&gt;Prompt caching&lt;/a&gt; helps, but it does not make output cheap and it definitely does not fix a messy workflow. So I would not worship token price. A more expensive model can still be cheaper if it gets the work done in fewer turns.&lt;/p&gt;

&lt;p&gt;So the number that matters is not cost per token. It is &lt;strong&gt;cost per accepted, reviewed, merged change&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%2F462tz89t9of0x8qalx56.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%2F462tz89t9of0x8qalx56.png" alt="AI Cost Comparison Chart" width="799" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DeepSWE benchmark is the one I currently trust the most. Higher percentages mean stronger benchmark results; further right means better cost efficiency. Sadly it's missing Composer due to the lack of a public API.&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%2Feyd3a3ondujau2o8l4p4.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%2Feyd3a3ondujau2o8l4p4.png" alt="LLM Costs Per Task" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This second chart includes Composer – and here it is the most efficient model by far, the best ROI in this comparison. Definitely an option to watch for API usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low, Medium, High, Extra
&lt;/h2&gt;

&lt;p&gt;If token volume is the real cost, the reasoning level is the first dial that changes it. Most tools now have some reasoning setting: low, medium, high, extra, max, and so on. The rule is simple: use the cheapest level that reliably solves the task.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Angular&lt;/em&gt; work: low for tiny edits, medium for normal implementation, high for tricky bugs, component creation and refactorings, and extra/Pro/Max when a bad result would cost much more than the model usage.&lt;/p&gt;

&lt;p&gt;In practice, I often just use &lt;strong&gt;High&lt;/strong&gt; as my default (for GPT and Opus), because it is usually good enough and I don't want to waste time tuning. I know I will get hate for this, but the truth is that my time is precious and I don't care as much since I can use the subsidized subscription anyway. And I still have the option to upgrade my sub if necessary 😏&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast Mode
&lt;/h2&gt;

&lt;p&gt;Reasoning level is one dial; fast mode is the other. Fast mode is interesting because speed matters. If an agent is too slow, I lose focus. But fast mode is also a cost feature. In Codex, &lt;a href="https://developers.openai.com/codex/pricing/" rel="noopener noreferrer"&gt;speed configurations increase credit consumption&lt;/a&gt;. In the Claude API, &lt;a href="https://platform.claude.com/docs/en/build-with-claude/fast-mode" rel="noopener noreferrer"&gt;Fast mode&lt;/a&gt; gives faster Opus responses at a premium.&lt;/p&gt;

&lt;p&gt;So I would definitely try it, experiment with it, and if your time is really precious, just use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Would Control Costs in a Team
&lt;/h2&gt;

&lt;p&gt;Cost control is mostly &lt;strong&gt;workflow control&lt;/strong&gt;. I would define simple rules from the beginning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start with medium reasoning for normal implementation work&lt;/li&gt;
&lt;li&gt;use high/extra only when the task deserves it&lt;/li&gt;
&lt;li&gt;keep tasks focused&lt;/li&gt;
&lt;li&gt;start a fresh thread when the context becomes messy&lt;/li&gt;
&lt;li&gt;keep project instructions short and useful&lt;/li&gt;
&lt;li&gt;watch terminal output size&lt;/li&gt;
&lt;li&gt;keep humans responsible for the final review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not only about saving money. Agents are better when the task is clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Engineering Workshop
&lt;/h2&gt;

&lt;p&gt;This is also why I don't think about costs in isolation: the model, the app, my &lt;em&gt;Angular&lt;/em&gt; Guardrails, my &lt;em&gt;Angular&lt;/em&gt; Coding Style Guide, the &lt;em&gt;Angular&lt;/em&gt; Skills, and the review workflow belong together.&lt;/p&gt;

&lt;p&gt;If you want to learn how to keep AI coding costs under control and optimize for cost per accepted, reviewed, merged change instead of price per token, make sure to join our &lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;, both in English and German.&lt;/p&gt;

&lt;p&gt;In this workshop, advanced &lt;em&gt;Angular&lt;/em&gt; developers learn how to move from vibe coding to traceable Agentic Engineering workflows: AI-ready project setup, guardrails, spec-first and plan-first workflows, UX and component prototyping, code review, testing, and brownfield refactoring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;&lt;/a&gt; – 2 days, remote&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The cheapest serious setup today is still a &lt;strong&gt;subsidized individual subscription&lt;/strong&gt;. If you have no access yet, I would start with OpenAI and Anthropic for roughly €40/month, and maybe add Cursor for another €20 for one month. That gives you Codex, Claude desktop app, and Cursor on real &lt;em&gt;Angular&lt;/em&gt; tasks, which is much more useful than reading another benchmark table.&lt;/p&gt;

&lt;p&gt;Then keep what actually changes your daily workflow. If you hit limits, upgrade the tool that is limiting you: OpenAI or Anthropic from €20 to €100, and only then maybe to €200 for serious heavy use. For companies, the answer may be business or enterprise seats, pooled credits, API usage, or an IDE-based setup with your own provider keys – WebStorm with &lt;a href="https://www.jetbrains.com/ai/ai-assistant-features/" rel="noopener noreferrer"&gt;AI Assistant&lt;/a&gt; and Junie, or VS Code with its AI extensions.&lt;/p&gt;

&lt;p&gt;So my recommendation is not "buy tool X". Build a small, serious AI coding workflow now, measure &lt;strong&gt;cost per accepted, reviewed, merged change&lt;/strong&gt;, and keep humans responsible for quality.&lt;/p&gt;

&lt;p&gt;Teams that learn this professionally will not only write code faster. They will modernize faster, test faster, review faster, and learn faster.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;next post&lt;/a&gt;, I will look at the other half of the question: what we actually share with AI coding tools, and how I would think about privacy, retention, EU regulation, and company policy.&lt;/p&gt;

&lt;p&gt;Thank you for reading 🙏 this blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. For feedback, remarks or questions, please reach out to me ❤️&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>angular</category>
    </item>
    <item>
      <title>Agentic: Which App/Harness Is Best for Angular Development?</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Tue, 02 Jun 2026 07:14:46 +0000</pubDate>
      <link>https://dev.to/lxt/agentic-which-appharness-is-best-for-angular-development-28h9</link>
      <guid>https://dev.to/lxt/agentic-which-appharness-is-best-for-angular-development-28h9</guid>
      <description>&lt;p&gt;In my &lt;a href="https://www.angulararchitects.io/blog/best-llms-for-angular/" rel="noopener noreferrer"&gt;last post&lt;/a&gt;, published yesterday, I wrote about which LLMs I currently like to use for &lt;em&gt;Angular&lt;/em&gt; development and promised a follow-up about the apps and harnesses around them. This is that follow-up.&lt;/p&gt;

&lt;p&gt;It is the second post in this five-part series. The follow-ups look at &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;costs&lt;/a&gt; and the &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;data and privacy side&lt;/a&gt; of agentic coding, before a final post pulls everything together into my overall verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Models to Agent Harnesses
&lt;/h2&gt;

&lt;p&gt;Picking the model is only half the picture. The same &lt;a href="https://www.anthropic.com/news/claude-opus-4-7" rel="noopener noreferrer"&gt;Opus 4.7&lt;/a&gt; behaves very differently in a JetBrains chat window, in &lt;a href="https://www.anthropic.com/product/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; on the terminal, or wrapped in &lt;a href="https://cursor.com/download/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;'s agent mode. The harness decides how the model sees your code, how it edits files, how it runs tools, how much it verifies, and whether it feels like autocomplete, a helpful assistant, or a real agent. For serious &lt;em&gt;Angular&lt;/em&gt; work, that part matters &lt;strong&gt;at least as much as the underlying model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So this post is about the other half: the apps, IDE integrations, and agentic coding tools I've used over the last few months. I'll move from classic Copilot-style autocomplete to IDE agents and today's super apps, and then explain where Codex, Claude desktop app, Cursor, Antigravity, VS Code, and WebStorm currently fit (or don't) into my daily &lt;em&gt;Angular&lt;/em&gt; workflow. I also want to look a little bit at the product philosophy behind these tools, because that often explains the day-to-day experience better than a feature checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Is Not a Benchmark
&lt;/h2&gt;

&lt;p&gt;Before we go any further, one important disclaimer: this is not a scientific benchmark, not a universal ranking, and definitely not the final truth about AI coding tools. It is my &lt;strong&gt;subjective opinion&lt;/strong&gt; based on my own daily work with &lt;em&gt;Angular&lt;/em&gt; projects, workshops, code reviews, refactorings, and experiments over the last few months.&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%2Fnymccwebusxb8s7nrrki.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%2Fnymccwebusxb8s7nrrki.png" alt="Apps/Harnesses Overview" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sketch above is a very simplified overview of the different tools and how I see them in terms of their capabilities and my personal preferences. It is not meant to be an objective comparison, but rather a visual aid to understand my current opinion of these apps and harnesses.&lt;/p&gt;

&lt;p&gt;Your workflow might be different. Your team might have different constraints. Your preferred IDE, operating system, budget, company policies, and tolerance for agentic autonomy might lead to a completely different result.&lt;/p&gt;

&lt;p&gt;So please, read this post as a practical field report from my current setup, not as a neutral lab test.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: My Current Setup
&lt;/h2&gt;

&lt;p&gt;If you only have thirty seconds: my daily drivers for &lt;em&gt;Angular&lt;/em&gt; work are currently &lt;strong&gt;Codex&lt;/strong&gt; (with GPT 5.5) and the &lt;strong&gt;Claude desktop app&lt;/strong&gt; (with Opus 4.7), used roughly interchangeably. Codex has the more polished app experience, while the Claude desktop app gives me access to the Opus models I trust most for architecture, design, and larger refactorings. &lt;strong&gt;Cursor&lt;/strong&gt; with Composer 2.5 is a strong third option, especially when speed, cost, IDE integration, or cloud agents matter. &lt;strong&gt;Antigravity&lt;/strong&gt; is interesting but, in my opinion, not yet on the same level. And whenever I do real handcrafting, I still happily switch back to &lt;strong&gt;WebStorm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All of this assumes a strict human-in-the-loop workflow: &lt;strong&gt;I review every diff&lt;/strong&gt;, and I expect every line of generated code to look as if I had written it myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  My AI Coding Journey
&lt;/h2&gt;

&lt;p&gt;To explain where I ended up, I first need to show the path that got me there. I'll start with the more traditional IDE integrations and then move on to the newer agentic coding apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebStorm + GitHub Copilot
&lt;/h3&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%2Fm7744vj5gm0v0gul9bsz.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%2Fm7744vj5gm0v0gul9bsz.png" alt="WebStorm by JetBrains" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For over a decade PhpStorm (starting in my WordPress era) and later &lt;a href="https://www.jetbrains.com/webstorm/" rel="noopener noreferrer"&gt;WebStorm&lt;/a&gt; have been my main IDEs for web development. So when &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; launched, it was a natural choice to try it out in WebStorm. It was one of the first AI coding tools I used, and it had a big impact on how I thought about AI-assisted coding.&lt;/p&gt;

&lt;p&gt;I just looked up in my GitHub &lt;a href="https://github.com/account/billing/history" rel="noopener noreferrer"&gt;Billing History&lt;/a&gt; and noticed that I've been using Copilot since 2023-10-27. That's exactly 31 months ago. What a journey. For the most part of that time – let's say the first 25 months, I used GitHub Copilot in WebStorm (and &lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;), which was a great way to get started with AI-assisted coding. It felt like a natural extension of the IDE's existing autocomplete features, and it worked well for small code snippets and boilerplate.&lt;/p&gt;

&lt;p&gt;However, it also had its limitations. It often struggled with larger code contexts, complex &lt;em&gt;Angular&lt;/em&gt; patterns, and multi-file refactorings. In particular, it was usually &lt;strong&gt;a step or two behind modern &lt;em&gt;Angular&lt;/em&gt; APIs&lt;/strong&gt;: it kept suggesting &lt;code&gt;NgModule&lt;/code&gt;-based code long after standalone components had become the default, leaned on &lt;code&gt;*ngIf&lt;/code&gt; and &lt;code&gt;*ngFor&lt;/code&gt; instead of the new control flow syntax, and rarely reached for signals, &lt;code&gt;input()&lt;/code&gt; or &lt;code&gt;output()&lt;/code&gt; on its own. It was more of a helper for writing code than a partner for driving larger coding workflows. And sometimes it just was pretty annoying, suggesting irrelevant or incorrect code that I had to manually reject.&lt;/p&gt;

&lt;p&gt;In 2025, I also experimented quite a bit with Cursor. However, I still didn't like the VS Code clone too much and preferred to stay in my beloved JetBrains editors.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebStorm + AI Assistant + Junie + Opus 4.5
&lt;/h3&gt;

&lt;p&gt;Switch to November 2025 when &lt;a href="https://www.anthropic.com/news/claude-opus-4-5" rel="noopener noreferrer"&gt;Opus 4.5&lt;/a&gt; was released. People on social platforms got pretty excited and also some of my friends pushed me to try the new model. So again, I followed the easy path and I started to use the new model through AI Assistant and Junie in WebStorm.&lt;/p&gt;

&lt;p&gt;For context: &lt;a href="https://www.jetbrains.com/ai/ai-assistant-features/" rel="noopener noreferrer"&gt;JetBrains AI Assistant&lt;/a&gt; is the AI layer built directly into JetBrains IDEs like WebStorm. It gives you the typical IDE-integrated AI features: chat, code explanations, code generation, documentation help, code completion, and smaller edits in the context of the project. The same AI Assistant interface is also becoming more of a hub for different agents and providers, so depending on your setup you can use tools like Junie, Claude, Codex or even Cursor and a lot more from inside the JetBrains workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.jetbrains.com/help/ai-assistant/junie-agent.html" rel="noopener noreferrer"&gt;Junie&lt;/a&gt; is JetBrains' own more agentic coding tool. Instead of only answering questions or completing snippets, it can take a task, create a plan, edit multiple files, run commands or tests if you allow it, and iterate inside the IDE.&lt;/p&gt;

&lt;p&gt;But then, since I was using &lt;a href="https://www.anthropic.com/news/claude-opus-4-6" rel="noopener noreferrer"&gt;Claude Opus 4.6&lt;/a&gt; most of the time, I wanted to experiment with different harnesses for that model. So I came to a new workflow where I used VS Code with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Anthropic.claude-code" rel="noopener noreferrer"&gt;Claude Code extension&lt;/a&gt; for my agentic coding.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code + Claude Code + Opus 4.6
&lt;/h3&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%2F2mzeuacol0v2z31y77lw.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%2F2mzeuacol0v2z31y77lw.png" alt="VS Code by Microsoft" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few weeks after &lt;a href="https://www.anthropic.com/news/claude-opus-4-6" rel="noopener noreferrer"&gt;Opus 4.6&lt;/a&gt; was released to the public, I think it was around the end of February, I started to look more seriously for the best harness for that model. Until then, I mostly thought about the model itself: is it good enough, fast enough, and useful enough for real &lt;em&gt;Angular&lt;/em&gt; work? But the more I compared the different tools, the more obvious it became that &lt;strong&gt;the surrounding app matters a lot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;VS Code was a natural candidate for that experiment. I never loved it as much as my JetBrains IDEs, but I knew it well from the work in my &lt;a href="https://www.angulararchitects.io/trainer/alexander-thalhammer/" rel="noopener noreferrer"&gt;&lt;em&gt;Angular Architects&lt;/em&gt; workshops&lt;/a&gt;, where many participants use it as their main editor. And with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Anthropic.claude-code" rel="noopener noreferrer"&gt;Claude Code extension&lt;/a&gt; in VS Code, I had the first setup where Opus really felt &lt;strong&gt;stronger because of the harness&lt;/strong&gt; around it. Suddenly Opus could read across an entire &lt;em&gt;Angular&lt;/em&gt; feature folder, follow a signal from &lt;code&gt;computed()&lt;/code&gt; back to its source, and propose multi-file edits that respected my standalone-component setup, my routing, and even my &lt;code&gt;providedIn: 'root'&lt;/code&gt; services without me having to spoon-feed it the context.&lt;/p&gt;

&lt;p&gt;That was the moment where I started to think: OK, this is &lt;strong&gt;not only about the LLM anymore&lt;/strong&gt;. Well and that was even before I tried the Agentic coding apps or what I like to call them &lt;em&gt;super apps&lt;/em&gt; for the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond IDE: Agentic Coding Apps (Super Apps)
&lt;/h2&gt;

&lt;p&gt;After using VS Code for my agentic coding for about a month or so, I was ready to start a new journey.&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%2Fuki5mlr2uigk3n2fumls.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%2Fuki5mlr2uigk3n2fumls.png" alt="Super App Candidates" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above, you can see the four main apps I currently use for agentic coding: Claude desktop app, Codex, Cursor, and Antigravity. Oh boy, they really look the same, don't they?&lt;/p&gt;

&lt;p&gt;To be clear, I strictly follow a human-in-the-loop workflow here. That is because I care a lot about &lt;strong&gt;clean code and high-quality code&lt;/strong&gt;. Some people think that the quality of the codebase does not, or will not, matter anymore in the future. I strongly disagree with that view, although I accept it as a fair point that challenges my own position.&lt;/p&gt;

&lt;p&gt;At first glance, these tools look like different skins around the same idea. But the more I use them, the more I think they are actually making &lt;strong&gt;different bets&lt;/strong&gt;. The Claude desktop app feels terminal-first and model-first: give a strong model a lot of tool access and let it work. Codex feels app-first and verification-first: keep the interface calm, use the local environment when possible, and make the agent prove more of its work. Cursor feels IDE- and cloud-first: keep the editor close, but also make it possible to run agents somewhere else and bring the result back into the team workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude desktop app + Opus 4.7
&lt;/h3&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%2Fmk1ogau6f5gkgtsdcqxu.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%2Fmk1ogau6f5gkgtsdcqxu.png" alt="Claude desktop app by Anthropic" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is why I really care about reviewing everything these tools do, and I want every line of code to look exactly &lt;strong&gt;as if I had handcrafted it&lt;/strong&gt;. Since the Claude desktop app and the VS Code extension share the same underlying Claude Code agent harness, the app already felt familiar to me. The Claude CLI is the canonical surface; the desktop app and the VS Code/JetBrains extensions are different front-ends that drive that same harness.&lt;/p&gt;

&lt;p&gt;To be honest, I have never been much of a CLI guy (nor Linux – I prefer macOS – but I love Android), so I really prefer using a fancy desktop app. But I fully understand that this is a pure personal preference. And yeah, that's all I can say to that.&lt;/p&gt;

&lt;p&gt;So basically, the first one of these super apps that I really used on a day-to-day basis was the &lt;a href="https://www.anthropic.com/product/claude-code" rel="noopener noreferrer"&gt;Claude desktop app&lt;/a&gt;. By the way, it also offers a Co-working tab, which is supposed to be the right choice for knowledge workers, while the Code tab is the choice for us developers.&lt;/p&gt;

&lt;p&gt;The big strength of Claude Code is still the &lt;strong&gt;terminal story&lt;/strong&gt;. It met developers where many of them already live, and that is probably one reason why adoption was so fast. The downside is that this also shapes the whole product: images, rich UI, visual verification, and desktop-app polish will always feel a bit different when the canonical workflow is the CLI. I also notice that Claude Code is very willing to spend tokens if that makes the agent feel more capable, for example with subagents or larger parallel explorations. That can be great for difficult tasks, but it is something I want to keep an eye on when cost and focus matter.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Angular&lt;/em&gt; work specifically, Claude Code with Opus 4.7 is the &lt;strong&gt;harness I trust most&lt;/strong&gt; when a task spans the whole stack: introducing a new feature module, migrating an older area to standalone components and signals, or refactoring a non-trivial RxJS pipeline into a cleaner mix of signals and &lt;code&gt;toSignal()&lt;/code&gt;. It also handles the boring-but-important parts well, like keeping &lt;code&gt;app.config.ts&lt;/code&gt;, route definitions, and lazy-loaded feature routes in sync after a rename.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex + GPT 5.5
&lt;/h3&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%2Fxajo9lpcvdoxdnv6qn5n.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%2Fxajo9lpcvdoxdnv6qn5n.png" alt="Codex by OpenAI" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that is already a big difference compared with the &lt;a href="https://openai.com/codex/" rel="noopener noreferrer"&gt;Codex app&lt;/a&gt; by OpenAI, because Codex does not split these workflows into separate apps. Instead, it combines coding and co-working in just one app.&lt;/p&gt;

&lt;p&gt;Again, starting to use Codex at the end of April felt familiar because it more or less had the same interface as the Claude desktop app. But from day one, I had the feeling that there were fewer bugs and that the Codex app, and therefore the GPT harness, &lt;strong&gt;just worked&lt;/strong&gt; most of the time.&lt;/p&gt;

&lt;p&gt;What I like about Codex is that it usually feels &lt;strong&gt;less theatrical&lt;/strong&gt;. There is less visual noise, fewer little productivity animations, and more focus on the task, the diff, the terminal output, and the verification. That sounds boring, but for me boring is often good when I work on real code. I especially like the direction around using the machine and environment I already have configured instead of pretending that every project is easy to run in a clean cloud sandbox. For an &lt;em&gt;Angular&lt;/em&gt; application with local setup, browser checks, environment variables, internal packages, and maybe a slightly weird test configuration, that matters a lot.&lt;/p&gt;

&lt;p&gt;And there is another practical detail I really like: &lt;a href="https://openai.com/index/work-with-codex-from-anywhere/" rel="noopener noreferrer"&gt;Codex can now be controlled from the smartphone&lt;/a&gt; while it still uses the desktop machine as the actual working environment. So I can start or continue a task from the couch, the train, or wherever, without moving the whole project into a cloud runner. Of course, I still review the diff properly later, but for keeping an agent moving this is very convenient.&lt;/p&gt;

&lt;p&gt;So my personal verdict would be that the Codex app is &lt;strong&gt;superior&lt;/strong&gt; to the Claude desktop app. However, the underlying models in the Claude desktop app, Opus 4.5, 4.6, and 4.7, are still &lt;strong&gt;superior&lt;/strong&gt; to &lt;a href="https://openai.com/index/introducing-gpt-5-5/" rel="noopener noreferrer"&gt;GPT 5.5&lt;/a&gt; in many cases, especially in the fields I mentioned in the last post, such as architecture, design, and more.&lt;/p&gt;

&lt;p&gt;In day-to-day &lt;em&gt;Angular&lt;/em&gt; tasks, though, the gap is often &lt;strong&gt;smaller than the model benchmarks suggest&lt;/strong&gt;. Codex with GPT 5.5 is very strong at the kind of work that fills most of my week: writing or updating standalone components, generating Vitest or Jasmine specs that actually use &lt;code&gt;TestBed&lt;/code&gt;, scaffolding signal-based stores, and producing usable Signal Forms code. Where I still reach for the Claude desktop app is the harder, more design-heavy work, for example shaping a new feature's component tree or untangling a legacy service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cursor + Composer 2.5
&lt;/h3&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%2F9t16rc0okpa8p29qq0lv.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%2F9t16rc0okpa8p29qq0lv.png" alt="Cursor" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once I decided to do a workshop about agentic coding, I realized I had to try out all the available options and not just use my favorite ones. So of course, one candidate for the best super app, or in other words, agentic coding app, is &lt;a href="https://cursor.com/download/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cursor, basically a VS Code fork, was one of the first IDEs to really focus on agentic coding and agentic workflows, and many people started to use these workflows much earlier than I did. But in the past, let's say at the beginning of 2025, I had the feeling that the code generation was not really good enough for my requirements.&lt;/p&gt;

&lt;p&gt;Nevertheless, Cursor recently announced a &lt;a href="https://cursor.com/blog/spacex-model-training" rel="noopener noreferrer"&gt;major deal with SpaceXAI&lt;/a&gt; that gives SpaceXAI the option to acquire Cursor for 60 billion dollars, or to pay 10 billion dollars for their partnership instead. So this is no longer only a small IDE story.&lt;/p&gt;

&lt;p&gt;So why are Cursor and SpaceXAI such a good match? Because SpaceXAI has huge compute with &lt;a href="https://www.tomshardware.com/pc-components/gpus/nvidia-backs-20-billion-xai-chip-deal" rel="noopener noreferrer"&gt;Colossus 2&lt;/a&gt;, while Cursor has strong product usage signals and developer workflow telemetry. Together, they are about to enter, or have already entered, the frontier stage of agentic coding.&lt;/p&gt;

&lt;p&gt;By the way, &lt;a href="https://cursor.com/download/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; and &lt;a href="https://cursor.com/changelog/composer-2-5" rel="noopener noreferrer"&gt;Composer 2.5&lt;/a&gt; are also super fast, and they can be a &lt;strong&gt;cheap alternative&lt;/strong&gt; if you really have to pay for the tokens and cannot use the subsidized OpenAI or Anthropic subscription model. But more on the costs of using these tools in &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;the next post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The part I had underestimated is &lt;strong&gt;Cursor's cloud story&lt;/strong&gt;. I used to think of Cursor mostly as the AI-first VS Code clone. That is still part of it, of course, but the more interesting direction is letting agents run away from my local machine and come back with a result. If your team wants to trigger work from Slack, run several agents in parallel, or give non-developers a safe way to start small engineering tasks, Cursor's &lt;a href="https://cursor.com/blog/cloud-agents" rel="noopener noreferrer"&gt;cloud agents&lt;/a&gt; become much more interesting than the editor alone. Of course, this also makes the &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;data and policy side&lt;/a&gt; more important.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Angular&lt;/em&gt; specifically, I found Composer 2.5 most useful for the mechanical kind of refactor: renaming a service across an entire feature, converting a class-based &lt;code&gt;@Input()&lt;/code&gt; to the new &lt;code&gt;input()&lt;/code&gt; signal API, or rewriting a template from &lt;code&gt;*ngIf&lt;/code&gt;/&lt;code&gt;*ngFor&lt;/code&gt; to the new &lt;code&gt;@if&lt;/code&gt;/&lt;code&gt;@for&lt;/code&gt; control flow. It is &lt;strong&gt;fast enough to feel interactive&lt;/strong&gt;, and that speed changes how often you actually run a refactor instead of postponing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Antigravity + Gemini 3.5 Flash
&lt;/h3&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%2F02svtpefgyc3eus51wfy.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%2F02svtpefgyc3eus51wfy.png" alt="Antigravity by Google" width="488" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A little more than a week ago, Google also announced a new release at &lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-developer-highlights/" rel="noopener noreferrer"&gt;Google I/O&lt;/a&gt;, where they presented the new agentic coding app called &lt;a href="https://antigravity.google/download" rel="noopener noreferrer"&gt;Antigravity 2.0&lt;/a&gt;. The interesting thing is that they transformed Antigravity from an IDE with an included agentic coding workflow into a super app similar to the Claude desktop app and Codex. If you compare the user interface to Codex, it almost feels like a one-to-one copy, which is good because I think Codex currently has the &lt;strong&gt;best user interface&lt;/strong&gt; of all the options.&lt;/p&gt;

&lt;p&gt;The former IDE is now kind of an integrated sub-app that can be triggered from the Antigravity app whenever needed. This is actually a really good idea, and I like it very much. The same idea already exists in Codex, where you can open your preferred IDE. In my case, that would be WebStorm, of course. For whatever reason, the Claude desktop app is missing that feature.&lt;/p&gt;

&lt;p&gt;So while this 2.0 release is definitely a big step in the right direction, I personally don't think Antigravity is competitive with the other options yet. There are two reasons for that. First, the app still &lt;strong&gt;feels clunky and buggy&lt;/strong&gt;, and it has fewer settings than the other apps, so it just does not feel as mature. Second, yes, you can use other models, but I think the real point of using Antigravity is to use it with &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-5/" rel="noopener noreferrer"&gt;Gemini models&lt;/a&gt;, especially Gemini 3.5 Flash in the new Antigravity 2.0 setup. Usage for models like Opus 4.6 has recently been reduced, and I guess it will be reduced even more in the future.&lt;/p&gt;

&lt;p&gt;On &lt;em&gt;Angular&lt;/em&gt; tasks, Gemini 3.5 Flash is genuinely capable, and its speed is useful when you want fast iterations over a whole feature module or a long template. But in practice I still see it default to slightly older &lt;em&gt;Angular&lt;/em&gt; idioms more often than Opus 4.7 or GPT 5.5, especially around signals and the new control flow. With clear guardrails it gets there, but it currently &lt;strong&gt;needs more steering&lt;/strong&gt; than the other two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Source Alternatives to the Big Ones
&lt;/h3&gt;

&lt;p&gt;Beyond the big names, I would also keep an eye on &lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; and &lt;a href="https://t3.codes/" rel="noopener noreferrer"&gt;T3 Code&lt;/a&gt;. OpenCode is a solid open-source, terminal-first option if you want a model-agnostic agent and bring your own provider setup. T3 Code is interesting for the opposite reason: it gives you an open-source GUI on top of the agents you may already pay for, like Claude Code, Codex CLI, OpenCode, or Cursor.&lt;/p&gt;

&lt;h3&gt;
  
  
  But I'm super happy with working in the terminal
&lt;/h3&gt;

&lt;p&gt;Fair enough. If you love the terminal, you can absolutely keep using it, but I no longer think it is obviously the best default for agentic coding. &lt;strong&gt;Apps make it easier&lt;/strong&gt; to review diffs, manage several agents, use screenshots or browser checks, continue from the phone, and turn work into PRs. But that is personal preference, and I don't want to force anybody into my workflow.&lt;/p&gt;

&lt;p&gt;If you are still happiest in the terminal, &lt;a href="https://pi.dev/" rel="noopener noreferrer"&gt;Pi&lt;/a&gt; is probably the option I would suggest trying first. It is a minimal terminal coding harness with &lt;code&gt;AGENTS.md&lt;/code&gt; support, skills, extensions, tree-structured sessions, and many model providers. It feels less like a sealed product and more like something you can adapt to your own workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Coding Agent Harness Would I Choose Today?
&lt;/h2&gt;

&lt;p&gt;Okay, that was either a lot of information to digest, or you were already familiar with all the super apps and IDEs I mentioned. So let's try to summarize the current state of my workflow and which harnesses I prefer for different tasks.&lt;/p&gt;

&lt;p&gt;My two favorite tools are definitely Codex and the Claude desktop app. I would put them &lt;strong&gt;roughly on par&lt;/strong&gt;. I use them daily for almost everything, and I feel really lucky that I can use these tools through subsidized subscription models. More on costs in &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;the next post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While preparing the upcoming workshop, I also found out that Cursor has really become a super app and that it supports agentic workflows very well. All three are capable of code generation, code review, modernization, refactoring, and writing tests in your &lt;em&gt;Angular&lt;/em&gt; codebases.&lt;/p&gt;

&lt;p&gt;The practical difference is not only which one can solve a given prompt. It is also &lt;strong&gt;how it tries to solve it&lt;/strong&gt;. Claude Code is great when I want to give a strong model a lot of freedom and let it explore. Codex is great when I want a calmer engineering tool that uses my local setup and keeps verification close to the work. Cursor is great when the work should move beyond my own machine into an IDE, browser, cloud, or team workflow.&lt;/p&gt;

&lt;p&gt;For Antigravity, however, I still see a lot of work for the Google teams before it reaches the level of the other three apps. And of course, those other apps will continue to improve as well.&lt;/p&gt;

&lt;p&gt;For my beloved IDE WebStorm, I see &lt;strong&gt;difficult times ahead&lt;/strong&gt;. I hope the JetBrains team will be able to catch up with the other apps in terms of productivity and usability when using these models for agentic tasks. There is still a big benefit to using IDEs, and they also allow you to choose between several model providers. Even Cursor allows you to do that, whereas Codex and the Claude desktop app want to lock you into one LLM provider.&lt;/p&gt;

&lt;p&gt;So today, I'll still stick with Codex and the Claude desktop app. But maybe in a month or so, I might already be switching to the next super app. We will see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Engineering Workshop
&lt;/h2&gt;

&lt;p&gt;This is also why I don't think about harnesses in isolation: the model, the app, my &lt;em&gt;Angular&lt;/em&gt; Guardrails, my &lt;em&gt;Angular&lt;/em&gt; Coding Style Guide, the &lt;em&gt;Angular&lt;/em&gt; Skills, and the review workflow belong together.&lt;/p&gt;

&lt;p&gt;If you want to learn how to pick the right harness and build a strict human-in-the-loop workflow where every generated line of &lt;em&gt;Angular&lt;/em&gt; code looks handcrafted, make sure to join our &lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;, both in English and German.&lt;/p&gt;

&lt;p&gt;In this workshop, advanced &lt;em&gt;Angular&lt;/em&gt; developers learn how to move from vibe coding to traceable Agentic Engineering workflows: AI-ready project setup, guardrails, spec-first and plan-first workflows, UX and component prototyping, code review, testing, and brownfield refactoring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;&lt;/a&gt; – 2 days, remote&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The main takeaway is simple: &lt;strong&gt;the harness matters&lt;/strong&gt;. A great model in a weak app can feel surprisingly limited, while the same model in a strong agentic workflow can become much more useful for real &lt;em&gt;Angular&lt;/em&gt; work. That is why &lt;em&gt;Angular&lt;/em&gt; teams should not only ask which model is best, but also which harness, review process, and engineering workflow actually help them ship better code.&lt;/p&gt;

&lt;p&gt;For my current workflow, Codex and the Claude desktop app are still my &lt;strong&gt;daily drivers&lt;/strong&gt;. Codex currently feels like the more polished app to me, while the Claude desktop app gives me access to the Opus models that I trust most for architecture, design, and complex reasoning. By the way, Anthropic has since released &lt;a href="https://www.anthropic.com/news/claude-opus-4-8" rel="noopener noreferrer"&gt;Opus 4.8&lt;/a&gt;, which only underlines the point of this post: the model underneath keeps improving, while the harness around it is what I actually commit to. Cursor deserves a serious look when speed, cost, an IDE-like workflow, or cloud agents matter, and Antigravity is worth watching, but for now it still feels like it needs more time. When I do careful handcrafting, I still switch back to WebStorm.&lt;/p&gt;

&lt;p&gt;One thing I learned quickly: don't force the same workflow onto every tool. Codex, the Claude desktop app, Cursor, and even the smaller open-source options work best when you let them shape your workflow a little bit.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;don't get too attached&lt;/strong&gt; to one app, one IDE, or one vendor workflow. These tools are changing too quickly, so it is worth trying new harnesses regularly and switching when they make your real project work better.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;costs post&lt;/a&gt;, I look at pricing, subscriptions, and cost per accepted change. In the &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;privacy post&lt;/a&gt;, I look at what these tools actually receive from your codebase and how I would think about company policy.&lt;/p&gt;

&lt;p&gt;Thank you for reading 🙏 this blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. For feedback, remarks or questions, please reach out to me ❤️&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>angular</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Agentic Engineering: Which LLM Is Best for Angular Development?</title>
      <dc:creator>Alexander Thalhammer</dc:creator>
      <pubDate>Mon, 01 Jun 2026 20:40:36 +0000</pubDate>
      <link>https://dev.to/lxt/agentic-engineering-which-llm-is-best-for-angular-development-1o2</link>
      <guid>https://dev.to/lxt/agentic-engineering-which-llm-is-best-for-angular-development-1o2</guid>
      <description>&lt;h2&gt;
  
  
  My AI Coding Journey
&lt;/h2&gt;

&lt;p&gt;It's almost six months since my &lt;a href="https://www.angulararchitects.io/blog/angular-aria/" rel="noopener noreferrer"&gt;last post on this blog&lt;/a&gt;. In that time, my daily work changed rapidly and completely. Until November 2025, I thought AI was &lt;strong&gt;not useful&lt;/strong&gt; for my work in complex enterprise &lt;em&gt;Angular&lt;/em&gt; projects, where code quality has to be very high.&lt;/p&gt;

&lt;p&gt;But then &lt;a href="https://www.anthropic.com/news/claude-opus-4-5" rel="noopener noreferrer"&gt;Opus 4.5&lt;/a&gt; was released to the public on November 24, 2025. I started to experiment with it and quickly found that it could help with &lt;strong&gt;real work&lt;/strong&gt;: code generation, code review, documentation, modernization, refactoring, and a lot more.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;quality surprised me&lt;/strong&gt; first. Some generated &lt;em&gt;Angular&lt;/em&gt; code looked close to how I would have written it myself. The speed surprised me next. Naturally, I felt a bit obsolete, but it was and still is pretty exciting.&lt;/p&gt;

&lt;p&gt;The biggest change is not that the latest models write better code. The bigger shift is that using them has become &lt;strong&gt;much easier&lt;/strong&gt;: I no longer need to craft a perfect prompt upfront and hope that the result matches my intent. Instead, the interaction feels much closer to talking to a senior developer: I can describe the problem, add constraints, answer questions, clarify the spec, and let the model help discover what we actually want to build.&lt;/p&gt;

&lt;p&gt;If you've been working with AI every day for months, there might not be much new for you in this post. But if you are rather new to Agentic Engineering and want to understand which LLM is best for &lt;em&gt;Angular&lt;/em&gt; development, this post is for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison from November 2025 (Outdated!)
&lt;/h3&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%2F5aei1e3ssbxry7o1fimu.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%2F5aei1e3ssbxry7o1fimu.webp" alt="Opus 4.5 comparison from November 2025" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BTW: SWE-bench itself is also showing its age. I will use more recent coding-agent benchmarks from Artificial Analysis later in this post.&lt;/p&gt;

&lt;p&gt;I started using Opus 4.5 through my favorite IDE, WebStorm. But in this post, I don't want to talk about IDE integration, harnesses, and tools yet. &lt;a href="https://www.angulararchitects.io/blog/ai-apps-harnesses-for-angular/" rel="noopener noreferrer"&gt;That part of the journey deserves its own post&lt;/a&gt;. Here, I want to stay focused on one question: which LLM is the best fit for &lt;em&gt;Angular&lt;/em&gt; development?&lt;/p&gt;

&lt;p&gt;There is no public &lt;em&gt;Angular&lt;/em&gt; benchmark, so we have to combine two imperfect sources: existing public coding benchmarks and personal experience from real &lt;em&gt;Angular&lt;/em&gt; work. I want to start with the public benchmarks and then move to my own verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: My Current Ranking
&lt;/h2&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%2Fqj2thh985qu0d2g9an78.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%2Fqj2thh985qu0d2g9an78.png" alt="Current LLM ranking for Angular development" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Frontier LLMs Are Relevant for &lt;em&gt;Angular&lt;/em&gt; in May 2026?
&lt;/h2&gt;

&lt;p&gt;I personally want to use the best and latest LLMs that I can realistically use for &lt;em&gt;Angular&lt;/em&gt; development, so I will mostly talk about frontier models that are generally available through common developer tools, APIs, or IDE integrations.&lt;/p&gt;

&lt;p&gt;In my subjective opinion, the most relevant frontier LLMs for &lt;em&gt;Angular&lt;/em&gt; development currently are (sorted by release date – earliest to latest):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/news/claude-opus-4-7" rel="noopener noreferrer"&gt;Opus 4.7&lt;/a&gt; by Anthropic, April 16, 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openai.com/index/introducing-gpt-5-5/" rel="noopener noreferrer"&gt;GPT 5.5&lt;/a&gt; by OpenAI, April 23, 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cursor.com/changelog/composer-2-5" rel="noopener noreferrer"&gt;Composer 2.5&lt;/a&gt; by Cursor, May 18, 2026&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-5/" rel="noopener noreferrer"&gt;Gemini 3.5 Flash&lt;/a&gt; by Google, May 19, 2026&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two were released to the public only a few days before I wrote this, so I have not had the chance to experiment with them a lot yet. Still, I think Opus 4.7, GPT 5.5, and Composer 2.5 are the current candidates for the &lt;strong&gt;best LLM for &lt;em&gt;Angular&lt;/em&gt; development&lt;/strong&gt;. Gemini 3.5 Flash is included here because it is new and interesting, but my first impression is that it is not really a challenger to the other three models in this post. I'm still pretty sure Gemini 3.5 Pro will be a serious contender once it is released.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Benchmarks
&lt;/h2&gt;

&lt;p&gt;At Google I/O in May 2026, Google released a bunch of new AI updates. One of those releases was Google Gemini 3.5 Flash, and the release notes included some benchmarks:&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%2F1y52xdrvn802dk1j1rss.gif" 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%2F1y52xdrvn802dk1j1rss.gif" alt="Gemini 3.5 Benchmark" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I see at least two problems here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This is vendor-provided marketing material for Gemini 3.5 Flash, so it is not really objective.&lt;/li&gt;
&lt;li&gt;We don't know enough about what the benchmarks are, how they were conducted, and what the results really mean for &lt;em&gt;Angular&lt;/em&gt; development.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So what can we use instead? My favorite benchmarks are done by Artificial Analysis. They are still not &lt;em&gt;Angular&lt;/em&gt;-specific, but they give us a better baseline for comparing model capability, speed, and token usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artificial Analysis Benchmarks
&lt;/h3&gt;

&lt;p&gt;I want to show you three views from &lt;a href="https://artificialanalysis.ai/agents/coding-agents" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt;: the "Coding Agent Index" for overall coding-agent performance, "Execution Time" for active agent runtime, and "Token Usage" for how much context and output the models need to solve tasks. The screenshots below were captured on May 26, 2026, so check the live leaderboard for the latest numbers.&lt;/p&gt;

&lt;p&gt;Strictly speaking, these are coding-agent benchmarks, not pure LLM benchmarks. The &lt;strong&gt;harness matters a lot&lt;/strong&gt;: the same model can behave differently in Claude Code, Cursor, Codex, OpenCode, an IDE integration, or a custom agent setup. That is why I cover harnesses and integrations separately; here I only use these numbers as a rough signal for model quality.&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%2F3g26tnz0i4s9lv4ya7l2.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%2F3g26tnz0i4s9lv4ya7l2.png" alt="Coding Agent Index" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Artificial Analysis Coding Agent Index is a composite score built from SWE-Bench-Pro-Hard-AA, Terminal-Bench v2, and SWE-Atlas-QnA. You can read their &lt;a href="https://artificialanalysis.ai/methodology/coding-agents-benchmarking" rel="noopener noreferrer"&gt;coding-agent benchmark methodology&lt;/a&gt; for the details.&lt;/p&gt;

&lt;p&gt;It is useful for quick comparison, but it should be read alongside the per-eval breakdowns. Two agents with similar index values can still have different strengths across repository tasks, terminal workflows, and rubric-based evaluations.&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%2F03n4qrk75odlnh6ti9g2.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%2F03n4qrk75odlnh6ti9g2.png" alt="Execution Time" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mean agent wall time per task. Lower is better.&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%2Frwwep5vpf3fpeekw41q2.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%2Frwwep5vpf3fpeekw41q2.png" alt="Token Usage" width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mean input, cache, and output tokens per task.&lt;/p&gt;

&lt;h3&gt;
  
  
  DeepSWE Benchmark
&lt;/h3&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%2Ftle2v3hp7qa80z2xlkok.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%2Ftle2v3hp7qa80z2xlkok.png" alt="Recent DeepSWE benchmark" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Update May 31st (including Opus 4.8):&lt;/em&gt; Another recent benchmark that matches my day-to-day impression more closely is &lt;a href="https://deepswe.datacurve.ai/blog" rel="noopener noreferrer"&gt;DeepSWE&lt;/a&gt;. In this leaderboard, GPT 5.5 reaches 70%, GPT 5.4 56%, Opus 4.8 58%, Opus 4.7 54%, Opus 4.6 28%, and Gemini 3.5 Flash reaches 28%. That lines up with my current feeling: GPT 5.5 and Opus 4.8 are &lt;strong&gt;both strong&lt;/strong&gt; for serious coding work, while Gemini 3.5 Flash still feels more experimental for &lt;em&gt;Angular&lt;/em&gt; development.&lt;/p&gt;

&lt;p&gt;What I like about this benchmark is that it focuses more on the kind of work that matters in real agentic workflows: can the agent take a short behavioral prompt, find the right area of the codebase, and implement the change cleanly without me spelling out every file, module, and function? That is exactly the kind of workflow where traditional SWE-Bench results can feel disconnected from daily coding experience.&lt;/p&gt;

&lt;p&gt;Composer 2.5 is unfortunately not part of this comparison, probably because it is mostly available through Cursor surfaces and not as a normal standalone model API.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the Benchmarks Suggest
&lt;/h3&gt;

&lt;p&gt;In these benchmark results, &lt;a href="https://blog.google/innovation-and-ai/models-and-research/gemini-models/gemini-3-1-pro/" rel="noopener noreferrer"&gt;Gemini 3.1 Pro&lt;/a&gt; – still the current Pro tier, since Gemini 3.5 Pro has not shipped yet – does not look competitive with the other models. Gemini 3.5 Flash looks more interesting, but the public data is still very fresh and not specific to our framework.&lt;/p&gt;

&lt;p&gt;My personal &lt;em&gt;Angular&lt;/em&gt; impression is similar, but not identical: Gemini 3.5 Flash is sometimes fast and sometimes not, and it does not yet feel like a real challenger to my top three for my work. Gemini 3.5 Pro might become the more serious candidate here, but we don't have enough data yet.&lt;/p&gt;

&lt;p&gt;In this benchmark snapshot, GPT 5.5 wins overall and is more efficient. So let me be upfront about the tension: the benchmarks I trust most currently lean GPT 5.5, yet my own &lt;em&gt;Angular&lt;/em&gt; verdict below still puts Opus on top. That gap between leaderboard and lived experience is exactly why I read these numbers as &lt;strong&gt;useful signals, not final answers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I don't want to look at cost in detail here. That deserves &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;its own follow-up&lt;/a&gt; after the tools and harnesses post, because cost depends heavily on how you access these models. For this post, the central question is still the same: which model gives &lt;em&gt;Angular&lt;/em&gt; developers the best mix of quality, speed, and practical usefulness?&lt;/p&gt;

&lt;p&gt;I also don't want to pretend that this post is a real benchmark. The deeper &lt;em&gt;Angular&lt;/em&gt; evidence behind my preferences comes from my own daily work experience with modernization tasks, refactorings, code reviews, testing workflows, and brownfield scenarios. In our &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;&lt;/a&gt;, I will show practical examples of why these models became my current favorites for professional &lt;em&gt;Angular&lt;/em&gt; work.&lt;/p&gt;

&lt;p&gt;Before I move to my personal verdict, there are four side questions worth clearing up because they often come up when comparing current LLMs: Chinese models, European models, local models, and whether any of them are actually open source.&lt;/p&gt;

&lt;h3&gt;
  
  
  But what about the Chinese LLMs?
&lt;/h3&gt;

&lt;p&gt;As of May 2026, Chinese LLMs like &lt;a href="https://api-docs.deepseek.com/updates/" rel="noopener noreferrer"&gt;DeepSeek V4&lt;/a&gt;, &lt;a href="https://qwenlm.github.io/qwen-code-docs/en/blog/weekly-update-2026-04-09/" rel="noopener noreferrer"&gt;Qwen 3.6&lt;/a&gt;, and &lt;a href="https://platform.kimi.ai/docs/guide/kimi-k2-6-quickstart" rel="noopener noreferrer"&gt;Kimi K2.6&lt;/a&gt; are &lt;strong&gt;absolutely worth watching&lt;/strong&gt;, especially because they are pushing very hard on coding quality, open weights, and low cost. But for serious &lt;em&gt;Angular&lt;/em&gt; development, I have not seen enough evidence yet to put them in the same tier as Opus 4.7, GPT 5.5, or Composer 2.5, so for now they are interesting to observe, not models I would bet my enterprise work on.&lt;/p&gt;

&lt;h3&gt;
  
  
  And what about the European LLMs?
&lt;/h3&gt;

&lt;p&gt;As of May 2026, European LLMs are mostly a Mistral story for me, with &lt;a href="https://mistral.ai/news/mistral-3" rel="noopener noreferrer"&gt;Mistral Large 3&lt;/a&gt; as the latest model worth tracking: interesting, improving, and relevant when data residency, EU vendors, or open-weight deployment matter. But for raw coding capability in my &lt;em&gt;Angular&lt;/em&gt; work, I would not rank them with the frontier labs right now, so I would treat them as &lt;strong&gt;strategic options&lt;/strong&gt; rather than serious candidates for the best model.&lt;/p&gt;

&lt;h3&gt;
  
  
  And what about local LLMs?
&lt;/h3&gt;

&lt;p&gt;As of May 2026, local LLMs are no longer just toys, but we have to be precise about what "local" means. Running a model on my MacBook Pro is very different from self-hosting a huge open-weight MoE (Mixture of Experts) model on serious GPU infrastructure.&lt;/p&gt;

&lt;p&gt;For local or self-hosted coding workflows, &lt;a href="https://github.com/QwenLM/Qwen3.6" rel="noopener noreferrer"&gt;Qwen 3.6&lt;/a&gt; is probably the most interesting family to watch right now, especially because the Qwen team explicitly focuses on agentic coding, repository-level reasoning, and local deployment via Transformers, llama.cpp, MLX, vLLM, and SGLang. &lt;a href="https://api-docs.deepseek.com/news/news260424" rel="noopener noreferrer"&gt;DeepSeek V4&lt;/a&gt; is also very relevant in the open-weight space, but the larger variants are more realistic for server-side self-hosting than for a normal developer machine. From Europe, &lt;a href="https://mistral.ai/news/mistral-3" rel="noopener noreferrer"&gt;Mistral 3&lt;/a&gt; is interesting because Mistral Large 3 and the smaller Ministral 3 models are open-weight, with the smaller models explicitly targeting edge and local use cases.&lt;/p&gt;

&lt;p&gt;For my professional &lt;em&gt;Angular&lt;/em&gt; work, I would not put local models in the same tier as my current favorites yet. The gap is not only raw model quality, but also tool use, long-running agent reliability, context handling, and integration quality. Still, local models are &lt;strong&gt;becoming useful&lt;/strong&gt; for privacy- or regulatory-sensitive work, CI helpers, and teams that want more control over where their code goes. I would not ignore them anymore, but I also would not make them my choice for enterprise &lt;em&gt;Angular&lt;/em&gt; development today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are any of these models open source?
&lt;/h3&gt;

&lt;p&gt;This is where the wording gets a bit tricky, because with LLMs people often say "open source" when they really mean "open weights". Claude, GPT, Gemini, and Composer 2.5 are not open source models in the practical sense, although Composer is based on Moonshot's open Kimi checkpoint. DeepSeek, Qwen, Kimi, and some Mistral models are much closer to that world because their weights are available under permissive or semi-permissive licenses. Still, even there, we usually don't get the full training data, training code, and recipe, so I would call them &lt;strong&gt;open-weight models&lt;/strong&gt; rather than fully open source models.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Subjective Verdict
&lt;/h2&gt;

&lt;p&gt;With that context out of the way, I want to move to my own experience with these LLMs. For me, the real question is not only which model wins a benchmark, but which model fits which part of an &lt;em&gt;Angular&lt;/em&gt; workflow. This part is purely subjective, and I'm open to other opinions and experiences. If you disagree with me, please let me know in the comments on X or Reddit.&lt;/p&gt;

&lt;h3&gt;
  
  
  My #1 for &lt;em&gt;Angular&lt;/em&gt;: Opus 4.7 by Anthropic
&lt;/h3&gt;

&lt;p&gt;My favorite model is Opus 4.7. It is the best LLM for &lt;em&gt;Angular&lt;/em&gt; development in my opinion because it has the &lt;strong&gt;strongest code quality&lt;/strong&gt;, the best overall performance, and still feels fast enough for serious agentic work.&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%2Fqvwq5wfbr4fm1lzbefsc.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%2Fqvwq5wfbr4fm1lzbefsc.png" alt="Opus 4.7 by Anthropic" width="620" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also the most versatile LLM. The big shift for me is that Opus 4.7 no longer feels like a junior helper that needs tiny step-by-step prompts, but more like a &lt;strong&gt;senior partner&lt;/strong&gt; that works best when I give it context, constraints, and good questions.&lt;/p&gt;

&lt;p&gt;I think this model is currently the &lt;strong&gt;most capable&lt;/strong&gt; one for complex enterprise &lt;em&gt;Angular&lt;/em&gt; applications. It's also the most expensive one, especially in Extra and Max Mode, but I think it's worth it for the quality of the results.&lt;/p&gt;

&lt;p&gt;I really like doing agentic work with this model, especially for tasks where I want it to explore trade-offs, challenge assumptions, and come back with a concrete implementation plan. In the follow-up post about tools and harnesses, I show how I use it in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  My #1 all-rounder: GPT 5.5 by OpenAI
&lt;/h3&gt;

&lt;p&gt;GPT 5.5 is also a great LLM for &lt;em&gt;Angular&lt;/em&gt; development, and &lt;a href="https://openai.com/index/introducing-gpt-5-5/" rel="noopener noreferrer"&gt;OpenAI's introduction&lt;/a&gt; matches what I see in practice: its real strength is &lt;strong&gt;focused implementation work&lt;/strong&gt;. It writes excellent code and is very token-efficient, especially when you do not force the highest reasoning level. However, it is not as versatile as Opus 4.7, and I find it more sensitive to context: if it starts from the wrong assumption, I often get better results by opening a fresh thread with clearer constraints instead of trying to steer the old one back.&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%2Feyv5xickpcxbj50kbqtq.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%2Feyv5xickpcxbj50kbqtq.png" alt="GPT 5.5 by OpenAI" width="620" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That efficiency matters because GPT 5.5 is expensive per token, but it often needs &lt;strong&gt;fewer tokens&lt;/strong&gt; to finish a task than competing frontier models. For practical work, I would start with lower or medium reasoning and only move up when the task really needs it.&lt;/p&gt;

&lt;p&gt;For everyday chat use cases, I really like this model. I use it for a variety of tasks, especially when I want to think through a problem by asking a series of focused questions, then start a clean implementation thread once the direction is clear. For example, if I need help fixing the language of this blog post, I will definitely choose GPT 5.5, but I also (currently) prefer it for modernization and refactoring of &lt;em&gt;Angular&lt;/em&gt; projects. Have you tried resolving hard merge conflicts? Oh boy, that's really a relief. Hard to trust though because it's so fast. I think it's kind of a &lt;strong&gt;senior engineer&lt;/strong&gt; while Opus 4.7 is the better &lt;strong&gt;architect&lt;/strong&gt;, but that's for sure subjective.&lt;/p&gt;

&lt;h3&gt;
  
  
  My #1 newcomer: Composer 2.5 by Cursor
&lt;/h3&gt;

&lt;p&gt;Composer 2.5 is a very interesting LLM for &lt;em&gt;Angular&lt;/em&gt; development because it combines good code quality with &lt;strong&gt;very high speed&lt;/strong&gt;. The most interesting claim from the current discussion around it is that Cursor distilled it from Moonshot's Kimi K2.5 open-weight checkpoint and, on Cursor's own benchmark, got surprisingly close to GPT 5.5 and Opus 4.7 at a much lower cost. The important caveat is that this is still hard to verify independently because Composer is mostly available through Cursor surfaces, not as a normal model API.&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%2Faem6bl742agkf1rzj06c.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%2Faem6bl742agkf1rzj06c.png" alt="Composer 2.5 by Cursor" width="620" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If costs are important to you and you cannot rely on generous included usage in paid plans such as Claude Code or Codex, Composer 2.5 might be the &lt;strong&gt;best option for your setup&lt;/strong&gt;, but only if Cursor fits your workflow. I cover tools and harnesses in the follow-up post, and then come back to costs and access models in a &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;dedicated follow-up&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Candidate to watch: Gemini 3.5 Flash now, Gemini 3.5 Pro later
&lt;/h3&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%2Ftpjwbffv6ioq20em7h93.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%2Ftpjwbffv6ioq20em7h93.png" alt="Gemini 3.5 Flash by Google" width="620" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gemini 3.5 Flash is interesting, but my first impression is mixed: sometimes it is fast, sometimes it is not, and for &lt;em&gt;Angular&lt;/em&gt; development it does not yet feel like a &lt;strong&gt;real challenger&lt;/strong&gt; to Opus 4.7, GPT 5.5, or Composer 2.5. I still need more hands-on time and better external benchmark data before I can recommend it with confidence. Gemini 3.5 Pro might become the more serious candidate here, especially if it combines stronger coding quality with more consistent speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Alex, what about the new model(s)?
&lt;/h3&gt;

&lt;p&gt;Only two days after publishing this post, Anthropic released &lt;a href="https://www.anthropic.com/news/claude-opus-4-8" rel="noopener noreferrer"&gt;Claude Opus 4.8&lt;/a&gt;. So does that mean this post was already outdated two or three days later?&lt;/p&gt;

&lt;p&gt;I don't think so.&lt;/p&gt;

&lt;p&gt;There is probably some improvement from Opus 4.7 to Opus 4.8, and Anthropic's benchmark numbers are impressive. The new model, and the next ones after it, might beat other models such as GPT 5.5 on several benchmarks. But that does not change the main point of this post: benchmark wins are &lt;strong&gt;useful signals, not final answers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For real &lt;em&gt;Angular&lt;/em&gt; work, the best model is still the one that &lt;strong&gt;fits your workflow best&lt;/strong&gt;. Try the new models when they arrive, but judge them in your own codebase, with your own tasks, your own tools, and your own review standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Engineering Workshop
&lt;/h2&gt;

&lt;p&gt;This is also why I don't think about LLMs in isolation anymore: the model, the app, my &lt;em&gt;Angular&lt;/em&gt; Guardrails, my &lt;em&gt;Angular&lt;/em&gt; Coding Style Guide, the &lt;em&gt;Angular&lt;/em&gt; Skills, and the review workflow belong together.&lt;/p&gt;

&lt;p&gt;If you want to learn how to choose the right model for your &lt;em&gt;Angular&lt;/em&gt; work – and actually put it to work on real modernization, refactoring, testing, and review tasks – make sure to join our &lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;, both in English and German.&lt;/p&gt;

&lt;p&gt;In this workshop, advanced &lt;em&gt;Angular&lt;/em&gt; developers learn how to move from vibe coding to traceable Agentic Engineering workflows: AI-ready project setup, guardrails, spec-first and plan-first workflows, UX and component prototyping, code review, testing, and brownfield refactoring.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;a href="https://www.angulararchitects.io/en/training/agentic-engineering-von-vibe-coding-zu-professionellen-ki-gestuetzten-workflows/" rel="noopener noreferrer"&gt;&lt;strong&gt;Agentic Engineering Workshop&lt;/strong&gt;&lt;/a&gt; – 2 days, remote&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The real shift is not that these models can generate code. The real shift is that they can &lt;strong&gt;participate in the engineering workflow&lt;/strong&gt;: asking questions, clarifying requirements, exploring trade-offs, and helping us move from vague intent to working &lt;em&gt;Angular&lt;/em&gt; code.&lt;/p&gt;

&lt;p&gt;For serious &lt;em&gt;Angular&lt;/em&gt; work, my current recommendation is simple: start with Opus 4.7 when quality, architecture, and complex agentic work matter most. Use GPT 5.5 when you want a very strong all-rounder that is efficient, pleasant to work with, and great for writing, modernization, and focused implementation tasks.&lt;/p&gt;

&lt;p&gt;Watch Composer 2.5 closely, especially if you already work in Cursor and care a lot about speed and cost. Treat Gemini 3.5 Flash as experimental for now, and revisit once Gemini 3.5 Pro ships.&lt;/p&gt;

&lt;p&gt;As the Opus 4.8 update above already shows, this space changes too quickly for fixed rankings to stay perfect for long. So whatever model you choose, don't rely on benchmarks alone: the real test is still whether the model helps you &lt;strong&gt;ship better &lt;em&gt;Angular&lt;/em&gt; code&lt;/strong&gt; in your own codebase.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.angulararchitects.io/blog/ai-apps-harnesses-for-angular/" rel="noopener noreferrer"&gt;next post&lt;/a&gt;, I look at the other half of the story: which apps, IDE integrations, and agentic coding harnesses actually make these models useful in daily &lt;em&gt;Angular&lt;/em&gt; work. After that, I go deeper into &lt;a href="https://www.angulararchitects.io/blog/ai-costs-for-angular/" rel="noopener noreferrer"&gt;AI coding costs&lt;/a&gt; and &lt;a href="https://www.angulararchitects.io/blog/ai-data-privacy-for-angular/" rel="noopener noreferrer"&gt;what AI coding tools do with your code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading 🙏 this blog post was written by &lt;a href="https://alex.thalhammer.name" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;. For feedback, remarks or questions, please reach out to me ❤️&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>angular</category>
      <category>llm</category>
    </item>
    <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="797" 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="797" 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="799" 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="570"&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="256"&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="409"&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="798" 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="250"&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="328"&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="297"&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="799" 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>
