<?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: Playful Programming Angular</title>
    <description>The latest articles on DEV Community by Playful Programming Angular (playfulprogramming-angular).</description>
    <link>https://dev.to/playfulprogramming-angular</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3316%2F612534f5-7bba-4bfe-8455-998219bc57cf.png</url>
      <title>DEV Community: Playful Programming Angular</title>
      <link>https://dev.to/playfulprogramming-angular</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/playfulprogramming-angular"/>
    <language>en</language>
    <item>
      <title>Route Resources in Angular: what the Router actually does with your Signal under the hood</title>
      <dc:creator>Nicolas Frizzarin</dc:creator>
      <pubDate>Tue, 15 Sep 2026 06:24:17 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/route-resources-in-angular-what-the-router-actually-does-with-your-signal-under-the-hood-2lc6</link>
      <guid>https://dev.to/playfulprogramming-angular/route-resources-in-angular-what-the-router-actually-does-with-your-signal-under-the-hood-2lc6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Route resolvers have been around in Angular for years, and they do their job, but they come with a price you pay on every navigation: they run one after the other, parent to child, and they can't be refreshed without triggering a full navigation. Angular now ships an alternative built directly on Signals, exposed through the router as a &lt;code&gt;resources&lt;/code&gt; route property. The official guide covers the "how to use it" part pretty well: &lt;a href="https://next.angular.dev/guide/routing/data-fetching-with-resources" rel="noopener noreferrer"&gt;https://next.angular.dev/guide/routing/data-fetching-with-resources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What the guide doesn't show you is what happens between the moment you write &lt;code&gt;resource({...})&lt;/code&gt; in your route config and the moment your component receives a value. There's an entire file dedicated to that, &lt;code&gt;router_resource.ts&lt;/code&gt;.&lt;br&gt;
This article is what I found in there (it's a depth technical article ): how blocking works, why your UI doesn't flash a loading skeleton when you navigate between two similar pages, and where a navigation cancellation can leave you stuck if you don't understand the reload guard.&lt;/p&gt;

&lt;p&gt;I'll assume you already know what a &lt;code&gt;resource()&lt;/code&gt; is (a &lt;code&gt;value()&lt;/code&gt;, an &lt;code&gt;isLoading()&lt;/code&gt;, an &lt;code&gt;error()&lt;/code&gt;, a &lt;code&gt;status()&lt;/code&gt;, and a &lt;code&gt;reload()&lt;/code&gt;, all as signals). If not, go read the &lt;a href="https://next.angular.dev/guide/signals/resource" rel="noopener noreferrer"&gt;Resource API guide&lt;/a&gt; first, this article builds on top of it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turning it on
&lt;/h2&gt;

&lt;p&gt;Route resources are gated behind a feature you have to opt into, &lt;code&gt;withRouterResources()&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;bootstrapApplication&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withComponentInputBinding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withRouterResources&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;routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.routes&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;App&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="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="nf"&gt;withRouterResources&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;Notice &lt;code&gt;withComponentInputBinding()&lt;/code&gt; next to it. It's not mandatory to make resources work, but without it you'll have to reach into &lt;code&gt;ActivatedRoute.resources&lt;/code&gt; manually instead of getting your data as a plain component &lt;code&gt;input()&lt;/code&gt;. In practice you'll almost always want both together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining resources on a route
&lt;/h2&gt;

&lt;p&gt;A route now accepts a &lt;code&gt;resources&lt;/code&gt; property, a function returning a map of &lt;code&gt;Resource&lt;/code&gt; instances:&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;inject&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;resource&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;Routes&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;ProductService&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;./product.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;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;products/:sku&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;ProductPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;productService&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;ProductService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&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;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBySku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&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;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ctx&lt;/code&gt; here is a &lt;code&gt;ResourceContext&lt;/code&gt;, giving you &lt;code&gt;params&lt;/code&gt;, &lt;code&gt;queryParams&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, and a static &lt;code&gt;snapshot&lt;/code&gt;, all as signals. The &lt;code&gt;resources&lt;/code&gt; function itself runs in an injection context, which is why &lt;code&gt;inject(ProductService)&lt;/code&gt; works directly at the top, without a factory wrapper.&lt;/p&gt;

&lt;p&gt;One detail that will bite you if you skip it: pass the primitive you actually need to &lt;code&gt;params&lt;/code&gt;, not the whole object. &lt;code&gt;params: () =&amp;gt; ctx.params()&lt;/code&gt; looks harmless, but the router creates a fresh &lt;code&gt;params&lt;/code&gt; object on every navigation, so the resource sees a new reference every time and reloads even when &lt;code&gt;sku&lt;/code&gt; didn't change. &lt;code&gt;params: () =&amp;gt; ctx.params()['sku']&lt;/code&gt; fixes that, because now the resource only reacts to the primitive value.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;resources&lt;/code&gt; can also be &lt;code&gt;async&lt;/code&gt;, if you need a dynamic import before wiring things up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetchProductBySku&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;./product-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&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;fetchProductBySku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&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;Any &lt;code&gt;Resource&lt;/code&gt; implementation is accepted, not just &lt;code&gt;resource()&lt;/code&gt;. If your team is still RxJS-heavy, &lt;code&gt;rxResource()&lt;/code&gt; works the same way, swapping &lt;code&gt;loader&lt;/code&gt; for &lt;code&gt;stream&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;rxResource&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/rxjs-interop&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nl"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;rxResource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&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;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBySku&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Blocking vs non-blocking, and what actually gets bound to your input
&lt;/h2&gt;

&lt;p&gt;By default every resource is blocking: the router waits for it before activating the route. That has a nice consequence for your component, the input type is the resolved value &lt;code&gt;T&lt;/code&gt;, not &lt;code&gt;Resource&amp;lt;T&amp;gt;&lt;/code&gt;, because your component never sees a loading or error state, the router already handled that for you.&lt;/p&gt;

&lt;p&gt;If you don't want to block navigation, wrap the resource with &lt;code&gt;nonBlocking()&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;Resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&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;Routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nonBlocking&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;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;products/:sku&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;ProductPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nonBlocking&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&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;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBySku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&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;reviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&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;reviewsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getForProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;product&lt;/code&gt; won't block navigation, &lt;code&gt;reviews&lt;/code&gt; will. Your component now has to handle both shapes at once:&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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    @if (product().isLoading()) {
      &amp;lt;p&amp;gt;Loading product…&amp;lt;/p&amp;gt;
    } @else if (product().hasValue()) {
      &amp;lt;h1&amp;gt;{{ product().value().name }}&amp;lt;/h1&amp;gt;
    }
    &amp;lt;app-reviews [reviews]="reviews" /&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;ProductPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&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="nx"&gt;reviews&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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;Review&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nonBlocking()&lt;/code&gt; itself is trivial, it just tags the resource with an internal symbol so the router knows to keep the wrapped &lt;code&gt;Resource&amp;lt;T&amp;gt;&lt;/code&gt; object as-is instead of unwrapping it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nonBlocking&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InternalRouterResource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;BLOCKING_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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 actual unwrapping for blocking resources happens in &lt;code&gt;createResourceOutletBindingEffects&lt;/code&gt;, in the same file. It uses &lt;code&gt;reflectComponentType&lt;/code&gt; to read the component's declared inputs, then for every input name that matches a key in &lt;code&gt;route.resources&lt;/code&gt; and whose &lt;code&gt;BLOCKING_SYMBOL&lt;/code&gt; isn't explicitly &lt;code&gt;false&lt;/code&gt;, it sets up an &lt;code&gt;effect()&lt;/code&gt; that calls &lt;code&gt;componentRef.setInput(templateName, resource.value())&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;templateName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;mirror&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputs&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;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;templateName&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InternalRouterResource&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nx"&gt;BLOCKING_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;continue&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;effectRef&lt;/span&gt; &lt;span class="o"&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="nx"&gt;componentRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&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="nx"&gt;componentRef&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="nx"&gt;createdEffects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;effectRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;handledKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth calling out here. First, the matching is done by input name against the resource map key, so &lt;code&gt;product: resource({...})&lt;/code&gt; only reaches an &lt;code&gt;@Input()&lt;/code&gt; (or &lt;code&gt;input()&lt;/code&gt;) literally named &lt;code&gt;product&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rename one without the other and the binding silently stops working, there's no compile-time link between the two.&lt;/p&gt;

&lt;p&gt;Second, &lt;code&gt;handledKeys&lt;/code&gt; is returned to the caller so that the router's standard data-binding subscription (&lt;code&gt;RoutedComponentInputBinder&lt;/code&gt;) skips those keys. Non-blocking resources are never added to &lt;code&gt;handledKeys&lt;/code&gt;, they go through that standard data stream instead, bound as the full &lt;code&gt;Resource&lt;/code&gt; object.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;routerResource()&lt;/code&gt; actually wraps
&lt;/h2&gt;

&lt;p&gt;Every resource you hand to the &lt;code&gt;resources&lt;/code&gt; map gets passed through &lt;code&gt;routerResource()&lt;/code&gt; internally before the router uses it. This is the function doing the heavy lifting:&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;function&lt;/span&gt; &lt;span class="nf"&gt;routerResource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;Resource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;reload&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;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;snapshotSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTransactionalSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;injector&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resourceFromSnapshots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshotSignal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InternalRouterResource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SOURCE_RESOURCE_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;BLOCKING_SYMBOL&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="nx"&gt;source&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;InternalRouterResource&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;BLOCKING_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;// ...reload wiring omitted here, see below&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&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;It doesn't touch your loader, your params function, or your original resource at all. It reads your resource's &lt;code&gt;snapshot()&lt;/code&gt; signal (a &lt;code&gt;ResourceSnapshot&lt;/code&gt;, the internal shape carrying status/value/error together) through &lt;code&gt;createTransactionalSnapshot&lt;/code&gt;, and rebuilds a brand-new &lt;code&gt;Resource&lt;/code&gt; from that snapshot stream using &lt;code&gt;resourceFromSnapshots&lt;/code&gt;. Your original resource is kept around under &lt;code&gt;SOURCE_RESOURCE_SYMBOL&lt;/code&gt;, purely so &lt;code&gt;reload()&lt;/code&gt; can delegate to it later. Everything the router hands to your component is this wrapper, not your original resource instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frozen snapshot: why navigating doesn't flash a loading spinner
&lt;/h2&gt;

&lt;p&gt;This is the part I found genuinely clever. When you navigate from &lt;code&gt;/products/1&lt;/code&gt; to &lt;code&gt;/products/2&lt;/code&gt;, both routes resolve to the same component and the same &lt;code&gt;product&lt;/code&gt; resource key, just with a different &lt;code&gt;sku&lt;/code&gt; param. Naively, that resource would immediately flip to &lt;code&gt;isLoading()&lt;/code&gt; the moment the param changes, and your component would render a loading skeleton for a split second before the new product shows up. The router avoids that by freezing the resource's snapshot for the duration of the navigation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;createTransactionalSnapshot&lt;/code&gt; subscribes to &lt;code&gt;router.events&lt;/code&gt; and reacts to five event types:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&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;else&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;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationSkipped&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;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&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;isRollback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancellationCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SupersededByNewNavigation&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancellationCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Redirect&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isRollback&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="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the exposed &lt;code&gt;snapshot&lt;/code&gt; signal is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computed&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;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the flow for a plain, successful navigation is: &lt;code&gt;NavigationStart&lt;/code&gt; fires, the router grabs whatever the source resource's snapshot currently is (the old product, already resolved) and locks it in &lt;code&gt;frozenSnapshot&lt;/code&gt;. From that point on, even though the underlying resource has already started loading the new &lt;code&gt;sku&lt;/code&gt; behind the scenes, your component keeps rendering the frozen, old value — no &lt;code&gt;isLoading&lt;/code&gt; flicker, no jump to an empty state. When &lt;code&gt;NavigationEnd&lt;/code&gt; fires, the freeze is lifted, &lt;code&gt;frozenSnapshot&lt;/code&gt; goes back to &lt;code&gt;null&lt;/code&gt;, and the &lt;code&gt;computed&lt;/code&gt; falls through to &lt;code&gt;source.snapshot()&lt;/code&gt;, which by then holds the new product.&lt;/p&gt;

&lt;p&gt;There's a subtlety in that &lt;code&gt;if (frozenSnapshot() === null)&lt;/code&gt; guard on &lt;code&gt;NavigationStart&lt;/code&gt;. If a second navigation starts while the first hasn't finished (you click fast between two product cards), the router won't re-freeze on top of an existing freeze, it keeps the original snapshot from before the first navigation began. That's intentional, it prevents freezing an intermediate, already-stale value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollback recovery: the part almost nobody thinks about
&lt;/h2&gt;

&lt;p&gt;Here's a scenario the guide only mentions briefly but the source code handles explicitly. Say a &lt;code&gt;canActivate&lt;/code&gt; guard rejects a navigation to &lt;code&gt;/products/2&lt;/code&gt;. The router cancels it and rolls the URL and route params back to &lt;code&gt;/products/1&lt;/code&gt;. That rollback is itself a change in the params signal your resource depends on, from &lt;code&gt;2&lt;/code&gt; back to &lt;code&gt;1&lt;/code&gt;, so your resource, doing exactly what it's supposed to, fires a new load for &lt;code&gt;sku: 1&lt;/code&gt; — data it already had, and had already shown a second ago.&lt;/p&gt;

&lt;p&gt;Without extra handling, that would mean: user clicks, sees the current product freeze for a moment (&lt;code&gt;NavigationStart&lt;/code&gt;), sees the guard reject it, then sees a loading flash for the product they were already looking at. The code guards against exactly that with &lt;code&gt;isRollbackRecoveryPending&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="k"&gt;else&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;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&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;isRollback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationError&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancel&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancellationCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SupersededByNewNavigation&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;NavigationCancellationCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Redirect&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isRollback&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="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cancellations caused by a newer navigation superseding this one, or by an internal redirect, are not treated as a rollback (the frozen snapshot gets released normally through the navigation that superseded it). Everything else, guard rejections, &lt;code&gt;NavigationError&lt;/code&gt;, is treated as a rollback, and the frozen snapshot is kept alive until the resource has actually settled again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isRollbackRecoveryPending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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="nx"&gt;injector&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The freeze only lifts once &lt;code&gt;source.isLoading()&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; again, which in the rollback case happens once the resource has re-fetched (or, if it's cached, re-resolved instantly) the old param value. From the user's perspective, nothing happened, the product they were looking at stayed exactly as it was, even though under the hood a full reload was silently triggered and absorbed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;reload()&lt;/code&gt; sometimes just returns &lt;code&gt;false&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Route resources expose &lt;code&gt;.reload()&lt;/code&gt;, but it's not a direct passthrough to your original resource's &lt;code&gt;.reload()&lt;/code&gt;. It's guarded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;frozenSnapshot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reload&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 call &lt;code&gt;.reload()&lt;/code&gt; while a navigation is pending, or while the router is in the middle of a rollback recovery, the call is rejected and returns &lt;code&gt;false&lt;/code&gt;, your original resource's &lt;code&gt;reload()&lt;/code&gt; is never invoked. The reasoning tracks with everything above: during those windows the router is already managing the resource's lifecycle for you, and a manual reload sneaking in would fight with that transition tracking, possibly unfreezing something the router is deliberately keeping frozen. If your source resource doesn't implement &lt;code&gt;reload&lt;/code&gt; at all (a custom &lt;code&gt;Resource&lt;/code&gt; that only has &lt;code&gt;loader&lt;/code&gt;, say), &lt;code&gt;res.reload&lt;/code&gt; is just &lt;code&gt;() =&amp;gt; false&lt;/code&gt; unconditionally.&lt;/p&gt;

&lt;p&gt;This is worth checking for in your own code. If you wire a refresh button to &lt;code&gt;userResource?.reload()&lt;/code&gt; and nothing happens, the boolean return value is telling you exactly why, it's just easy to ignore a boolean you don't check.&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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;button (click)="refresh()"&amp;gt;Refresh&amp;lt;/button&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;ProductPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;productResource&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nf"&gt;refresh&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;didReload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productResource&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;reload&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;didReload&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="s1"&gt;Reload skipped, a navigation transition is in progress.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Redirecting from inside a loader
&lt;/h2&gt;

&lt;p&gt;Blocking resources can redirect before the route ever activates, by throwing a &lt;code&gt;RedirectCommand&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;inject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&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;RedirectCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;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="nl"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;params&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;abortSignal&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBySku&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;abortSignal&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedirectCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/not-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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;abortSignal&lt;/code&gt; in the loader arguments, forward it into your fetch call. When the router supersedes a navigation or rolls it back, it aborts the in-flight request through that signal instead of letting an orphaned request finish and update state nobody asked for anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this gets uncomfortable
&lt;/h2&gt;

&lt;p&gt;Everything above is solid engineering, but a few things are worth flagging before you rely on this in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a developer preview API (22.2 at the time of writing).&lt;/strong&gt; The shapes of &lt;code&gt;ResourceContext&lt;/code&gt;, &lt;code&gt;nonBlocking&lt;/code&gt;, and the internal snapshot mechanism can change between minors. If you build a design system component library around &lt;code&gt;route.resources&lt;/code&gt;, expect to revisit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking-resource errors kill the entire navigation, not just the affected component.&lt;/strong&gt; If you have three parallel blocking resources on a route and one of them throws, the router cancels the whole navigation and fires &lt;code&gt;NavigationError&lt;/code&gt;, even if the other two resolved fine and their data would have been perfectly usable. There's no equivalent of a partial activation, you either get every blocking resource or none of them. &lt;code&gt;nonBlocking()&lt;/code&gt; sidesteps this, but only if you're willing to accept the full &lt;code&gt;Resource&amp;lt;T&amp;gt;&lt;/code&gt; object as your input type instead of the unwrapped value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The input binding is name-matching, not type-checked.&lt;/strong&gt; &lt;code&gt;createResourceOutletBindingEffects&lt;/code&gt; matches on &lt;code&gt;templateName&lt;/code&gt; between your &lt;code&gt;resources&lt;/code&gt; map and your component's declared inputs. Rename an input, rename a resource key, or introduce an alias, and the binding breaks silently. TypeScript won't catch this for you, you'll find out at runtime when the input stays &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual reload has a silent failure mode.&lt;/strong&gt; As shown above, &lt;code&gt;.reload()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; during transitions and rollback recovery instead of throwing or queuing the request. That's the right call for correctness, but it means a "refresh" button clicked at the wrong moment does nothing, and your only way to notice is checking the boolean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Params identity is a footgun the type system won't warn you about.&lt;/strong&gt; Passing &lt;code&gt;ctx.params()&lt;/code&gt; instead of &lt;code&gt;ctx.params()['id']&lt;/code&gt; compiles fine and works fine in your first manual test, then quietly reloads on every navigation once you actually have more than one route parameter changing shape. This is exactly the kind of bug that survives a demo and shows up three sprints later as "why does this refetch on every click."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing route resources properly means testing through the router.&lt;/strong&gt; Because the freezing and rollback logic lives on &lt;code&gt;router.events&lt;/code&gt; subscriptions tied to &lt;code&gt;NavigationStart&lt;/code&gt; / &lt;code&gt;NavigationEnd&lt;/code&gt; / &lt;code&gt;NavigationCancel&lt;/code&gt;, a shallow component test that stubs your service won't exercise any of that behavior. You need a &lt;code&gt;RouterTestingHarness&lt;/code&gt; (or equivalent) actually driving navigations to see the freeze/unfreeze cycle at all, plain &lt;code&gt;TestBed.createComponent&lt;/code&gt; won't cut it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Route resources solve a real problem, resolvers force a sequential waterfall and can't be refreshed without a full renavigation. What the docs describe as "the router masks loading states during pending navigations" is, under the hood, a &lt;code&gt;computed&lt;/code&gt; signal falling back between a frozen snapshot and the live one, driven by five router events and one small state machine for rollback recovery. It's not magic, it's a resource wrapper subscribing to navigation lifecycle events and freezing a value at the right moment, then releasing it at the right moment.&lt;/p&gt;

&lt;p&gt;If you're adopting this feature now, keep it away from anything load-bearing until it graduates out of developer preview, and keep an eye on that &lt;code&gt;reload()&lt;/code&gt; return value.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>web</category>
    </item>
    <item>
      <title>Optimus UI, NG Switzerland, and NgRx 22</title>
      <dc:creator>ng-news</dc:creator>
      <pubDate>Thu, 10 Sep 2026 20:05:14 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/optimus-ui-ng-switzerland-and-ngrx-22-32ag</link>
      <guid>https://dev.to/playfulprogramming-angular/optimus-ui-ng-switzerland-and-ngrx-22-32ag</guid>
      <description>&lt;p&gt;&lt;strong&gt;Optimus UI&lt;/strong&gt;, &lt;strong&gt;NG Switzerland&lt;/strong&gt;, and &lt;strong&gt;NgRx 22&lt;/strong&gt; headline this episode.&lt;/p&gt;

&lt;p&gt;Also in brief: new Angular Space podcast episodes and articles, Micro Frontends, Native Federation v4, RxJS 9, and two Angular community deep dives.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/u2WBf2F7tKI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimus UI
&lt;/h2&gt;

&lt;p&gt;Let's start with Optimus UI. In episode 26-17, we explained that PrimeNG, one of the most popular UI libraries, would become more of a commercial product. Because of that, a fork was created called Optimus UI, which keeps the library fully open source without requiring any additional licence.&lt;/p&gt;

&lt;p&gt;Optimus UI was first published as version 1. Later, version 2 followed, aligning it with Angular 22.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://optimus.openng.org/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Foptimus.openng.org%2Fopen-graph-logo.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://optimus.openng.org/" rel="noopener noreferrer" class="c-link"&gt;
            Optimus UI - Angular UI Component Library
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Optimus UI is a community-maintained, MIT licensed suite of 80+ accessible Angular UI components.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Foptimus.openng.org%2Flogo-icon.svg" width="83" height="87"&gt;
          optimus.openng.org
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://github.com/openng-org/optimus-ui" rel="noopener noreferrer"&gt;Optimus UI on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  NG Switzerland
&lt;/h2&gt;

&lt;p&gt;Dominic Bachmann, who is one of the people behind OpenNG, which hosts Optimus UI but also other projects, has now started to organize a conference in Switzerland - in Lucerne to be precise.&lt;/p&gt;

&lt;p&gt;NG Switzerland will take place on February 11-12, 2027. The call for papers is already open and ticket sales have also started. So, for us, that makes it the first already confirmed conference for next year.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://ng-switzerland.ch/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fng-switzerland.ch%2Fog-default.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://ng-switzerland.ch/" rel="noopener noreferrer" class="c-link"&gt;
            NG Switzerland — 11–12 February 2027, Lucerne, Switzerland
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            The Angular conference in the heart of Switzerland. 11–12 February 2027, Lucerne, Switzerland.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fng-switzerland.ch%2Ffavicon.svg" width="32" height="32"&gt;
          ng-switzerland.ch
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Angular Space Podcast
&lt;/h2&gt;

&lt;p&gt;Armen Vardanyan also came back from his summer holidays with his Angular Space podcast. We already had three different episodes: one about machine learning, another one about RAG, and another episode about Agentic UI, where I was also a guest.&lt;/p&gt;

&lt;p&gt;So in the end, all three new episodes focus very much on AI topics around Angular.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/JI_IwQYnEyU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Ht0g8mROV6M" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Scw5jAnVHo8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular Space Articles
&lt;/h2&gt;

&lt;p&gt;Angular Space is not just a podcast, but also a platform where different developers can publish their articles. Recently, we got four new articles by Jaroslaw Zolnowski, Paweł Ostromecki, and Emmanuel Oyibo.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.angularspace.com/how-to-handle-global-errors-in-modern-zoneless-angular/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2F2026%2F08%2Farticle-header-1200x800--9-.png" height="533" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.angularspace.com/how-to-handle-global-errors-in-modern-zoneless-angular/" rel="noopener noreferrer" class="c-link"&gt;
            How to Handle Global Errors in Modern Zoneless Angular
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2Fsize%2Fw256h256%2F2024%2F01%2F08074be6-4592-4425-9361-d2193562c3c3-removebg.png" width="256" height="256"&gt;
          angularspace.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.angularspace.com/want-to-speak-at-a-conferences-heres-what-ive-learned/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2F2026%2F09%2Farticle-header-1200x800--12-.png" height="533" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.angularspace.com/want-to-speak-at-a-conferences-heres-what-ive-learned/" rel="noopener noreferrer" class="c-link"&gt;
            Want to Speak at a Conferences? Here’s What I’ve Learned
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2Fsize%2Fw256h256%2F2024%2F01%2F08074be6-4592-4425-9361-d2193562c3c3-removebg.png" width="256" height="256"&gt;
          angularspace.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.angularspace.com/code-became-cheap-did-developer-knowledge-too/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2F2026%2F08%2Farticle-header-1200x800--10-.png" height="533" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.angularspace.com/code-became-cheap-did-developer-knowledge-too/" rel="noopener noreferrer" class="c-link"&gt;
            Code Became Cheap. Did Developer Knowledge Too?
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2Fsize%2Fw256h256%2F2024%2F01%2F08074be6-4592-4425-9361-d2193562c3c3-removebg.png" width="256" height="256"&gt;
          angularspace.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.angularspace.com/best-api-integration-platforms-for-saas-and-agents/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2F2026%2F08%2Farticle-header-1200x800--11-.jpg" height="533" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.angularspace.com/best-api-integration-platforms-for-saas-and-agents/" rel="noopener noreferrer" class="c-link"&gt;
            The best API integration platforms for SaaS apps and agents in 2026
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            A hands-on guide for Angular and TypeScript teams.  compared six platforms and focused on how easily each fits into an Angular app and how much backend control you retain when an integration goes beyond the standard flow.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstorage.ghost.io%2Fc%2F3f%2F63%2F3f6333b8-1f83-4017-8426-91bfbc264d81%2Fcontent%2Fimages%2Fsize%2Fw256h256%2F2024%2F01%2F08074be6-4592-4425-9361-d2193562c3c3-removebg.png" width="256" height="256"&gt;
          angularspace.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Micro Frontends
&lt;/h2&gt;

&lt;p&gt;Stefan Haas, Senior Software Engineer at Microsoft, wrote quite a detailed article on Micro Frontends in general. He explains the difference between the Federation approaches - Native Federation and Module Federation - and iframes. He argues that large enterprise companies very often go with iframes, not because of performance - they are actually paying a performance price - but because of the isolation. That is really important when multiple applications have been developed for many years and don't work together so easily.&lt;/p&gt;

&lt;p&gt;Stefan also mentions that the Federation approaches can be combined with Web Components. But again, that doesn't give you the same level of isolation that you find with iframes.&lt;/p&gt;

&lt;p&gt;Stefan sees Native and Module Federation as a perfect fit, especially for greenfield projects, where you can define the boundaries and make sure that everything works together. And he also mentions another, newer alternative called Web Fragments.&lt;/p&gt;

&lt;p&gt;All in all, it is a very good and detailed guide to the Micro Frontends landscape and the pros and cons of the various approaches.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://stefanhaas.dev/article/how-big-tech-builds-micro-frontends/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstefanhaas.dev%2Fog%2Fhow-big-tech-builds-micro-frontends.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://stefanhaas.dev/article/how-big-tech-builds-micro-frontends/" rel="noopener noreferrer" class="c-link"&gt;
            How Big Tech Builds Micro Frontends
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Mention iframes to a frontend developer and watch their face. Then look at what Google Cloud Console, Spotify, Salesforce and SAP actually run inside their largest products.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstefanhaas.dev%2Ffavicon.svg" width="32" height="32"&gt;
          stefanhaas.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Native Federation v4
&lt;/h2&gt;

&lt;p&gt;Staying with Micro Frontends, Angular Meetup Graz also hosted a special online meetup with multiple talks around Native Federation version 4. So, if you are interested, you can check out those talks as well. The full recording is available on YouTube.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/bG_eZM02Vfw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  NgRx 22
&lt;/h2&gt;

&lt;p&gt;NgRx 22 is finally out. The headline feature is new experimental support for resources called Resource Extensions.&lt;/p&gt;

&lt;p&gt;For example, you can apply an extension which makes the resource keep the previous value while a new value is loading after its reactive parameters change. You could also build that behavior yourself with &lt;code&gt;snapshot()&lt;/code&gt; and some additional state logic, but with Resource Extensions, you stay on the resource itself. This means you can still write to the value, and it doesn't lose the more specific type when you're using, for example, &lt;code&gt;httpResource&lt;/code&gt; or other resource types. So that is a little bit more than what you normally get with snapshots.&lt;/p&gt;

&lt;p&gt;Another tiny addition is &lt;code&gt;SignalStoreFeatureType&lt;/code&gt;. That is a utility type for custom features which makes reusing them easier.&lt;/p&gt;

&lt;p&gt;All of that and more is covered in the official NgRx blog post on dev.to.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ngrx/announcing-ngrx-v22-resource-extensions-dynamic-deep-signals-a-light-theme-and-more-5gci" class="crayons-story__hidden-navigation-link"&gt;Announcing NgRx v22: Resource Extensions, Dynamic Deep Signals, a Light Theme, and more!&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/ngrx"&gt;
            &lt;img alt="NgRx logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5016%2Fa8dc55b2-63c3-4e96-917c-2c18727a650a.png" class="crayons-logo__image" width="800" height="846"&gt;
          &lt;/a&gt;

          &lt;a href="/markostanimirovic" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F455571%2F51bedc18-8017-4569-a25d-30a74074bcb9.jpg" alt="markostanimirovic profile" class="crayons-avatar__image" width="500" height="500"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/markostanimirovic" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Marko Stanimirović
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Marko Stanimirović
                
                
              
              &lt;div id="story-author-preview-content-4469856" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/markostanimirovic" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F455571%2F51bedc18-8017-4569-a25d-30a74074bcb9.jpg" class="crayons-avatar__image" alt="" width="500" height="500"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Marko Stanimirović&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/ngrx" class="crayons-story__secondary fw-medium"&gt;NgRx&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/ngrx/announcing-ngrx-v22-resource-extensions-dynamic-deep-signals-a-light-theme-and-more-5gci" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ngrx/announcing-ngrx-v22-resource-extensions-dynamic-deep-signals-a-light-theme-and-more-5gci" id="article-link-4469856"&gt;
          Announcing NgRx v22: Resource Extensions, Dynamic Deep Signals, a Light Theme, and more!
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ngrx"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ngrx&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/angular"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;angular&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ngrx/announcing-ngrx-v22-resource-extensions-dynamic-deep-signals-a-light-theme-and-more-5gci" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/ngrx/announcing-ngrx-v22-resource-extensions-dynamic-deep-signals-a-light-theme-and-more-5gci#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  RxJS 9
&lt;/h2&gt;

&lt;p&gt;And after about two and a half years, work on the next generation of RxJS has resumed. There was never a stable version 8 of RxJS, although there were 15 alpha releases and more work was done on it. But now the work continues as RxJS version 9.&lt;/p&gt;

&lt;p&gt;The idea is that RxJS 9 builds on the native &lt;code&gt;Observable&lt;/code&gt; from the web platform. It is not yet broadly available in browsers. If the browser does not support it, RxJS installs a fallback.&lt;/p&gt;

&lt;p&gt;The first beta, &lt;code&gt;9.0.0-beta.0&lt;/code&gt;, was released on August 4, 2026, and announced on August 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ReactiveX/rxjs" rel="noopener noreferrer"&gt;RxJS on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ReactiveX/rxjs/releases/tag/9.0.0-beta.0" rel="noopener noreferrer"&gt;RxJS 9.0.0-beta.0 release&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://benlesh.com/posts/rxjs-9-built-on-the-web-platform/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;benlesh.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Angular Articles
&lt;/h2&gt;

&lt;p&gt;And then we have two further articles. The first one is more like a comment by Andrés Gesteira on whether Angular is still opinionated. It is a very good read. He analyzes the changes that we have recently seen in the Angular style guide and presents his opinion on them. So, as I said, it is more like a comment article.&lt;/p&gt;

&lt;p&gt;The other one is a technical deep dive by Jaime Burgos. His article takes a deep dive into &lt;code&gt;@defer&lt;/code&gt;. This is not just about how a defer block works or about its edge cases. It is really about how it works internally and what the compiler generates. So, if you are interested in those things, definitely have a look at it.&lt;/p&gt;

&lt;p&gt;Jaime is a very active contributor to the Angular project, so he definitely knows quite a lot about what is going on.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://quantum-quirks.hashnode.dev/is-angular-still-opinionated" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F656758a193baa1f51f2e0c22%2F54717ee2-10fa-4089-8a12-cd7b31841754.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://quantum-quirks.hashnode.dev/is-angular-still-opinionated" rel="noopener noreferrer" class="c-link"&gt;
            Is Angular Still Opinionated? The New Style Guide
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Angular’s new style guide dropped file suffixes, feature-folder rules, and CLI opinions. This article asks if the framework is still opinionated.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fquantum-quirks.hashnode.dev%2Ffavicon.ico" width="256" height="256"&gt;
          quantum-quirks.hashnode.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://medium.com/@jaimeburgostejada/inside-angulars-defer-the-instructions-behind-deferrable-views-7a40f90c5d0f" rel="noopener noreferrer"&gt;Inside Angular's @defer: The Instructions Behind Deferrable Views&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/pull/67190" rel="noopener noreferrer"&gt;Angular PR #67190&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/pull/66112" rel="noopener noreferrer"&gt;Angular PR #66112&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/pull/68333" rel="noopener noreferrer"&gt;Angular PR #68333&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/pull/70187" rel="noopener noreferrer"&gt;Angular PR #70187&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>programming</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Angular Addicts #52: Angular Addicts #52: Angular 22.1, CopilotKit, MCP Apps, Signal Form Validators &amp; more</title>
      <dc:creator>Gergely Szerovay</dc:creator>
      <pubDate>Thu, 27 Aug 2026 07:40:18 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/angular-addicts-52-angular-addicts-52-angular-221-copilotkit-mcp-apps-signal-form-3ml3</link>
      <guid>https://dev.to/playfulprogramming-angular/angular-addicts-52-angular-addicts-52-angular-221-copilotkit-mcp-apps-signal-form-3ml3</guid>
      <description>&lt;h2&gt;
  
  
  👋Hey fellow Angular Addict
&lt;/h2&gt;

&lt;p&gt;This is the 52nd issue of the Angular Addicts Newsletter, a monthly collection of carefully selected Angular resources that caught my attention. (Here are the &lt;a href="https://www.angularaddicts.com/p/angular-addicts-51-a2ui-signal-debouncing" rel="noopener noreferrer"&gt;51st&lt;/a&gt;, &lt;a href="https://www.angularaddicts.com/p/angular-addicts-50-angular-22-agentic-deploying" rel="noopener noreferrer"&gt;50th&lt;/a&gt; and &lt;a href="https://www.angularaddicts.com/p/angular-addicts-49-analogjs-25-agentic-angular" rel="noopener noreferrer"&gt;49th&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  📢 Release announcements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📢 &lt;a href="https://blog.ninja-squad.com/2026/07/29/what-is-new-angular-22.1" rel="noopener noreferrer"&gt;What's new in Angular 22.1?&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/exbrayatcedric/" rel="noopener noreferrer"&gt;Cédric Exbrayat&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📢 &lt;a href="https://dev.to/copilotkit/introducing-angular-support-for-copilotkit-bring-any-agent-into-your-app-2f3e"&gt;Introducing Angular support for CopilotKit: bring any Agent into your app&lt;/a&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  📢 &lt;a href="https://nx.dev/blog/nx-23-1-release" rel="noopener noreferrer"&gt;Nx 23.1 Is Here: Per-Run Performance Reports, TypeScript 6, and Angular 22 | Nx Blog&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/juristr/" rel="noopener noreferrer"&gt;Juri Strumpflohner&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💎Angular Gems of July, 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/brandontroberts/choosing-angular-in-the-age-of-agents-1bo7"&gt;Choosing Angular in the Age of Agents&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/brandontroberts/" rel="noopener noreferrer"&gt;Brandon Roberts&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://angular.love/one-validator-to-rule-them-all-part-2-advanced-error-handling-with-multi-language-support" rel="noopener noreferrer"&gt;One Validator to Rule Them All — Part 2: Advanced Error Handling with multi-language support&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/gabor-kotel/" rel="noopener noreferrer"&gt;Gábor Kötél&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://www.angulararchitects.io/en/blog/mcp-apps-in-angular-with-copilotkit-rich-chat-interfaces-instead-of-text-responses/" rel="noopener noreferrer"&gt;MCP Apps in Angular with CopilotKit: Rich Chat Interfaces Instead of Text Responses&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/manfred-steyer-84645821/" rel="noopener noreferrer"&gt;Manfred Steyer&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://push-based.io/article/faster-builds-slower-applications-optimizing-angulars-bundle-output" rel="noopener noreferrer"&gt;Faster Builds, Slower Applications? Optimizing Angular's Bundle Output&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/christopher-p-holder/" rel="noopener noreferrer"&gt;Christopher Holder&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://blog.angulartraining.com/how-to-add-async-http-validators-to-angular-signal-forms-df020267c130" rel="noopener noreferrer"&gt;How to Add Async HTTP Validators to Angular Signal Forms&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/achautard/" rel="noopener noreferrer"&gt;Alain Chautard&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  👨‍💻About the author
&lt;/h2&gt;

&lt;p&gt;My name is &lt;a href="https://www.linkedin.com/in/gergelyszerovay/" rel="noopener noreferrer"&gt;Gergely Szerovay&lt;/a&gt;, I worked as a data scientist and full-stack developer for many years, and I have been working as frontend tech lead, focusing on Angular based frontend development. As part of my role, I'm constantly following how Angular and the frontend development scene in general is evolving. To share my knowledge, I started the &lt;a href="https://angularaddicts.com/" rel="noopener noreferrer"&gt;Angular Addicts&lt;/a&gt; monthly newsletter and publication in 2022, so that I can send you the best resources I come across each month. Whether you are a seasoned Angular Addict or a beginner, I got you covered. Let me know if you would like to be included as a writer. Let’s learn Angular together! &lt;a href="https://www.angularaddicts.com/" rel="noopener noreferrer"&gt;Subscribe here&lt;/a&gt; 🔥&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.angularaddicts.com/" rel="noopener noreferrer"&gt;Substack (Angular Addicts)&lt;/a&gt;, &lt;a href="https://aiboosted.dev" rel="noopener noreferrer"&gt;Substack (AIBoosted.dev)&lt;/a&gt;, &lt;a href="https://medium.com/@GergelySzerovay" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;, &lt;a href="https://dev.to/gergelyszerovay"&gt;Dev.to&lt;/a&gt;, &lt;a href="https://twitter.com/GergelySzerovay" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/gergelyszerovay/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to learn more about Angular, and how to build AI apps with AI, Typescript, React and Angular!&lt;/p&gt;

&lt;h2&gt;
  
  
  🕹️Previous issues
&lt;/h2&gt;

&lt;p&gt;If you missed the previous issues of the newsletter, you can read them &lt;a href="https://www.angularaddicts.com/t/angular-addicts-monthly" rel="noopener noreferrer"&gt;here&lt;/a&gt;, these are the latest 3 issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-51-a2ui-signal-debouncing" rel="noopener noreferrer"&gt;Angular Addicts #51: spartan/ui 1.0, A2UI, Signal Debouncing &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-50-angular-22-agentic-deploying" rel="noopener noreferrer"&gt;Angular Addicts #50: Angular 22, Agentic Angular, Deploying to the Cloud &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-49-analogjs-25-agentic-angular" rel="noopener noreferrer"&gt;Angular Addicts #49: AnalogJS 2.5, Agentic Angular &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📨 Submit your Angular resource
&lt;/h2&gt;

&lt;p&gt;Have you found or written an interesting Angular-related article, tweet or other resource lately? Please let me know here in the comments or send me a DM on &lt;a href="https://twitter.com/gergelyszerovay" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;! I might feature it in the next Angular Addicts issue!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ng-News 26/19: Angular 22.1</title>
      <dc:creator>ng-news</dc:creator>
      <pubDate>Sat, 22 Aug 2026 13:27:49 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/ng-news-2619-angular-221-2geb</link>
      <guid>https://dev.to/playfulprogramming-angular/ng-news-2619-angular-221-2geb</guid>
      <description>&lt;p&gt;Angular 22.1 adds a custom &lt;strong&gt;&lt;code&gt;set&lt;/code&gt; function for &lt;code&gt;linkedSignal&lt;/code&gt;&lt;/strong&gt;. It gives us something like two-way binding between signals while keeping one signal as the source of truth.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ai_LqkUXmDE"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular 22.1
&lt;/h2&gt;

&lt;p&gt;Angular 22.1 is the first minor release in the Angular 22 range. With Angular's yearly major release cycle, we can expect multiple minor releases during the year.&lt;/p&gt;

&lt;p&gt;As a minor release, 22.1 can introduce new features without breaking existing applications. The main feature for this episode is the custom &lt;code&gt;set&lt;/code&gt; function of &lt;code&gt;linkedSignal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Until now, &lt;code&gt;linkedSignal&lt;/code&gt; could derive a writable value from another signal. With the custom setter, writing to that linked value can also update the source signal.&lt;/p&gt;

&lt;p&gt;That gives us something like two-way binding between signals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/releases/tag/22.1.0" rel="noopener noreferrer"&gt;Angular 22.1 release notes&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://angular.dev/reference/releases" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fangular.dev%2Fassets%2Fimages%2Fng-image.jpg" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://angular.dev/reference/releases" rel="noopener noreferrer" class="c-link"&gt;
            Versioning and releases • Angular
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            The web development framework for building modern apps.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fangular.dev%2Fassets%2Ficons%2Ffavicon-48x48.png" width="48" height="48"&gt;
          angular.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  A currency converter in both directions
&lt;/h2&gt;

&lt;p&gt;Consider a currency converter with two input fields: one for euros and another for dollars.&lt;/p&gt;

&lt;p&gt;When the user changes the euro value, the dollar value should update immediately. The same should work in the other direction: changing dollars should update euros.&lt;/p&gt;

&lt;p&gt;The euro signal remains the source of truth. The dollar signal is linked to it and derives its value using the exchange rate.&lt;/p&gt;

&lt;p&gt;Before Angular 22.1, the other direction required an &lt;code&gt;effect&lt;/code&gt;. The effect observed changes to the dollar value and wrote the converted result back to the euro signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: synchronizing with an effect
&lt;/h3&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;effect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;linkedSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;untracked&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;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;required&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/forms/signals&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-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormField&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;main class="mx-auto mt-16 grid max-w-md gap-4 p-4"&amp;gt;
      &amp;lt;h1 class="text-2xl font-semibold"&amp;gt;EUR ↔ USD&amp;lt;/h1&amp;gt;
      &amp;lt;label class="grid gap-1 text-sm"&amp;gt;
        EUR
        &amp;lt;input class="rounded border p-2" [formField]="eurForm" type="number" step="0.01" /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;label class="grid gap-1 text-sm"&amp;gt;
        USD
        &amp;lt;input class="rounded border p-2" [formField]="usdForm" type="number" step="0.01" /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;button
        class="cursor-pointer rounded bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700"
        (click)="resetEur()"
      &amp;gt;
        Set USD to 100
      &amp;lt;/button&amp;gt;
    &amp;lt;/main&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;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;eurValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;eurForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usdValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;linkedSignal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;source&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;computation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eurValue&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eurValue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usdForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&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;usdValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;usd&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;usdForm&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="nf"&gt;untracked&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;eur&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1.16&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;eur&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eur&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;span class="nf"&gt;resetEur&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;usdValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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="s2"&gt;`USD &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;usdValue&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; are in EUR &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;eurValue&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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 forward direction already belongs to &lt;code&gt;linkedSignal&lt;/code&gt;: whenever &lt;code&gt;eurValue&lt;/code&gt; changes, the computation derives the dollar value synchronously.&lt;/p&gt;

&lt;p&gt;The reverse direction needs more machinery. The &lt;code&gt;effect&lt;/code&gt; observes the dollar form, &lt;code&gt;untracked&lt;/code&gt; prevents the write to &lt;code&gt;eurValue&lt;/code&gt; from becoming another dependency, and the equality check avoids an unnecessary update.&lt;/p&gt;

&lt;p&gt;There is another difference: effects run asynchronously. If code sets the dollar value and reads the euro value immediately afterwards, it can still see the old euro value until the effect runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  After: writing back with linkedSignal
&lt;/h2&gt;

&lt;p&gt;Angular 22.1 allows &lt;code&gt;linkedSignal&lt;/code&gt; to define what should happen when someone calls &lt;code&gt;set&lt;/code&gt; or &lt;code&gt;update&lt;/code&gt; on it. The write-back logic can therefore live directly beside the computation.&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;linkedSignal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&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;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FormField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;required&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/forms/signals&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-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;FormField&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;main class="mx-auto mt-16 grid max-w-md gap-4 p-4"&amp;gt;
      &amp;lt;h1 class="text-2xl font-semibold"&amp;gt;EUR ↔ USD&amp;lt;/h1&amp;gt;
      &amp;lt;label class="grid gap-1 text-sm"&amp;gt;
        EUR
        &amp;lt;input class="rounded border p-2" [formField]="eurForm" type="number" step="0.01" /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;label class="grid gap-1 text-sm"&amp;gt;
        USD
        &amp;lt;input class="rounded border p-2" [formField]="usdForm" type="number" step="0.01" /&amp;gt;
      &amp;lt;/label&amp;gt;

      &amp;lt;button
        class="cursor-pointer rounded bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700"
        (click)="resetEur()"
      &amp;gt;
        Set USD to 100
      &amp;lt;/button&amp;gt;
    &amp;lt;/main&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;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;eurValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;eurForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usdValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;linkedSignal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;source&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;computation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eurValue&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eurValue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;eurValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&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;usdValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1.16&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;usdForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;form&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;usdValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nf"&gt;resetEur&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;usdValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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="s2"&gt;`USD &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;usdValue&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; are in EUR &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;eurValue&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="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;toMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;effect&lt;/code&gt;, &lt;code&gt;untracked&lt;/code&gt;, and constructor disappear. The &lt;code&gt;linkedSignal&lt;/code&gt; now owns both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;computation&lt;/code&gt; derives dollars whenever the euro source changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;set&lt;/code&gt; handles explicit writes to the dollar signal and writes back to the euro source.&lt;/li&gt;
&lt;li&gt;The source update causes the linked value to be recomputed synchronously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a smaller implementation in which the read and write relationship is declared in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  A custom setter is not a general-purpose effect
&lt;/h2&gt;

&lt;p&gt;The custom &lt;code&gt;set&lt;/code&gt; function runs whenever code explicitly writes to the linked signal. That makes it powerful enough to execute other logic and react to multiple synchronous writes individually.&lt;/p&gt;

&lt;p&gt;Technically, that could be used to work around the glitch-free behavior of an &lt;code&gt;effect&lt;/code&gt;. But that is not the intended pattern. The custom setter is most useful when it expresses how a write to derived state should update its source of truth.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>programming</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Angular pattern that needs no Provider</title>
      <dc:creator>Michele Stieven</dc:creator>
      <pubDate>Sun, 16 Aug 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/the-angular-pattern-that-needs-no-provider-2ee4</link>
      <guid>https://dev.to/playfulprogramming-angular/the-angular-pattern-that-needs-no-provider-2ee4</guid>
      <description>&lt;p&gt;Services are often the default abstraction for reusable Angular logic. But what if the logic is local to a component, depends on its injection context, and does not represent shared application state?&lt;/p&gt;

&lt;p&gt;Consider a dashboard widget that knows whether its host element is currently visible:&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;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-dashboard-widget&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;p&amp;gt;{{ visible() ? 'Awake' : 'Sleeping' }}&amp;lt;/p&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;DashboardWidget&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectVisibility&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;That information can be used to avoid work the user cannot currently see.&lt;/p&gt;

&lt;p&gt;For example, we can avoid creating an expensive part of the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (visible()) {
  &lt;span class="nt"&gt;&amp;lt;app-expensive-chart&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;Or pause and resume an asynchronous process:&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;readonly&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;toObservable&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;visible&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;visible&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;switchMap&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;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadStatistics&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="nx"&gt;EMPTY&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same pattern can suspend polling, animations, data transformations or expensive visualizations while the component is outside the viewport.&lt;/p&gt;

&lt;p&gt;There is no directive in the template, no lifecycle hook and, most importantly, no provider to configure.&lt;/p&gt;

&lt;p&gt;Let’s see how we get there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The component-scoped service
&lt;/h2&gt;

&lt;p&gt;A first solution could be a service that injects the component’s &lt;code&gt;ElementRef&lt;/code&gt;, creates an &lt;code&gt;IntersectionObserver&lt;/code&gt; and exposes the result as a Signal.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VisibilityService&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;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;inject&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="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asReadonly&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&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;host&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="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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;For the service to resolve the correct host element, however, it must be created in the component’s element 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="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;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;VisibilityService&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;DashboardWidget&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;visible&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;VisibilityService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;visible&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 works, but the API has a hidden requirement: every consumer must remember to declare the provider.&lt;/p&gt;

&lt;p&gt;Forget it, and the feature stops working.&lt;/p&gt;

&lt;p&gt;A root service would not solve the problem either. A singleton has no single component host associated with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The host directive
&lt;/h2&gt;

&lt;p&gt;A host directive models the responsibility more accurately:&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;hostDirectives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;VisibilityDirective&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;DashboardWidget&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;visible&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;VisibilityDirective&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;visible&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 behavior is nicely encapsulated, but we still have metadata boilerplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;hostDirectives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;VisibilityDirective&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For behavior that should also be selectable from a template, this is perfectly reasonable. But our component only needs a contextual value: a Signal tied to its own host and lifecycle.&lt;/p&gt;

&lt;p&gt;Do we really need another injectable instance for that?&lt;/p&gt;

&lt;h2&gt;
  
  
  A custom inject function
&lt;/h2&gt;

&lt;p&gt;Angular’s &lt;code&gt;inject()&lt;/code&gt; API is not limited to services. It can also be used inside a function called while an injection context is active, for example during a component field initializer.&lt;/p&gt;

&lt;p&gt;That lets us implement the entire feature as a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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;ElementRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;assertInInjectionContext&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;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;injectVisibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserverInit&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="nx"&gt;Signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;assertInInjectionContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;injectVisibility&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;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;inject&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="nx"&gt;ElementRef&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;nativeElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&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;visible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asReadonly&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;Usage 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;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-dashboard-widget&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;p&amp;gt;{{ visible() ? 'Awake' : 'Sleeping' }}&amp;lt;/p&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;DashboardWidget&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectVisibility&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;200px&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;p&gt;The function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;finds the consumer’s host element;&lt;/li&gt;
&lt;li&gt;creates private state for that consumer;&lt;/li&gt;
&lt;li&gt;connects the cleanup to its lifecycle;&lt;/li&gt;
&lt;li&gt;returns a normal readonly Signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No service instance or component metadata is required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the call location matters
&lt;/h2&gt;

&lt;p&gt;This works because field initializers run while Angular is creating the component:&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;DashboardWidget&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectVisibility&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;Calling the same function later does not work:&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;DashboardWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;startObserving&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;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectVisibility&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Error&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;By then, the component creation context has ended. &lt;code&gt;assertInInjectionContext()&lt;/code&gt; makes this constraint immediately clear to consumers of the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use this pattern?
&lt;/h2&gt;

&lt;p&gt;A custom inject function is a good fit when the feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;depends on the caller’s injection context;&lt;/li&gt;
&lt;li&gt;creates state local to each consumer;&lt;/li&gt;
&lt;li&gt;needs contextual dependencies such as &lt;code&gt;ElementRef&lt;/code&gt; or &lt;code&gt;DestroyRef&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;does not represent a shared service;&lt;/li&gt;
&lt;li&gt;can expose its result through an ordinary value, Signal or Observable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A service remains the better abstraction when it has shared state, a meaningful identity, or implementations that consumers need to replace through dependency injection.&lt;/p&gt;

&lt;p&gt;The important distinction is not whether some code uses Angular DI.&lt;/p&gt;

&lt;p&gt;It is whether that code needs to be an injectable object.&lt;/p&gt;

&lt;p&gt;Sometimes the cleanest injectable Angular API is just a function.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ng-News 26/18: Angular Annual Releases, CopilotKit Integration</title>
      <dc:creator>ng-news</dc:creator>
      <pubDate>Thu, 06 Aug 2026 18:27:06 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/ng-news-2618-angular-annual-releases-copilotkit-integration-22hk</link>
      <guid>https://dev.to/playfulprogramming-angular/ng-news-2618-angular-annual-releases-copilotkit-integration-22hk</guid>
      <description>&lt;p&gt;Angular is moving to one major release per year, and CopilotKit for Angular is now officially available.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/bU8i0LHCyvQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Angular Release Cadence
&lt;/h2&gt;

&lt;p&gt;The Angular team decided to switch the release interval of major versions from half a year to one year. That is effective immediately and it also means that we won't get Angular 23 later this year, but in May or early June 2027.&lt;/p&gt;

&lt;p&gt;That does not mean that new features land later, because adding new features is also allowed in minor versions. The same is also true when it comes to upgrading a certain feature from experimental or developer preview status.&lt;/p&gt;

&lt;p&gt;What changes with the major is the amount of breaking changes. They can only now happen once per year. According to the Angular team, the half-year pace was reported to be an issue for enterprises. One also has to say that one major per year also makes it easier for library developers because the amount of work for updating to the new Angular major is now half the work.&lt;/p&gt;

&lt;p&gt;Speaking of minor releases, version 22.1 was already released and will be covered in the next episode. We can expect 4-6 minor releases instead of a maximum of three as it was before.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://angular.dev/reference/releases" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fangular.dev%2Fassets%2Fimages%2Fng-image.jpg" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://angular.dev/reference/releases" rel="noopener noreferrer" class="c-link"&gt;
            Versioning and releases • Angular
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            The web development framework for building modern apps.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fangular.dev%2Fassets%2Ficons%2Ffavicon-48x48.png" width="48" height="48"&gt;
          angular.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  CopilotKit for Angular
&lt;/h2&gt;

&lt;p&gt;CopilotKit is behind the protocol AG-UI, which is the foundation for agent-driven UIs, including dynamic UI generated at runtime by AI - formats like A2UI run on top of it.&lt;/p&gt;

&lt;p&gt;Their Angular support was not open source, but that changed. The code went public in June, followed by the official announcement in July.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For ng-news there are strict rules and one of them is no self-promotion, unless the speaker is part of a team, which is the case here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation for Angular was done in collaboration with Soverius AI, which is now maintaining the Angular part. Soverius AI consists of Murat Sari and me, Rainer Hahnekamp.&lt;/p&gt;

&lt;p&gt;The Angular integration is part of the CopilotKit repository. The announcement blog links to a demo application which showcases several features, like tool calling, A2UI, Generative UI, and more.&lt;/p&gt;

&lt;p&gt;Other content creators have already updated their content. For example, Manfred Steyer with his article series on Agentic UI.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.copilotkit.ai/blog/copilotkit-for-angular" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;copilotkit.ai&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://soverius.ai/blog/soverius-ai-is-now-maintaining-copilotkit-for-angular" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;soverius.ai&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.angulararchitects.io/en/blog/integrating-a2ui-with-ag-ui-in-angular/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2026%2F05%2Fseries-1024x536.png" height="419" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.angulararchitects.io/en/blog/integrating-a2ui-with-ag-ui-in-angular/" rel="noopener noreferrer" class="c-link"&gt;
            Integrating A2UI with AG-UI and CopilotKit in Angular - ANGULARarchitects
          &lt;/a&gt;
        &lt;/h2&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.angulararchitects.io%2Fwp-content%2Fuploads%2F2024%2F01%2Fcropped-2023_Angular_Architects_FavIcon-32x32.png" width="32" height="32"&gt;
          angulararchitects.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>angular</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Give Your Angular Modal a URL: Three Router-Based Approaches</title>
      <dc:creator>Michele Stieven</dc:creator>
      <pubDate>Sun, 02 Aug 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/give-your-angular-modal-a-url-three-router-based-approaches-2k10</link>
      <guid>https://dev.to/playfulprogramming-angular/give-your-angular-modal-a-url-three-router-based-approaches-2k10</guid>
      <description>&lt;p&gt;Opening a modal is easy. Giving it a URL, and making that URL behave like a real part of the application, is where things become interesting.&lt;/p&gt;

&lt;p&gt;Let’s look at three ways to connect a modal to the modern Angular Router, including the trade-offs that are easy to miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why route a modal at all?
&lt;/h2&gt;

&lt;p&gt;The usual modal starts with a click handler:&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;(click)=&lt;/span&gt;&lt;span class="s"&gt;"openEditDialog(product.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Edit&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;That works, but the modal exists only in memory. The browser does not know about it.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refreshing the page closes it;&lt;/li&gt;
&lt;li&gt;the Back button may leave the page instead of closing it;&lt;/li&gt;
&lt;li&gt;the Forward button cannot reopen it;&lt;/li&gt;
&lt;li&gt;you cannot bookmark or share the modal;&lt;/li&gt;
&lt;li&gt;analytics cannot naturally distinguish the underlying page from the modal state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the times, that is completely fine. A confirmation dialog such as “Delete this draft?” probably does not need a permanent address.&lt;/p&gt;

&lt;p&gt;But a product editor, login form, checkout step, image preview, or share panel may deserve one.&lt;/p&gt;

&lt;p&gt;For the examples below, imagine a products page with an edit modal. I will use Angular Material’s &lt;code&gt;MatDialog&lt;/code&gt;, but the routing strategies do not depend on Material. Near the end, we will connect the same idea to a plain HTML modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  First rule: the URL as the source of truth
&lt;/h2&gt;

&lt;p&gt;Before choosing a URL shape, we need one rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Navigation opens the modal, and another navigation closes it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A common half-solution is to update the URL when the user clicks a button, then call &lt;code&gt;dialog.open()&lt;/code&gt; separately. That creates two sources of truth. A deep link may update the URL without opening anything, while closing the dialog may leave a modal-looking URL behind.&lt;/p&gt;

&lt;p&gt;Instead, our flow should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user action → router navigation → route state changes → modal opens
modal closes → router navigation → route state changes → modal stays closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also means the modal must react when route state changes because of Back, Forward, a redirect, or a pasted URL, not only because of our own button.&lt;/p&gt;

&lt;p&gt;With that in mind, let’s choose where the modal state lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: a query parameter
&lt;/h2&gt;

&lt;p&gt;The smallest change is to represent the modal with a query parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products?dialog=edit&amp;amp;productId=42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: “We are still on &lt;code&gt;/products&lt;/code&gt;, with some optional UI state layered on top.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening it
&lt;/h3&gt;

&lt;p&gt;The trigger is a normal router 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="nt"&gt;&amp;lt;a&lt;/span&gt;
  &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"[]"&lt;/span&gt;
  &lt;span class="na"&gt;[queryParams]=&lt;/span&gt;&lt;span class="s"&gt;"{ dialog: 'edit', productId: product.id }"&lt;/span&gt;
  &lt;span class="na"&gt;queryParamsHandling=&lt;/span&gt;&lt;span class="s"&gt;"merge"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Edit
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a link rather than a click handler gives us standard browser behaviour: users can open it in a new tab, copy its address, and use keyboard navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping &lt;code&gt;MatDialog&lt;/code&gt; in sync
&lt;/h3&gt;

&lt;p&gt;The products page observes the query parameters and owns the dialog instance:&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;inject&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouterLink&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;MatDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatDialogRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/material/dialog&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;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;take&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;rxjs&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;takeUntilDestroyed&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/rxjs-interop&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-products-page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RouterLink&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./products-page.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsPage&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;route&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dialog&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;MatDialog&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="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;MatDialogRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EditProductDialog&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="nx"&gt;openProductId&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="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryParamMap&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;params&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;isEditDialog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isEditDialog&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;params&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;productId&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nf"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;productId&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="nf"&gt;syncDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;syncDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;productId&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;openProductId&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&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="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="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="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;openProductId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;productId&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="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;dialogRef&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openProductId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;productId&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;dialogRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EditProductDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;ariaLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Edit product&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;dialogRef&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterClosed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Ignore a previous dialog closing after a new ID replaced it.&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="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;dialogRef&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="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;dialogRef&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openProductId&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="c1"&gt;// If Back already removed the parameter, do not navigate again.&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="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryParamMap&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;edit&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;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;relativeTo&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;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;queryParamsHandling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;replaceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="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;There is a little more code here than in the usual &lt;code&gt;dialog.open()&lt;/code&gt; example because we are handling both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL opens or replaces the dialog;&lt;/li&gt;
&lt;li&gt;removing the parameters closes it;&lt;/li&gt;
&lt;li&gt;closing it removes the parameters;&lt;/li&gt;
&lt;li&gt;closing it &lt;em&gt;because Back already changed the URL&lt;/em&gt; does not trigger a second navigation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That final check prevents a subtle history bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;replaceUrl&lt;/code&gt; when closing?
&lt;/h3&gt;

&lt;p&gt;Opening the modal creates a useful history entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products
/products?dialog=edit&amp;amp;productId=42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user presses Back, Angular returns to &lt;code&gt;/products&lt;/code&gt; and our subscription closes the dialog.&lt;/p&gt;

&lt;p&gt;If the user clicks the modal’s close button, we remove the query parameters with &lt;code&gt;replaceUrl: true&lt;/code&gt;. This avoids adding yet another entry just for the close operation.&lt;/p&gt;

&lt;p&gt;History policy is a product choice, though. If moving from “open” to “closed” should itself be a revisit-able step, omit &lt;code&gt;replaceUrl&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When query parameters work well
&lt;/h3&gt;

&lt;p&gt;This approach is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the modal is optional UI state on the current page;&lt;/li&gt;
&lt;li&gt;it may appear over several primary routes;&lt;/li&gt;
&lt;li&gt;you want a small routing change;&lt;/li&gt;
&lt;li&gt;filters, pagination, and the modal already coexist in the query string.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its weaknesses are equally clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the owning page needs modal-coordination code;&lt;/li&gt;
&lt;li&gt;query parameters are global to the URL, so naming collisions are possible;&lt;/li&gt;
&lt;li&gt;URLs become untidy if you encode a lot of modal data;&lt;/li&gt;
&lt;li&gt;the modal has no route lifecycle of its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pass stable identifiers in the URL, not an entire object. &lt;code&gt;/products?productId=42&lt;/code&gt; can be refreshed; a JavaScript object passed only through navigation memory cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: a child path
&lt;/h2&gt;

&lt;p&gt;Maybe editing product 42 feels less like an optional flag and more like a nested destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products/42/edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a strong, readable URL. The trick is keeping the products page rendered underneath it.&lt;/p&gt;

&lt;p&gt;We can make the edit route a child of the products page:&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;Routes&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;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;products&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;ProductsPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&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="s1"&gt;:productId/edit&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;EditProductDialogRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parent template needs an outlet where Angular can activate that child route:&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;h1&amp;gt;&lt;/span&gt;Products&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;app-product-list&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- The route component renders no visible page content here. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;router-outlet&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;And the link becomes pleasantly ordinary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"[product.id, 'edit']"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Edit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A route component that opens the dialog
&lt;/h3&gt;

&lt;p&gt;Angular route configuration activates components; it does not execute arbitrary “open modal” functions. We therefore use a small route component as the bridge:&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;inject&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;takeUntilDestroyed&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/rxjs-interop&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;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;MatDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MatDialogRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/material/dialog&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;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;take&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;rxjs&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-edit-product-dialog-route&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="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;EditProductDialogRoute&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;route&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;dialog&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;MatDialog&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="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;MatDialogRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EditProductDialog&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="nx"&gt;closingBecauseRouteChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paramMap&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;params&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;params&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;productId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;takeUntilDestroyed&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateByUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;replaceUrl&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="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;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="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;closingBecauseRouteChanged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;dialogRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&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;dialogRef&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="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;openDialog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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="c1"&gt;// Angular can reuse this route component when only productId changes.&lt;/span&gt;
    &lt;span class="c1"&gt;// Invalidate the previous reference before closing it, so its&lt;/span&gt;
    &lt;span class="c1"&gt;// afterClosed callback cannot navigate away from the new dialog.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&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;dialogRef&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="nx"&gt;previousRef&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EditProductDialog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;ariaLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Edit product&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dialogRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;dialogRef&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterClosed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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="nx"&gt;dialogRef&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;dialogRef&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="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;dialogRef&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&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="nx"&gt;closingBecauseRouteChanged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigateByUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;replaceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="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;Observing &lt;code&gt;paramMap&lt;/code&gt; matters because Angular normally reuses the route component when only a parameter changes. Navigating directly from &lt;code&gt;/products/42/edit&lt;/code&gt; to &lt;code&gt;/products/43/edit&lt;/code&gt; does not run the constructor again; the subscription closes the old dialog and opens the new one.&lt;/p&gt;

&lt;p&gt;The reference identity check then ignores the old dialog's late &lt;code&gt;afterClosed()&lt;/code&gt; emission. Otherwise, replacing product 42 with product 43 could accidentally navigate back to &lt;code&gt;/products&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The destruction handler covers a different case. If the user presses Back or navigates elsewhere while the dialog is open, Angular destroys the route component and the component closes the overlay.&lt;/p&gt;

&lt;p&gt;The boolean prevents that programmatic close from sending the user back to &lt;code&gt;/products&lt;/code&gt; after they have already navigated somewhere else. Without it, clicking a link to &lt;code&gt;/orders&lt;/code&gt; could briefly navigate there and then be “corrected” back to &lt;code&gt;/products&lt;/code&gt; by a late &lt;code&gt;afterClosed()&lt;/code&gt; callback. Not fun.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid hard-coding the parent when needed
&lt;/h3&gt;

&lt;p&gt;The example navigates to &lt;code&gt;/products&lt;/code&gt; for clarity. In a reusable feature, you may prefer relative navigation or an explicit return target.&lt;/p&gt;

&lt;p&gt;Be careful with &lt;code&gt;history.back()&lt;/code&gt; as a universal close implementation. It feels elegant when the modal was opened from &lt;code&gt;/products&lt;/code&gt;, but a user may arrive directly at &lt;code&gt;/products/42/edit&lt;/code&gt;. In that case, Back could leave your application entirely.&lt;/p&gt;

&lt;p&gt;A deterministic parent URL is usually safer. If preserving the exact background state matters (filters included) encode that state in the URL or adopt the auxiliary-route approach below.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a child path works well
&lt;/h3&gt;

&lt;p&gt;Choose this approach when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the modal belongs to one clear parent page;&lt;/li&gt;
&lt;li&gt;the URL should read like a resource or operation;&lt;/li&gt;
&lt;li&gt;guards and resolvers should apply specifically to the modal;&lt;/li&gt;
&lt;li&gt;direct links such as &lt;code&gt;/products/42/edit&lt;/code&gt; make sense to users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the parent must remain active and expose a child outlet;&lt;/li&gt;
&lt;li&gt;a small route component is needed when using &lt;code&gt;MatDialog&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;reusing the same modal over unrelated pages requires repeated child routes or a broader layout;&lt;/li&gt;
&lt;li&gt;the URL alone identifies the child destination, but not an arbitrary underlying page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This option is often the nicest compromise for resource editors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: an auxiliary route
&lt;/h2&gt;

&lt;p&gt;Angular can activate more than one route at the same time. A shell component can expose a primary outlet for the page and a named outlet for the modal:&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;router-outlet&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;router-outlet&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"modal"&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;That lets the URL describe both states independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products(modal:edit/42)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax is unusual at first, but the model is precise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;primary route: &lt;code&gt;products&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modal&lt;/code&gt; outlet: &lt;code&gt;edit/42&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Route configuration
&lt;/h3&gt;

&lt;p&gt;Both routes should be children of the shell that owns those outlets:&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;Routes&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;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="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;AppShell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&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="s1"&gt;products&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;ProductsPage&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="s1"&gt;edit/:productId&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;EditProductDialogRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;outlet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modal&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a link is created inside &lt;code&gt;ProductsPage&lt;/code&gt;, its current route is the primary child. The &lt;code&gt;modal&lt;/code&gt; outlet, however, belongs to the parent shell. We can create the &lt;code&gt;UrlTree&lt;/code&gt; relative to that parent:&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UrlTree&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductsPage&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;route&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;editModalUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;UrlTree&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUrlTree&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;outlets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&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;span class="na"&gt;relativeTo&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;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;queryParamsHandling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preserve&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="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;a&lt;/span&gt; &lt;span class="na"&gt;[routerLink]=&lt;/span&gt;&lt;span class="s"&gt;"editModalUrl(product.id)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Edit&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Closing a named outlet means navigating with that outlet set to &lt;code&gt;null&lt;/code&gt;, again relative to the shell:&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&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;outlets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;span class="na"&gt;relativeTo&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;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&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;Our reactive &lt;code&gt;EditProductDialogRoute&lt;/code&gt; can be reused here, including its &lt;code&gt;paramMap&lt;/code&gt; subscription: changing &lt;code&gt;/products(modal:edit/42)&lt;/code&gt; to &lt;code&gt;/products(modal:edit/43)&lt;/code&gt; can reuse the same route component too. The only change is what happens after the dialog closes: instead of navigating to &lt;code&gt;/products&lt;/code&gt;, it clears the &lt;code&gt;modal&lt;/code&gt; outlet.&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="nf"&gt;closeOutlet&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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&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;outlets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;span class="na"&gt;relativeTo&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;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;queryParamsHandling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preserve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;replaceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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;As with the child-path version, the route component should close its &lt;code&gt;MatDialogRef&lt;/code&gt; when it is destroyed and avoid navigating again when destruction was caused by an existing navigation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why auxiliary routes are special
&lt;/h3&gt;

&lt;p&gt;With query parameters, we manually interpret a value as modal state.&lt;/p&gt;

&lt;p&gt;With a child path, the modal belongs to a specific parent route.&lt;/p&gt;

&lt;p&gt;With an auxiliary route, the Router natively represents the page and modal as two simultaneously active branches. This is powerful when the same modal can sit above different pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products(modal:cart)
/account(modal:cart)
/search?q=headphones(modal:cart)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The primary route can change independently from the modal route, if that is what the application needs.&lt;/p&gt;

&lt;p&gt;Auxiliary routes also provide a real route lifecycle. They can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;route parameters;&lt;/li&gt;
&lt;li&gt;guards;&lt;/li&gt;
&lt;li&gt;resolvers;&lt;/li&gt;
&lt;li&gt;lazy-loaded components;&lt;/li&gt;
&lt;li&gt;route-specific providers;&lt;/li&gt;
&lt;li&gt;their own activation and deactivation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The cost
&lt;/h3&gt;

&lt;p&gt;The URL is not subtle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products(modal:edit/42)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers unfamiliar with auxiliary routes may find the configuration and navigation syntax surprising. Relative navigation also requires care when named outlets are nested inside feature routes.&lt;/p&gt;

&lt;p&gt;Use this model because you genuinely have two independent routing regions, not because it is the fanciest option available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Material dialog or plain HTML?
&lt;/h2&gt;

&lt;p&gt;So far, each routed component has opened a &lt;code&gt;MatDialog&lt;/code&gt;. That works well because Material already handles the difficult modal mechanics: overlay positioning, backdrop, focus trapping, Escape, focus restoration, and ARIA roles.&lt;/p&gt;

&lt;p&gt;But the Router does not care how the modal is rendered.&lt;/p&gt;

&lt;p&gt;With a named outlet, for example, the routed component can &lt;em&gt;be&lt;/em&gt; the modal:&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="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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;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="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-edit-product-modal&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;div class="backdrop" (click)="close()"&amp;gt;
      &amp;lt;section
        class="modal"
        role="dialog"
        aria-modal="true"
        aria-labelledby="edit-product-title"
        (click)="$event.stopPropagation()"
      &amp;gt;
        &amp;lt;h2 id="edit-product-title"&amp;gt;Edit product&amp;lt;/h2&amp;gt;

        &amp;lt;app-product-form /&amp;gt;

        &amp;lt;button type="button" (click)="close()"&amp;gt;Close&amp;lt;/button&amp;gt;
      &amp;lt;/section&amp;gt;
    &amp;lt;/div&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;EditProductModal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;route&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;ActivatedRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;outlets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&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;relativeTo&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;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;queryParamsHandling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preserve&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;replaceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="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;That example demonstrates the routing relationship, not a production-complete modal. A custom modal must also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;move focus inside when it opens;&lt;/li&gt;
&lt;li&gt;trap focus while open;&lt;/li&gt;
&lt;li&gt;close on Escape when appropriate;&lt;/li&gt;
&lt;li&gt;restore focus when it closes;&lt;/li&gt;
&lt;li&gt;prevent background content from being interactive;&lt;/li&gt;
&lt;li&gt;expose a useful accessible name;&lt;/li&gt;
&lt;li&gt;handle scrolling correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The native HTML &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;/code&gt; element or Angular CDK’s dialog and accessibility utilities can remove some of that work. A visually convincing &lt;code&gt;&amp;lt;div class="modal"&amp;gt;&lt;/code&gt; is not automatically an accessible modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about route inputs and signals?
&lt;/h2&gt;

&lt;p&gt;Modern Angular can bind route state directly to component inputs with &lt;code&gt;withComponentInputBinding()&lt;/code&gt;. That can make routed modal components pleasantly small:&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;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="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;class&lt;/span&gt; &lt;span class="nc"&gt;EditProductModal&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;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&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="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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 especially useful for the child-path and auxiliary-route approaches because &lt;code&gt;productId&lt;/code&gt; is a real route parameter.&lt;/p&gt;

&lt;p&gt;For query parameters, direct input binding can also be convenient, but we still need coordination code if an imperative overlay such as &lt;code&gt;MatDialog&lt;/code&gt; must open and close in response.&lt;/p&gt;

&lt;p&gt;Signals improve how we consume route state; they do not change the architectural choice of where that state belongs in the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Example URL&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Main drawback&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query parameter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products?dialog=edit&amp;amp;productId=42&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional UI state attached to the current page&lt;/td&gt;
&lt;td&gt;Coordination lives in the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Child path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products/42/edit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A modal that belongs to one parent resource or page&lt;/td&gt;
&lt;td&gt;Parent layout and route bridge are required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auxiliary route&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/products(modal:edit/42)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Page and modal are independent routed regions&lt;/td&gt;
&lt;td&gt;More unusual URL and router syntax&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My default decision process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the modal is temporary and should not survive refresh, do not route it.&lt;/li&gt;
&lt;li&gt;If it is optional state of the current page, start with a query parameter.&lt;/li&gt;
&lt;li&gt;If it is a destination naturally nested under one page, use a child path.&lt;/li&gt;
&lt;li&gt;If it must coexist with several unrelated primary routes, consider an auxiliary route.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no universally “most Angular” answer. The URL should describe the product behaviour you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few rules that apply to all three
&lt;/h2&gt;

&lt;p&gt;Whichever option you choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat the URL as the source of truth.&lt;/li&gt;
&lt;li&gt;Test pasted deep links, not only clicks from inside the app.&lt;/li&gt;
&lt;li&gt;Test Back and Forward while the modal is open.&lt;/li&gt;
&lt;li&gt;Close the overlay when its route is deactivated.&lt;/li&gt;
&lt;li&gt;Prevent &lt;code&gt;afterClosed()&lt;/code&gt; from overriding a navigation already in progress.&lt;/li&gt;
&lt;li&gt;Put stable identifiers in the URL, then load the data from a service, store, or resolver.&lt;/li&gt;
&lt;li&gt;Decide deliberately whether closing should push or replace a history entry.&lt;/li&gt;
&lt;li&gt;Do not sacrifice modal accessibility just because routing works.&lt;/li&gt;
&lt;li&gt;Add an SSR-safe fallback or render routed modal content declaratively if the route can be rendered on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second-to-last point deserves repeating. A modal with a beautiful URL but broken keyboard focus is still a broken modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Routing a modal is not really about calling &lt;code&gt;open()&lt;/code&gt; from the Router. It is about deciding whether the modal is part of your application’s navigable state.&lt;/p&gt;

&lt;p&gt;Once that decision is clear, the implementation becomes much easier: choose the URL shape that tells the truth, let navigation drive the UI, and make closing the modal a navigation too.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Choosing the right RxJS Operator for Action Streams</title>
      <dc:creator>Michele Stieven</dc:creator>
      <pubDate>Mon, 27 Jul 2026 05:03:03 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/choosing-the-right-rxjs-operator-for-action-streams-2j68</link>
      <guid>https://dev.to/playfulprogramming-angular/choosing-the-right-rxjs-operator-for-action-streams-2j68</guid>
      <description>&lt;p&gt;If you use RxJS action streams (maybe for API calls?), you have probably paused over the same question I have:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this effect use &lt;code&gt;mergeMap&lt;/code&gt;, &lt;code&gt;switchMap&lt;/code&gt;, &lt;code&gt;concatMap&lt;/code&gt;, or &lt;code&gt;exhaustMap&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The honest answer is not “use this operator for GET and that operator for POST.” The right choice depends on what a repeated action &lt;em&gt;means&lt;/em&gt; in your application.&lt;/p&gt;

&lt;p&gt;I will use NgRx Effects in the first example because it is a familiar action-based API, but the ideas in this article are not tied to &lt;code&gt;@ngrx/effects&lt;/code&gt;. They apply whenever an RxJS stream represents commands or events that start asynchronous work—for example, in an NgRx SignalStore method built with &lt;code&gt;rxMethod&lt;/code&gt;, in a ComponentStore effect, or in your own action stream.&lt;/p&gt;

&lt;p&gt;Let’s build a mental model that makes that choice much less mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The familiar effect
&lt;/h2&gt;

&lt;p&gt;A typical effect listens for an action, calls a service, and maps the result to either a success or a failure action:&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;inject&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;Actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ofType&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;@ngrx/effects&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;catchError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;switchMap&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;rxjs&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;loadTodos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;actions$&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;Actions&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;todoService&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;TodoService&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="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;switchMap&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todos&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
          &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&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;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;functional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern NgRx supports functional effects, so the full example above uses one. From this point on, I will show only the relevant &lt;code&gt;actions$.pipe(...)&lt;/code&gt; fragment. You can place the same pipeline inside a class-based effect, a functional effect, an &lt;code&gt;rxMethod&lt;/code&gt;, or another RxJS-based action handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: what are we flattening?
&lt;/h2&gt;

&lt;p&gt;An action stream is an Observable. For every action, our service returns another Observable. We therefore have an Observable producing Observables—a &lt;em&gt;higher-order Observable&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The four common flattening operators decide what happens when a new action arrives while the previous inner Observable is still running:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;When another action arrives&lt;/th&gt;
&lt;th&gt;Useful mental model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mergeMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subscribe to it too&lt;/td&gt;
&lt;td&gt;Run in parallel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;concatMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Put it in a queue&lt;/td&gt;
&lt;td&gt;One at a time, in order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;switchMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unsubscribe from the previous inner Observable and switch to the new one&lt;/td&gt;
&lt;td&gt;Latest subscription wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exhaustMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ignore the new action while the current inner Observable is active&lt;/td&gt;
&lt;td&gt;First one wins until completion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last column is more useful than memorising a rule per HTTP verb.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mergeMap&lt;/code&gt;: run everything
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;mergeMap&lt;/code&gt; subscribes to every inner Observable as it arrives. Requests may run concurrently, and their responses may arrive in a different order.&lt;/p&gt;

&lt;p&gt;This is a good fit when every operation matters and the operations are independent.&lt;/p&gt;

&lt;p&gt;The danger is a race condition. If two responses update the same state, an older request can finish last and overwrite newer data.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;concatMap&lt;/code&gt;: queue everything
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;concatMap&lt;/code&gt; waits for the current inner Observable to complete before subscribing to the next one.&lt;/p&gt;

&lt;p&gt;Nothing is dropped, and the client starts operations in action order. The trade-off is waiting: one slow request blocks everything queued behind it.&lt;/p&gt;

&lt;p&gt;This is often a good default for writes that must be applied in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;switchMap&lt;/code&gt;: keep listening only to the latest
&lt;/h3&gt;

&lt;p&gt;When a new action arrives, &lt;code&gt;switchMap&lt;/code&gt; unsubscribes from the previous inner Observable and subscribes to the new one.&lt;/p&gt;

&lt;p&gt;With Angular &lt;code&gt;HttpClient&lt;/code&gt;, unsubscribing normally aborts the client-side request. But be careful with the wording: this does &lt;strong&gt;not&lt;/strong&gt; prove that the server never received or processed it. A write may already have crossed that boundary.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;switchMap&lt;/code&gt; excellent for replaceable reads—search suggestions are the classic example—but risky as a general policy for writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;exhaustMap&lt;/code&gt;: ignore repeated triggers
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;exhaustMap&lt;/code&gt; accepts the first action, then ignores new actions until the active inner Observable completes.&lt;/p&gt;

&lt;p&gt;It is useful when repeated triggers are accidental or meaningless: double-clicking a login button, submitting the same form twice, or requesting the same deletion while it is already in progress.&lt;/p&gt;

&lt;p&gt;Remember that ignored actions are not queued. They are gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not choose by HTTP verb alone
&lt;/h2&gt;

&lt;p&gt;“Use &lt;code&gt;switchMap&lt;/code&gt; for GET” sounds convenient, but it is too broad.&lt;/p&gt;

&lt;p&gt;Ask these questions instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does every action represent work that must happen?&lt;/li&gt;
&lt;li&gt;Can two operations safely run at the same time?&lt;/li&gt;
&lt;li&gt;Does order matter?&lt;/li&gt;
&lt;li&gt;Is a newer action a replacement for an older one?&lt;/li&gt;
&lt;li&gt;Should repeated actions be ignored while one is running?&lt;/li&gt;
&lt;li&gt;Are those answers global, or only true for actions concerning the same entity?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last question is where things get interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading one replaceable collection
&lt;/h2&gt;

&lt;p&gt;Suppose the store contains one visible list of todos. Every &lt;code&gt;load&lt;/code&gt; action means “replace that list with the newest response.”&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;switchMap&lt;/code&gt; is a natural fit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;switchMap&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todos&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user refreshes twice, we usually care about the latest refresh, not both responses.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;exhaustMap&lt;/code&gt; could also be correct—but it expresses a different product decision. It says that refresh clicks should be ignored until the current request completes. NgRx’s own effects guide uses this pattern in a collection-loading example. Neither operator is universally right; the UX decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading a collection with query parameters
&lt;/h2&gt;

&lt;p&gt;Now imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;completed&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="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;completed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should the first request be cancelled?&lt;/p&gt;

&lt;p&gt;It depends on the state model.&lt;/p&gt;

&lt;p&gt;If the store holds only the currently selected list, the second action replaces the first query. &lt;code&gt;switchMap&lt;/code&gt; is still appropriate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;completed&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todos&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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 the store caches a separate result for each query, both requests matter. &lt;code&gt;mergeMap&lt;/code&gt; may be the better choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;completed&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todos&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;Notice that the success action includes the query. Without it, the reducer may not know which cache entry to update.&lt;/p&gt;

&lt;p&gt;The parameter alone did not determine the operator. The way we store the result did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading individual entities
&lt;/h2&gt;

&lt;p&gt;Consider these actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The requests concern different entities, so cancelling one because the other started would be surprising. &lt;code&gt;mergeMap&lt;/code&gt; allows both to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;todoService&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;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOneSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOneFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;But what about 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="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the requests target the same entity. If they run in parallel, an older response could arrive last.&lt;/p&gt;

&lt;p&gt;What we really want is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different IDs: run in parallel;&lt;/li&gt;
&lt;li&gt;the same ID: keep only the latest subscription.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is &lt;em&gt;keyed concurrency&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyed concurrency with &lt;code&gt;groupBy&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;RxJS’s &lt;code&gt;groupBy&lt;/code&gt; can split the action stream into one stream per ID. We can use &lt;code&gt;switchMap&lt;/code&gt; inside each group and &lt;code&gt;mergeMap&lt;/code&gt; across groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loadOne&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actionsForId$&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;actionsForId$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;todoService&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;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOneSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadOneFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;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 gives us both behaviours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requests for different entities can run concurrently;&lt;/li&gt;
&lt;li&gt;a newer request for the same entity replaces the previous subscription.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the discriminator is a query object, create a stable key from the meaningful fields. Do not casually use &lt;code&gt;JSON.stringify&lt;/code&gt; unless key order and normalisation are under your control.&lt;/p&gt;

&lt;h3&gt;
  
  
  A &lt;code&gt;groupBy&lt;/code&gt; lifetime warning
&lt;/h3&gt;

&lt;p&gt;Application-level action streams are usually long-lived. Without a duration, every distinct group created by &lt;code&gt;groupBy&lt;/code&gt; also remains available for the life of the subscription. That is harmless for a small, bounded set of keys, but an unbounded stream of unique IDs can retain an unbounded number of groups.&lt;/p&gt;

&lt;p&gt;RxJS lets you provide a &lt;code&gt;duration&lt;/code&gt; that closes an inactive group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&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;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;group$&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;group$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;debounceTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&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 deserves more caution than it usually gets. If a group closes while one of its requests is still active, a later action with the same key creates a new group. The old request and the new group can then overlap, weakening the per-key guarantee.&lt;/p&gt;

&lt;p&gt;Use a duration only when its timing is valid for your domain, or when that occasional overlap is acceptable. For strict, reusable keyed concurrency, prefer a thoroughly tested custom operator or a request-coordination layer with explicit lifecycle tracking. A five-second timeout copied from a blog post is not a correctness proof—I say that with affection as someone who once wanted it to be one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating entities
&lt;/h2&gt;

&lt;p&gt;For independent creates, every action normally matters. &lt;code&gt;mergeMap&lt;/code&gt; lets them run concurrently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;draft&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;concatMap&lt;/code&gt; instead if creation order matters or the server requires serial execution.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;exhaustMap&lt;/code&gt; if the UI represents a single submission and double-submission must be ignored. Disabling the submit button while the request is pending is still a good UX improvement, but the effect can provide a second line of defence.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;switchMap&lt;/code&gt; is rarely the safe default for a create. Unsubscribing does not undo a record the server has already created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating entities
&lt;/h2&gt;

&lt;p&gt;This is where I would change a common recommendation: for writes to the same entity, “latest wins” on the client is not enough.&lt;/p&gt;

&lt;p&gt;Imagine two edits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="p"&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;First&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="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changes&lt;/span&gt;&lt;span class="p"&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;Second&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;With &lt;code&gt;switchMap&lt;/code&gt;, the first response may be ignored, but the server might still apply the first update after the second. The client then &lt;em&gt;looks&lt;/em&gt; correct until the next reload reveals stale data.&lt;/p&gt;

&lt;p&gt;A safer client-side policy is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different IDs: update in parallel;&lt;/li&gt;
&lt;li&gt;the same ID: queue updates in order.
&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="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actionsForId$&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;actionsForId$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;concatMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changes&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;todo&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;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 preserves the order in which the client sends updates for each entity. It does not solve every distributed-systems problem: other clients can still write concurrently. If that matters, use server-side concurrency control such as version numbers, ETags, or conflict detection.&lt;/p&gt;

&lt;p&gt;For high-frequency UI changes, another valid design is to debounce &lt;em&gt;before&lt;/em&gt; sending writes, or to dispatch a “commit” action separately from local edits. That is a product and data-consistency decision, not merely an operator choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting entities
&lt;/h2&gt;

&lt;p&gt;For deletion, duplicate requests for the same ID are usually pointless, while deletions of different IDs can run in parallel.&lt;/p&gt;

&lt;p&gt;That suggests &lt;code&gt;exhaustMap&lt;/code&gt; per key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;actions$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosPageActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;mergeMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actionsForId$&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;actionsForId$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;exhaustMap&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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;todoService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nf"&gt;map&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;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteSuccess&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nf"&gt;catchError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TodosApiActions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteFailure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A global &lt;code&gt;exhaustMap&lt;/code&gt; would also suppress deletion of ID 2 while ID 1 is being deleted. That may be right for a single confirmation dialog, but it is too restrictive for many list UIs.&lt;/p&gt;

&lt;p&gt;Also consider making deletion idempotent on the server. Client-side operator choices help the UX; server-side semantics protect the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical decision table
&lt;/h2&gt;

&lt;p&gt;These are starting points, not laws:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Likely choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latest search or filter replaces the previous one&lt;/td&gt;
&lt;td&gt;&lt;code&gt;switchMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Old results are no longer useful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh clicks should be ignored while loading&lt;/td&gt;
&lt;td&gt;&lt;code&gt;exhaustMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prevent duplicate in-flight loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independent reads or creates&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mergeMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All operations matter and may run together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordered writes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;concatMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preserve client-side execution order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latest read per entity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;groupBy&lt;/code&gt; + &lt;code&gt;switchMap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Replace only requests with the same key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordered writes per entity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;groupBy&lt;/code&gt; + &lt;code&gt;concatMap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Serialize the same entity, parallelise different ones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignore duplicate work per entity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;groupBy&lt;/code&gt; + &lt;code&gt;exhaustMap&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Suppress repeats only for the same key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When uncertain, describe the desired behaviour in plain English first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run all of them, queue them, replace the previous one, or ignore the new one?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then decide whether that rule applies to the whole action stream or separately per entity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should we hide this in a custom operator?
&lt;/h2&gt;

&lt;p&gt;A custom operator can remove repetition, but it also hides the most important behaviour in the effect: concurrency.&lt;/p&gt;

&lt;p&gt;Names such as &lt;code&gt;PARALLEL&lt;/code&gt;, &lt;code&gt;QUEUE&lt;/code&gt;, &lt;code&gt;LATEST&lt;/code&gt;, and &lt;code&gt;IGNORE_WHILE_BUSY&lt;/code&gt; are readable, but a general helper must also get error handling, typing, key lifetimes, cancellation, and per-key cleanup right. That is quite a contract.&lt;/p&gt;

&lt;p&gt;My current preference is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the standard RxJS operator visible for simple effects;&lt;/li&gt;
&lt;li&gt;extract a keyed operator only after the pattern repeats;&lt;/li&gt;
&lt;li&gt;give it a behavioural name;&lt;/li&gt;
&lt;li&gt;test it with marble tests, including same-key and different-key actions;&lt;/li&gt;
&lt;li&gt;document exactly what happens when groups expire and requests outlive them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Abstraction should make the concurrency policy clearer, not merely make the pipe shorter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The best flattening operator is not determined by &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt;, nor by whether you happen to be using NgRx Effects, SignalStore, or a home-grown action stream. It is determined by which actions matter, which may overlap, and what your state considers “the same piece of work.”&lt;/p&gt;

&lt;p&gt;Once you ask those questions, the four operators stop feeling like RxJS trivia and start looking like four very precise product decisions. And that is much easier to reason about.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>ngrx</category>
      <category>rxjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Angular Addicts #51: spartan/ui 1.0, A2UI, Signal Debouncing &amp; more</title>
      <dc:creator>Gergely Szerovay</dc:creator>
      <pubDate>Thu, 16 Jul 2026 08:31:30 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/angular-addicts-51-spartanui-10-a2ui-signal-debouncing-more-48kb</link>
      <guid>https://dev.to/playfulprogramming-angular/angular-addicts-51-spartanui-10-a2ui-signal-debouncing-more-48kb</guid>
      <description>&lt;h2&gt;
  
  
  👋Hey fellow Angular Addict
&lt;/h2&gt;

&lt;p&gt;This is the 51st issue of the Angular Addicts Newsletter, a monthly collection of carefully selected Angular resources that caught my attention. (Here are the &lt;a href="https://www.angularaddicts.com/p/angular-addicts-50-angular-22-agentic-deploying" rel="noopener noreferrer"&gt;50th&lt;/a&gt;, &lt;a href="https://www.angularaddicts.com/p/angular-addicts-49-analogjs-25-agentic-angular" rel="noopener noreferrer"&gt;49th&lt;/a&gt; and &lt;a href="https://www.angularaddicts.com/p/angular-addicts-48-typescript-6-on-push-as-default" rel="noopener noreferrer"&gt;48th&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  📢 Release announcements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📢 &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o"&gt;Announcing spartan/ui 1.0&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/robingoetz/" rel="noopener noreferrer"&gt;Robin Goetz&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💎Angular Gems of June, 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://www.angulararchitects.io/en/blog/ae-harness-setup-for-angular/" rel="noopener noreferrer"&gt;Agentic Engineering: How to Set Up Your Harness for Angular Development&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/thalhammer/" rel="noopener noreferrer"&gt;Alexander Thalhammer&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://angular.love/getting-to-know-angular-resource-snapshots" rel="noopener noreferrer"&gt;Getting to Know Angular Resource Snapshots&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://x.com/prodromouf" rel="noopener noreferrer"&gt;Fanis Prodromou&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/ryantsuji/making-the-context-across-46-repositories-semantically-searchable-for-ai-part-2-51d9"&gt;Making the Context Across 46 Repositories Semantically Searchable for AI (Part 2)&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/ryantsuji/" rel="noopener noreferrer"&gt;Ryosuke Tsuji&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://blog.angular.dev/demystifying-a2ui-how-to-make-ai-agents-speak-ui-in-your-app-e1ffea2303bd" rel="noopener noreferrer"&gt;Demystifying A2UI: How to Make AI Agents “Speak UI” in Your App&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/devinchasanoff/" rel="noopener noreferrer"&gt;Devin Chasanoff&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/geromegrignon/angular-resources-are-not-a-good-fit-for-guards-58j"&gt;Angular resources are not a good fit for Guards&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/gerome-grignon/" rel="noopener noreferrer"&gt;Gerome Grignon&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/shhdharmen/angular-material-theming-system-course-now-100-free-1f1h"&gt;Angular Material Theming System Course — Now 100% Free&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/shhdharmen/" rel="noopener noreferrer"&gt;Dharmen Shah&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/dhutaryan/tanstack-query-style-caching-the-angular-native-way-4igc"&gt;TanStack Query style caching, the Angular-native way&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/dzmitry-hutaryan/" rel="noopener noreferrer"&gt;Dzmitry Hutaryan&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://www.angulararchitects.io/en/blog/architecture-beyond-layers-tsarch-for-ai-agents/" rel="noopener noreferrer"&gt;Architecture Beyond Layers: tsarch for AI Coding Agents&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/manfred-steyer-84645821/" rel="noopener noreferrer"&gt;Manfred Steyer&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://nartc.me/blog/ai-work-life-balance/" rel="noopener noreferrer"&gt;AI Made Coding Faster, But My Day Harder to End | Chau Tran&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/chauntran/" rel="noopener noreferrer"&gt;Chau Tran&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://blog.angulartraining.com/how-to-debounce-angular-signals-14425d2cd63e" rel="noopener noreferrer"&gt;How to Debounce Angular Signals?&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/achautard/" rel="noopener noreferrer"&gt;Alain Chautard&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  📰 &lt;a href="https://dev.to/sylwia-lask/7-new-javascript-features-and-2-im-still-waiting-for-2ck8"&gt;7 New JavaScript Features (And 2 I'm Still Waiting For)&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;By &lt;a href="https://www.linkedin.com/in/sylwia-laskowska-5a8467131/" rel="noopener noreferrer"&gt;Sylwia Laskowska&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  👨‍💻About the author
&lt;/h2&gt;

&lt;p&gt;My name is &lt;a href="https://www.linkedin.com/in/gergelyszerovay/" rel="noopener noreferrer"&gt;Gergely Szerovay&lt;/a&gt;, I worked as a data scientist and full-stack developer for many years, and I have been working as frontend tech lead, focusing on Angular based frontend development. As part of my role, I'm constantly following how Angular and the frontend development scene in general is evolving. To share my knowledge, I started the &lt;a href="https://angularaddicts.com/" rel="noopener noreferrer"&gt;Angular Addicts&lt;/a&gt; monthly newsletter and publication in 2022, so that I can send you the best resources I come across each month. Whether you are a seasoned Angular Addict or a beginner, I got you covered. Let me know if you would like to be included as a writer. Let’s learn Angular together! &lt;a href="https://www.angularaddicts.com/" rel="noopener noreferrer"&gt;Subscribe here&lt;/a&gt; 🔥&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.angularaddicts.com/" rel="noopener noreferrer"&gt;Substack (Angular Addicts)&lt;/a&gt;, &lt;a href="https://aiboosted.dev" rel="noopener noreferrer"&gt;Substack (AIBoosted.dev)&lt;/a&gt;, &lt;a href="https://medium.com/@GergelySzerovay" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;, &lt;a href="https://dev.to/gergelyszerovay"&gt;Dev.to&lt;/a&gt;, &lt;a href="https://twitter.com/GergelySzerovay" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/gergelyszerovay/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to learn more about Angular, and how to build AI apps with AI, Typescript, React and Angular!&lt;/p&gt;

&lt;h2&gt;
  
  
  🕹️Previous issues
&lt;/h2&gt;

&lt;p&gt;If you missed the previous issues of the newsletter, you can read them &lt;a href="https://www.angularaddicts.com/t/angular-addicts-monthly" rel="noopener noreferrer"&gt;here&lt;/a&gt;, these are the latest 3 issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-50-angular-22-agentic-deploying" rel="noopener noreferrer"&gt;Angular Addicts #50: Angular 22, Agentic Angular, Deploying to the Cloud &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-49-analogjs-25-agentic-angular" rel="noopener noreferrer"&gt;Angular Addicts #49: AnalogJS 2.5, Agentic Angular &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.angularaddicts.com/p/angular-addicts-48-typescript-6-on-push-as-default" rel="noopener noreferrer"&gt;Angular Addicts #48: TypeScript 6, OnPush as default, AI tools &amp;amp; more&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📨 Submit your Angular resource
&lt;/h2&gt;

&lt;p&gt;Have you found or written an interesting Angular-related article, tweet or other resource lately? Please let me know here in the comments or send me a DM on &lt;a href="https://twitter.com/gergelyszerovay" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;! I might feature it in the next Angular Addicts issue!&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Ng-News 26/17: PrimeNG's New Licensing, and A2UI for Angular</title>
      <dc:creator>ng-news</dc:creator>
      <pubDate>Mon, 13 Jul 2026 15:18:58 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/ng-news-2617-primengs-new-licensing-and-a2ui-for-angular-4eik</link>
      <guid>https://dev.to/playfulprogramming-angular/ng-news-2617-primengs-new-licensing-and-a2ui-for-angular-4eik</guid>
      <description>&lt;p&gt;&lt;strong&gt;PrimeNG's new licensing model&lt;/strong&gt; and &lt;strong&gt;A2UI for model-controlled Angular interfaces&lt;/strong&gt; are the headline topics this week.&lt;/p&gt;

&lt;p&gt;Also in brief: OpenNG's community fork, transactional router resource behavior, and the current Native Federation and Module Federation options for Angular.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/0HI13Rhah0U"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Future of PrimeNG
&lt;/h2&gt;

&lt;p&gt;PrimeNG, a very popular UI component library, has announced that future major versions won't be available as open source anymore, except through a free Community license for individuals, students, non-profits, non-commercial open source projects, and small organizations.&lt;/p&gt;

&lt;p&gt;That doesn't just apply to the Angular version but to the React and Vue versions as well. The reason is that the library has grown over time, which means if you do this as open source you have to spend much more time on it, and at some point, the income that they got from selling things around the library itself didn't cover the costs anymore.&lt;/p&gt;

&lt;p&gt;The new licensing brand is PrimeUI. The launch price is USD 599 per developer. That is perpetual, so for life for the covered versions, and it includes one year of updates. It also includes PrimeUI PRO, which contains components for charts, text editor, scheduler, task board, and more. The Pro components are not part of the Community license.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://primeui.dev/nextchapter" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffqjltiegiezfetthbags.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fwebsite.images%2Fmeta%2Fmeta-primeui.jpg" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://primeui.dev/nextchapter" rel="noopener noreferrer" class="c-link"&gt;
            PrimeUI
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            PrimeUI is a complete UI ecosystem — premium component libraries, advanced pro components, UI kit, and design resources to build modern web applications.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffqjltiegiezfetthbags.supabase.co%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fwebsite.images%2Ffavicon%2Ffavicon.svg" width="16" height="16"&gt;
          primeui.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;That being said, OpenNG, an organization which maintains unmaintained libraries, announced that PrimeNG went closed-source, the GitHub repository was archived on June 28th, 2026, and OpenNG created a copy of the project with regenerated open issues and PRs. Their goal is to release a beta version supporting Angular 22. So we are talking here about a fork. At the time of this writing, the current name is &lt;code&gt;open-prime&lt;/code&gt;, but OpenNG says it is too close to PrimeNG because PrimeTek plans to put trademarks on it.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.linkedin.com/posts/openng_as-people-have-questions-about-the-future-activity-7479828161099198464-izyG" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E22AQGnln6sjuYamg%2Ffeedshare-shrink_800%2FB4EZ82wFEkIkAc-%2F0%2F1783330001527%3Fe%3D2147483647%26v%3Dbeta%26t%3DHFGqM_FIlkdiH5K5nitvi0vMq1CxUMv4WFfln-A6jSg" height="800" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.linkedin.com/posts/openng_as-people-have-questions-about-the-future-activity-7479828161099198464-izyG" rel="noopener noreferrer" class="c-link"&gt;
            OpenNG Update: Funding, Versioning, PrimeNG and ngneat projects | OpenNG posted on the topic | LinkedIn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            As people have questions about the future of projects such as Spectator or PrimeNG, here is the current state of our actions.

OpenNG

We just renamed the initiative from OpenNG Foundation to OpenNG to avoid confusion and to respect the OpenJS Foundation trademark policy.

Funding

We are working on a funding model to support the maintenance of the projects and give back to contributors. We submitted the initiative to Open Collective and contacted HeroDevs for partnership.

Versioning Policy

We'll start over with v1.0.0 for all the packages. Each project will include a matrix compatibility table with the supported major Angular versions (including the previous package). Some packages were already not following the Angular versioning policy, and this will allow us to author a major version for breaking changes anytime.

PrimeNG

PrimeNG went closed-source, and the GitHub repository was archived on June 28th, 2026. Discover the announcement here.

We created a copy of the project and regenerated the open issues/PRs. Out of 968 open issues, 58 have already been closed, and 23 triaged as still relevant and accepting contributions to resolve them. Triaging was made possible thanks to the contribution of the community.

Our goal this week is to release a beta version supporting Angular 22, as people were already delaying updating their projects. 

It'll include:
- a set of unreleased changes from the original repository (modernize existing components, add a few new ones)
- ng add support for new projects
- ng update support as unreleased changes include a few breaking changes (camelcase selectors removed, deprecated APIs removed)

Our current blocker for this release is to find a proper name for the project. Currently named open-prime, it's too close to PrimeNG as PrimeTek plans to put trademarks on it. We asked the community for suggestions here.

We're evaluating proposals, with the main restriction being to avoid confusion with another project (many UI frameworks are matching the proposals).

ngneat projects

Out of the 34 original repositories, we decided to start working on the following ones:

- cashew
- query
- elf
- spectator

We have a volunteer working on cashew, currently preparing the first release.
Spectator is our priority within the ngneat ecosystem, planning a release this week. We also have a contribution to expose the jasmine package as a standalone (like jest/vitest ones, planned to land in 1.1.0.

We still need to triage the remaining repositories to understand if we should keep them or not. 13 of them might not be maintained anymore due to low activity or having a better alternative nowadays.

GitHub: https://lnkd.in/eE7QuzZR
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.licdn.com%2Faero-v1%2Fsc%2Fh%2Fal2o9zrvru7aqj8e1x2rzsrca" width="64" height="64"&gt;
          linkedin.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  A2UI
&lt;/h2&gt;

&lt;p&gt;Devin Chasanoff from the Angular team has published an article on how to use A2UI with Angular, which enables dynamic UI that is controlled by an AI model.&lt;/p&gt;

&lt;p&gt;The user expresses an intent through the frontend. The frontend sends that intent and relevant context to a model. This communication can also include a component catalog that the frontend knows how to render. The model can then decide whether UI components are needed, which components are appropriate, and return structured UI instructions to the application.&lt;/p&gt;

&lt;p&gt;A2UI is a protocol used for these agent-generated UI use cases. A2UI messages can be transported over HTTP, WebSocket, or Server-Sent Events, depending on the application architecture.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://blog.angular.dev/demystifying-a2ui-how-to-make-ai-agents-speak-ui-in-your-app-e1ffea2303bd" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1019%2F1%2AN552Qsvg9kAK3fH6B-Yoig.png" height="395" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://blog.angular.dev/demystifying-a2ui-how-to-make-ai-agents-speak-ui-in-your-app-e1ffea2303bd" rel="noopener noreferrer" class="c-link"&gt;
            Demystifying A2UI: How to Make AI Agents “Speak UI” in Your App | by Devin Chasanoff | Jun, 2026 | Angular Blog
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            I’ve spent a fair amount of time experimenting with A2UI, a protocol for building agentic interfaces. Why? Quite simply, because I think…
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afill%3A256%3A256%2F1%2AuJa0SU-F2NXVKhHTbP1HBw.png" width="256" height="256"&gt;
          blog.angular.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  routerResource
&lt;/h2&gt;

&lt;p&gt;In other news, work on integrating resource behavior with the router seems to have begun. A PR meant for internal usage popped up, which uses a snapshot-like router resource behavior to integrate navigation-specific information.&lt;/p&gt;

&lt;p&gt;That snapshot would freeze a resource's state, for example the value, while a navigation happens. Once the navigation succeeds, the underlying value is exposed, and if the navigation is cancelled, the frozen value would still be there during rollback recovery.&lt;/p&gt;

&lt;p&gt;Imagine the following use case. A resource has a router parameter as its own parameter. That could be a product id which loads a product by id. If the user stays on the same component but navigates to a different product id, the resource would immediately change its state and the UI would change while the navigation happens. That could cause flickering effects, especially if the navigation gets cancelled via a guard and the product id changes back to its initial value.&lt;/p&gt;

&lt;p&gt;With the introduced router resource behavior, that is not going to happen. Again, the PR's description explicitly states that this is not currently exposed for public use, but it is very likely the start to get resources and the router together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/angular/angular/pull/69490" rel="noopener noreferrer"&gt;Angular PR #69490: Create transactional router resource&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Micro-Frontends
&lt;/h2&gt;

&lt;p&gt;In other news, going with Micro-Frontends is an architectural need which we find very often in enterprises. Although they are not an Angular framework feature, there are two common community solutions in the form of Native Federation and Module Federation.&lt;/p&gt;

&lt;p&gt;The main distinction in Angular is that Native Federation works with esbuild, so the modern Angular builder, and Module Federation traditionally works with the old webpack builder or alternative builders such as Rspack.&lt;/p&gt;

&lt;p&gt;Native Federation is maintained by Angular Architects, and Module Federation support in Nx has been common for Angular workspaces. In the release blog for Nx 23, Nx now recommends &lt;code&gt;@angular-architects/native-federation&lt;/code&gt; for Angular and deprecates the older Nx-specific Module Federation generators and executors.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://nx.dev/blog/nx-23-release#module-federation-aligned-with-upstream" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnx.dev%2Fblog%2Fimages%2Farticles%2F_generated%2Fnx-23-release.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://nx.dev/blog/nx-23-release#module-federation-aligned-with-upstream" rel="noopener noreferrer" class="c-link"&gt;
            Nx 23: 4x Faster Nx Agents, Agentic Nx Migrate, improved targetDefaults, .Net GA | Nx Blog
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Nx 23 brings a smarter multi-major nx migrate, performance wins across the local engine and Nx Cloud agents, native Node.js TypeScript stripping and V8 compile cache on by default, more precise target configuration, and a big cleanup of deprecated generators, executors, and APIs.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnx.dev%2Ffavicon%2Ffavicon.svg" width="32" height="32"&gt;
          nx.dev
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>angular</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Ng-News 26/16: OpenNG Foundation, spartan/ui</title>
      <dc:creator>ng-news</dc:creator>
      <pubDate>Thu, 02 Jul 2026 21:47:45 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/ng-news-2616-openng-foundation-spartanui-1aim</link>
      <guid>https://dev.to/playfulprogramming-angular/ng-news-2616-openng-foundation-spartanui-1aim</guid>
      <description>&lt;p&gt;OpenNG Foundation and spartan/ui 1.0 are the headline topics this week: a new home for libraries like Spectator and Elf, and spartan/ui, a stable shadcn-inspired component library for Angular.&lt;/p&gt;

&lt;p&gt;Also in brief: Storybook's Angular modernization through AnalogJS, the end of ng-conf, and AI Dev Craft in Las Vegas.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/IEaeS_4eHKo"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenNG Foundation
&lt;/h2&gt;

&lt;p&gt;Maintaining open-source libraries is hard work. Developers often do it in their spare time, committing to years of maintenance, adding new features, and responding to user requests.&lt;/p&gt;

&lt;p&gt;Last episode, we reported that the ngneat organization was taken down for unknown reasons. While we still don't know why it happened, a new home has emerged for its popular libraries like Spectator and Elf: the OpenNG Foundation.&lt;/p&gt;

&lt;p&gt;Gerome Grignon, known for CanIUseAngular and as the organizer of Ng-Baguette, announced the foundation, which is already hosting these libraries. Alongside Gerome, the current OpenNG team also includes Dominic Bachmann, organizer of Angular Lucerne and author of the angular-typed-router library.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/openng-foundation" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F294824923%3Fs%3D280%26v%3D4" height="280" class="m-0" width="280"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/openng-foundation" rel="noopener noreferrer" class="c-link"&gt;
            OpenNG Foundation · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            OpenNG Foundation has 8 repositories available. Follow their code on GitHub.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg" width="32" height="32"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  spartan/ui 1.0
&lt;/h2&gt;

&lt;p&gt;spartan/ui has officially released its 1.0 version. It provides an "accessible, production-ready library of more than 55 components" with fully customizable styling.&lt;/p&gt;

&lt;p&gt;After debuting in August 2023 with 30 primitives, it now reaches stable in 2026 with a modern architecture built around signals, standalone components, zoneless change detection, and SSR. Originally initiated by Robin Götz, a full team quickly formed around the project.&lt;/p&gt;

&lt;p&gt;spartan/ui can be seen as the Angular equivalent to shadcn/ui, famous for its customizability. While similar open-source alternatives exist, spartan/ui was the pioneer and has a proven track record of active maintenance over the years.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o" class="crayons-story__hidden-navigation-link"&gt;Announcing spartan/ui 1.0&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/playfulprogramming-angular"&gt;
            &lt;img alt="Playful Programming Angular logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3316%2F612534f5-7bba-4bfe-8455-998219bc57cf.png" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/goetzrobin" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F961549%2F3c98ffe5-8d8b-4db1-827a-de8c63c25444.jpeg" alt="goetzrobin profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/goetzrobin" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Robin Goetz
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Robin Goetz
                
              
              &lt;div id="story-author-preview-content-3974106" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/goetzrobin" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F961549%2F3c98ffe5-8d8b-4db1-827a-de8c63c25444.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Robin Goetz&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/playfulprogramming-angular" class="crayons-story__secondary fw-medium"&gt;Playful Programming Angular&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o" id="article-link-3974106"&gt;
          Announcing spartan/ui 1.0
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/angular"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;angular&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;h2&gt;
  
  
  AnalogJS in Storybook
&lt;/h2&gt;

&lt;p&gt;Storybook’s Angular integration is getting a modernization, and it is all thanks to AnalogJS.&lt;/p&gt;

&lt;p&gt;While modern versions of the Angular CLI use Vite under the hood as a development server, it remains a "black box." The Angular CLI is still the one managing the compilation. Because the CLI itself is not a Vite plugin, it cannot be easily plugged into other modern tools.&lt;/p&gt;

&lt;p&gt;And that's the issue for Storybook, Vitest, but also others. They are designed to support any framework that presents itself as a Vite plugin.&lt;/p&gt;

&lt;p&gt;AnalogJS, founded and maintained by Brandon Roberts, solves this by doing exactly that. It fully decouples Angular from the CLI's build pipeline and exposes it as a standard Vite plugin. Just activate it, and these modern tools can suddenly compile and serve Angular components out of the box without any Angular-specific configuration. It just works.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2067695792106705118-982" src="https://platform.twitter.com/embed/Tweet.html?id=2067695792106705118"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2067695792106705118-982');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2067695792106705118&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  ng-conf is ending
&lt;/h2&gt;

&lt;p&gt;ng-conf is ending.&lt;/p&gt;

&lt;p&gt;ng-conf was the first Angular conference, going back to 2014 with AngularJS, and it continued through to the modern Angular era all the way through 2025. This also means that there is no US-based Angular conference anymore.&lt;/p&gt;

&lt;p&gt;In its place, a new conference called AI Dev Craft will debut in October in Las Vegas, with a call for speakers open.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2071616309742453054-891" src="https://platform.twitter.com/embed/Tweet.html?id=2071616309742453054"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2071616309742453054-891');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2071616309742453054&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Announcing spartan/ui 1.0</title>
      <dc:creator>Robin Goetz</dc:creator>
      <pubDate>Wed, 24 Jun 2026 00:24:58 +0000</pubDate>
      <link>https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o</link>
      <guid>https://dev.to/playfulprogramming-angular/announcing-spartanui-10-546o</guid>
      <description>&lt;p&gt;After a long and deliberate alpha, spartan/ui is now &lt;strong&gt;1.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We shipped the first 30 primitives in August 2023 with a simple bet: building accessible, good-looking UI in Angular is harder than it should be, and the community deserved a better starting point. Almost three years later, that bet has grown into a stable, production-ready library of more than 55 components - built on signals, ready for zoneless, and server-side-rendering compatible out of the box.&lt;/p&gt;

&lt;p&gt;Here's what 1.0 actually means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stable, and ready to build on
&lt;/h2&gt;

&lt;p&gt;We stayed in alpha for a long time on purpose. It let us refine the APIs in the open, with real applications putting real pressure on the design, instead of freezing a v1 we'd regret six months later.&lt;/p&gt;

&lt;p&gt;That patience is what 1.0 cashes in. The APIs are now stable and semantically versioned, so you can depend on &lt;code&gt;spartan/ui/brain&lt;/code&gt; and upgrade with confidence. The copy-in &lt;code&gt;spartan/ui/helm&lt;/code&gt; layer stays exactly as it's always been - yours to own, read, and customize. No black boxes, no fighting the library to change a style.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for modern Angular
&lt;/h2&gt;

&lt;p&gt;Every primitive is built on Angular signals and standalone components. spartan is zoneless-ready and SSR compatible out of the box, so it drops cleanly into how Angular apps are actually written today - no extra setup, no adapters.&lt;/p&gt;

&lt;p&gt;The split that's defined spartan from day one still holds. &lt;code&gt;spartan/ui/brain&lt;/code&gt; carries the hard, unglamorous parts - ARIA, keyboard navigation, focus management - and keeps them maintained so you don't have to. &lt;code&gt;spartan/ui/helm&lt;/code&gt; gives you full styling control on top, copied into your project like a recipe. Accessibility you can rely on; appearance you fully own.&lt;/p&gt;

&lt;h2&gt;
  
  
  From 30 primitives to 55+
&lt;/h2&gt;

&lt;p&gt;The alpha shipped with 30 components. 1.0 ships with more than 55 - nearly double - including many of the most-requested additions over the past two years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Table&lt;/strong&gt; - sorting, filtering, and selection, the piece people asked for most&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar&lt;/strong&gt; - composable app navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar&lt;/strong&gt; and &lt;strong&gt;Date Picker&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carousel&lt;/strong&gt;, &lt;strong&gt;Autocomplete&lt;/strong&gt;, &lt;strong&gt;Pagination&lt;/strong&gt;, and &lt;strong&gt;Breadcrumb&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we added &lt;strong&gt;Blocks&lt;/strong&gt;: ready-made, fully responsive layouts assembled from spartan components - authentication pages, sidebar layouts, and multi-step steppers you can drop in and adapt. Less scaffolding, more shipping.&lt;/p&gt;

&lt;p&gt;You can browse the &lt;a href="https://spartan.ng/components" rel="noopener noreferrer"&gt;full component set&lt;/a&gt; and the &lt;a href="https://spartan.ng/blocks" rel="noopener noreferrer"&gt;blocks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  This was built by a community
&lt;/h2&gt;

&lt;p&gt;I want to be clear about something: spartan/ui is not a one-person project, and 1.0 least of all. It exists because a group of people kept showing up - reviewing PRs, building components, fixing the edge cases nobody sees, and arguing about API design until we got it right.&lt;/p&gt;

&lt;p&gt;A huge thank you to the people who carried the heaviest load getting us here - &lt;strong&gt;Merlin&lt;/strong&gt; (&lt;a href="https://github.com/MerlinMoos" rel="noopener noreferrer"&gt;@MerlinMoos&lt;/a&gt;), &lt;strong&gt;Marc&lt;/strong&gt; (&lt;a href="https://github.com/marcjulian" rel="noopener noreferrer"&gt;@marcjulian&lt;/a&gt;), &lt;strong&gt;Ashley&lt;/strong&gt; (&lt;a href="https://github.com/ashley-hunter" rel="noopener noreferrer"&gt;@ashley-hunter&lt;/a&gt;), &lt;strong&gt;Sammy&lt;/strong&gt; (&lt;a href="https://github.com/thatsamsonkid" rel="noopener noreferrer"&gt;@thatsamsonkid&lt;/a&gt;), &lt;strong&gt;Benni&lt;/strong&gt; (&lt;a href="https://github.com/elite-benni" rel="noopener noreferrer"&gt;@elite-benni&lt;/a&gt;), and &lt;strong&gt;Toni&lt;/strong&gt; (&lt;a href="https://github.com/toniskobic" rel="noopener noreferrer"&gt;@toniskobic&lt;/a&gt;) - and to every single person who has opened an issue, sent a PR, answered a question in Discord, or sponsored the work. There are too many of you to name, and that's exactly the point. spartan is what it is because of you.&lt;/p&gt;

&lt;p&gt;If you've contributed and want to say hi or get more involved, the door is open in &lt;a href="https://discord.gg/EqHnxQ4uQr" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Powered by Zerops
&lt;/h2&gt;

&lt;p&gt;spartan's development infrastructure runs on &lt;a href="https://zerops.io" rel="noopener noreferrer"&gt;Zerops&lt;/a&gt;, and has for a long time. Preview environments, the docs and demo site, the day-to-day plumbing that makes maintaining an open-source library of this size sustainable - Zerops powers all of it. Their support is a real reason we were able to keep iterating in the open without the infrastructure becoming a tax on the team. Thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;There's never been a better time to start.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spartan.ng/documentation/installation" rel="noopener noreferrer"&gt;Get started with spartan/ui →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;spartan is MIT-licensed and built in the open. If it saves you time, consider &lt;a href="https://github.com/sponsors/goetzrobin" rel="noopener noreferrer"&gt;sponsoring its development&lt;/a&gt; - sponsorships fund ongoing maintenance, new components, and keeping spartan free for the whole Angular community.&lt;/p&gt;

&lt;p&gt;Here's to 1.0 - and to everything we build on top of it next.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
