<?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: Ali</title>
    <description>The latest articles on DEV Community by Ali (@aelmufti).</description>
    <link>https://dev.to/aelmufti</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3936579%2Fc2c24978-0cee-4131-8ee7-2b8f9ea628ef.jpeg</url>
      <title>DEV Community: Ali</title>
      <link>https://dev.to/aelmufti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aelmufti"/>
    <language>en</language>
    <item>
      <title>I deleted my CanActivate classes and my routing got readable</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/i-deleted-my-canactivate-classes-and-my-routing-got-readable-ald</link>
      <guid>https://dev.to/aelmufti/i-deleted-my-canactivate-classes-and-my-routing-got-readable-ald</guid>
      <description>&lt;p&gt;For years a route guard meant a class: &lt;code&gt;@Injectable&lt;/code&gt;, &lt;code&gt;implements CanActivate&lt;/code&gt;, a constructor full of services, and an entry in some &lt;code&gt;providers&lt;/code&gt; array you'd inevitably forget. The guard was usually four lines of actual logic wrapped in twenty lines of ceremony. Functional guards deleted the wrapper, and the same &lt;code&gt;inject()&lt;/code&gt; move that cleaned up interceptors did it again here.&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;authGuard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanActivateFn&lt;/span&gt; &lt;span class="o"&gt;=&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;state&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;auth&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;AuthService&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="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;()&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="nf"&gt;createUrlTree&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&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;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;returnUrl&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="nx"&gt;url&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's the whole thing. No class, no provider registration — you reference the function directly in the route. But the syntax win is the boring part. The interesting part is that going functional made the three jobs guards do finally feel like three different tools instead of one overloaded interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Return a UrlTree, not false
&lt;/h2&gt;

&lt;p&gt;The most common guard bug I find in review isn't a logic error — it's a guard that returns &lt;code&gt;false&lt;/code&gt;. Returning &lt;code&gt;false&lt;/code&gt; cancels the navigation and leaves the user exactly where they were, with no explanation. They clicked a link, nothing happened, and now they think the app is broken. Half the time they're sitting on a blank shell because the click came from a fresh page load.&lt;/p&gt;

&lt;p&gt;Returning a &lt;code&gt;UrlTree&lt;/code&gt; is almost always what you meant: it cancels the original navigation &lt;em&gt;and&lt;/em&gt; redirects in one step. Blocked from a page → send them to &lt;code&gt;/login&lt;/code&gt; with a &lt;code&gt;returnUrl&lt;/code&gt; so you can bounce them back after they sign in. The rule I use: a guard should either say yes, or say where to go instead. &lt;code&gt;false&lt;/code&gt; is for the rare case where staying put with a toast is genuinely the right UX, and even then I'd rather show the toast and redirect.&lt;/p&gt;

&lt;h2&gt;
  
  
  canActivate vs canMatch — they answer different questions
&lt;/h2&gt;

&lt;p&gt;These get used interchangeably and they shouldn't be. &lt;code&gt;canActivate&lt;/code&gt; asks "this route matched the URL — is the user allowed to land on it?" It runs &lt;em&gt;after&lt;/em&gt; the router has picked the route. &lt;code&gt;canMatch&lt;/code&gt; asks the earlier question: "does this route even apply?" It runs during route matching, before the component is chosen.&lt;/p&gt;

&lt;p&gt;That timing difference is the whole point. With &lt;code&gt;canMatch&lt;/code&gt; you can have two routes for the same path and let the guard decide which one wins:&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;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="s2"&gt;dashboard&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;AdminDashboard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;canMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dashboard&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;UserDashboard&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;Admins match the first route, everyone else falls through to the second. You can't express that with &lt;code&gt;canActivate&lt;/code&gt; — by the time it runs, the route is already locked in. The other &lt;code&gt;canMatch&lt;/code&gt; superpower: on a lazy route, a failed &lt;code&gt;canMatch&lt;/code&gt; means the chunk never even downloads. Guarding a whole admin feature behind &lt;code&gt;canActivate&lt;/code&gt; still ships its JavaScript to anonymous visitors; &lt;code&gt;canMatch&lt;/code&gt; keeps it on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small guards compose; god-guards don't
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;canActivate&lt;/code&gt; takes an array, I stopped writing guards that check three things and started writing three guards that check one thing each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billing&lt;/span&gt;&lt;span class="dl"&gt;"&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;Billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasBillingRole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orgIsActive&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;They run in order and the first one to return a &lt;code&gt;UrlTree&lt;/code&gt; wins, so the redirects naturally prioritize: not logged in beats wrong role beats suspended org. Each guard is independently testable and reusable across routes. A single guard doing all three reads fine the day you write it and becomes a tangle of nested conditions the third time someone adds a rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolvers: powerful, and the easiest way to make your app feel frozen
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ResolveFn&lt;/code&gt; got the same functional treatment, and it's genuinely useful — but it's the guard-family tool I reach for least, on purpose. A resolver fetches data &lt;em&gt;before&lt;/em&gt; the route activates, so the component renders with everything already present. No loading spinner inside the page, no flash of empty state.&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;orderResolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResolveFn&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;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;route&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;id&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;paramMap&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="s2"&gt;id&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="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&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;OrderService&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getOrder&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch is in that word "before." Navigation &lt;em&gt;blocks&lt;/em&gt; on the resolver. If the API takes two seconds, clicking the link does nothing visible for two seconds — the user is stuck on the previous page with no feedback. You've moved the loading state from "a spinner on the new page" to "the entire app appears to hang." If you use resolvers, you almost have to wire a progress bar to the router's &lt;code&gt;NavigationStart&lt;/code&gt;/&lt;code&gt;NavigationEnd&lt;/code&gt; events, or the UX is worse than just fetching in the component.&lt;/p&gt;

&lt;p&gt;So my line is: a resolver earns its place when the page is genuinely meaningless without the data — an order detail page with no order isn't a page, it's a 404 waiting to happen, and resolving lets you redirect on a missing record before anything renders. For everything else, fetching in the component with &lt;code&gt;httpResource&lt;/code&gt; gives you the same data with an honest, local loading state and no global freeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision rule
&lt;/h2&gt;

&lt;p&gt;Deciding whether the route applies, or want to skip downloading a lazy chunk → &lt;code&gt;canMatch&lt;/code&gt;. Deciding whether this user may enter a route that does apply → &lt;code&gt;canActivate&lt;/code&gt;, returning a &lt;code&gt;UrlTree&lt;/code&gt; when the answer is no. The page is structurally meaningless without server data, and you've got a progress indicator → &lt;code&gt;ResolveFn&lt;/code&gt;. Otherwise, let the component fetch its own data. All three are just functions now, which means all three are just unit tests now too.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>routing</category>
      <category>guards</category>
      <category>resolvers</category>
    </item>
    <item>
      <title>My route params bind straight to inputs now</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/my-route-params-bind-straight-to-inputs-now-2033</link>
      <guid>https://dev.to/aelmufti/my-route-params-bind-straight-to-inputs-now-2033</guid>
      <description>&lt;p&gt;Here's a pattern every Angular dev has typed a hundred times: a component needs the &lt;code&gt;:id&lt;/code&gt; from the URL, so you inject &lt;code&gt;ActivatedRoute&lt;/code&gt;, subscribe to &lt;code&gt;paramMap&lt;/code&gt;, pull the value, remember to unsubscribe, and handle the fact that the component is reused when only the id changes so &lt;code&gt;ngOnInit&lt;/code&gt; won't fire twice. Six lines of plumbing to read one string off the URL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;withComponentInputBinding()&lt;/code&gt; deletes all of it. You turn it on 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="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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and now the router sets your component's inputs from the route, matched by name. A route param &lt;code&gt;:id&lt;/code&gt; binds to an input called &lt;code&gt;id&lt;/code&gt;. That's the entire mental model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderDetail&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="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="c1"&gt;// &amp;lt;- comes straight from :id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Four sources feed the same inputs
&lt;/h2&gt;

&lt;p&gt;It's not just path params. The router binds from four places, and once you know all four you stop reaching for &lt;code&gt;ActivatedRoute&lt;/code&gt; almost entirely: resolved data and static &lt;code&gt;data&lt;/code&gt; on the route, path params (&lt;code&gt;:id&lt;/code&gt;), matrix params, and query params (&lt;code&gt;?tab=invoices&lt;/code&gt;). A search page reads its filters as inputs; a wizard reads its step; a detail page reads its id. All by name, all without a subscription.&lt;/p&gt;

&lt;p&gt;The one thing you have to internalize is precedence, because all four write to the same input slots. If the same key exists in more than one source, resolved/static &lt;code&gt;data&lt;/code&gt; wins, then path params, then query params. The practical consequence: don't name a query param the same thing as something in your resolver &lt;code&gt;data&lt;/code&gt;, or the data value silently shadows it and you'll swear the query param "isn't binding." Give colliding things distinct names and the whole feature stays predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real payoff is signal inputs
&lt;/h2&gt;

&lt;p&gt;Input binding is nice on its own, but combined with &lt;code&gt;input()&lt;/code&gt; signals it changes how route-driven components are written. The route param isn't a value you grabbed in &lt;code&gt;ngOnInit&lt;/code&gt; — it's a &lt;em&gt;signal&lt;/em&gt; that updates when the URL changes. Which means you can derive from it and the reactive graph handles the rest:&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;id&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="c1"&gt;// re-runs automatically when the URL id changes — no subscription,&lt;/span&gt;
&lt;span class="c1"&gt;// no ngOnChanges, no manual refetch&lt;/span&gt;
&lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;httpResource&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`/api/orders/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate from order 7 to order 8 and the component instance is reused, the &lt;code&gt;id&lt;/code&gt; signal flips to &lt;code&gt;"8"&lt;/code&gt;, and &lt;code&gt;httpResource&lt;/code&gt; refetches because its url depended on &lt;code&gt;id()&lt;/code&gt;. The "component reuse won't re-run my init" problem — the one that sent everyone subscribing to &lt;code&gt;paramMap&lt;/code&gt; in the first place — just isn't a problem anymore, because nothing was keyed on a lifecycle hook to begin with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stops, and that's fine
&lt;/h2&gt;

&lt;p&gt;Two honest limits. First, it binds to the &lt;em&gt;routed&lt;/em&gt; component — the one named in the route config. A deeply nested child that wants the parent route's params still reads &lt;code&gt;ActivatedRoute&lt;/code&gt;, or you pass them down as inputs yourself. Second, this is a router feature, not magic on every component; a component you drop into a template the normal way gets its inputs from the template, as always.&lt;/p&gt;

&lt;p&gt;I treat &lt;code&gt;ActivatedRoute&lt;/code&gt; the way I now treat &lt;code&gt;ngOnDestroy&lt;/code&gt;: still there, occasionally necessary, but no longer the default reflex. For the common case — a routed component that wants a value off the URL — the value just arrives as an input, and if it's a signal input, it stays correct on its own.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>routing</category>
      <category>signals</category>
      <category>inputs</category>
    </item>
    <item>
      <title>SignalStore is the first NgRx I'd actually reach for</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Mon, 15 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/signalstore-is-the-first-ngrx-id-actually-reach-for-58p9</link>
      <guid>https://dev.to/aelmufti/signalstore-is-the-first-ngrx-id-actually-reach-for-58p9</guid>
      <description>&lt;p&gt;I've spent a good chunk of my career talking teams &lt;em&gt;out&lt;/em&gt; of NgRx. Not because the Redux pattern is wrong — because most apps that adopted it didn't have a Redux-shaped problem. They had a "two components need the same list" problem, and they paid for it with an action, a reducer case, an effect, a selector, and three files, per feature. The ratio of ceremony to value was brutal.&lt;/p&gt;

&lt;p&gt;SignalStore is the first time the NgRx name has shown up in my projects without me wincing. It throws out the dispatch/reducer machinery and builds on signals directly. State is signals you read; methods are functions that change them. That's the model.&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;CartStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;withState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;loading&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="nf"&gt;withComputed&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;count&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;items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;total&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;items&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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="p"&gt;})),&lt;/span&gt;
  &lt;span class="nf"&gt;withMethods&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;store&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="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;item&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;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&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;count&lt;/code&gt; and &lt;code&gt;total&lt;/code&gt; are signals. &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;clear&lt;/code&gt; are methods. A component injects the store and reads &lt;code&gt;store.count()&lt;/code&gt; in its template like any other signal. No &lt;code&gt;store.select(...)&lt;/code&gt;, no string action types, no switch statement. The thing that used to be five concepts is now two: &lt;code&gt;withState&lt;/code&gt; and &lt;code&gt;withMethods&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  patchState, and the immutability you can't skip
&lt;/h2&gt;

&lt;p&gt;You never assign to state directly — you call &lt;code&gt;patchState&lt;/code&gt; with a partial update or an updater function. And the array spread in &lt;code&gt;add&lt;/code&gt; above isn't optional styling: SignalStore state follows the same rule signals always do. Mutating the existing array in place (&lt;code&gt;items().push(item)&lt;/code&gt;) changes the contents without changing the reference, so the signal never fires and your computed &lt;code&gt;count&lt;/code&gt; goes stale. Treat state as immutable, produce new references, and the reactive graph stays honest. This is the one rule that trips people coming from the mutate-anything world.&lt;/p&gt;

&lt;h2&gt;
  
  
  rxMethod is where the async lives
&lt;/h2&gt;

&lt;p&gt;Real stores fetch. &lt;code&gt;rxMethod&lt;/code&gt; is the bridge to RxJS for exactly that — it takes a stream of inputs and lets you run an Observable pipeline per emission, with proper cancellation:&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;loadItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rxMethod&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="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;tap&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;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
    &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cartId&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;cartService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cartId&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;tapResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
          &lt;span class="na"&gt;error&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="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;loading&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="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;switchMap&lt;/code&gt; there is doing the work an NgRx effect used to: a second call cancels the first in-flight request, so a fast-typing user doesn't race two responses into your state out of order. This is the spot where the "signals killed RxJS" crowd gets corrected — streams are still the right tool for events over time, and SignalStore is happy to let them feed the signals. It's the same boundary as everywhere else in modern Angular: signals hold state, streams describe the async.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision that actually matters: where you provide it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;providedIn: "root"&lt;/code&gt; gives you one global instance — right for genuinely app-wide state like the cart or the current user. But you can also leave that out and list the store in a component's &lt;code&gt;providers&lt;/code&gt; array. Now you get a fresh instance scoped to that component and its children, created when the feature mounts and &lt;em&gt;destroyed when it unmounts&lt;/em&gt;. State that belongs to one screen lives and dies with that screen, no manual reset.&lt;/p&gt;

&lt;p&gt;This is the call I see teams get wrong most often. They make every store global, then write cleanup code to wipe stale feature state on every navigation — reinventing component scope by hand. If the state is "this page's state," scope it to the page. If it's "the app's state," put it in root. &lt;code&gt;withEntities&lt;/code&gt; is worth knowing for the collection cases — it gives you normalized add/update/remove over an entity map so you're not hand-rolling the same dictionary logic again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you even need it?
&lt;/h2&gt;

&lt;p&gt;Honest answer: often not. A plain &lt;code&gt;@Injectable&lt;/code&gt; service holding a few &lt;code&gt;signal()&lt;/code&gt;s and exposing &lt;code&gt;computed()&lt;/code&gt; getters covers a surprising amount of shared state, and it's the lighter choice for a single feature with simple needs. SignalStore earns its dependency when you want &lt;em&gt;conventions&lt;/em&gt; a team will follow without a meeting — a predictable shape for every store, async that's already cancellation-safe, and &lt;code&gt;withEntities&lt;/code&gt;/custom &lt;code&gt;withX&lt;/code&gt; features you can compose and reuse across stores.&lt;/p&gt;

&lt;p&gt;The way I'd put it: plain signals service is the answer until you find yourself reinventing the same store skeleton for the fourth time. At that point SignalStore is the skeleton, already written, and unlike the NgRx of five years ago it doesn't make you pay for a Redux you don't have.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>ngrx</category>
      <category>signals</category>
      <category>state</category>
    </item>
    <item>
      <title>Calibrating a tax simulator against URSSAF's own engine</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Sun, 14 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/calibrating-a-tax-simulator-against-urssafs-own-engine-5hlh</link>
      <guid>https://dev.to/aelmufti/calibrating-a-tax-simulator-against-urssafs-own-engine-5hlh</guid>
      <description>&lt;p&gt;I shipped &lt;a href="https://freelance-ou-cdi.fr" rel="noopener noreferrer"&gt;freelance-ou-cdi.fr&lt;/a&gt;, a calculator that answers a question every French developer asks at least once: at what daily rate does going freelance actually beat my CDI? It compares net income — after social contributions &lt;em&gt;and&lt;/em&gt; income tax — across micro-entreprise, EI, EURL, SASU, portage salarial and the salaried baseline, under 2026 rates.&lt;/p&gt;

&lt;p&gt;Writing the arithmetic was the easy half. The hard half was proving it wasn't wrong. A tax simulator that is plausibly, quietly off by ten percent is worse than no simulator at all: it hands people a number they'll trust to make a five-figure decision. So the interesting engineering here isn't the model — it's how I kept the model honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't trust your own tax code
&lt;/h2&gt;

&lt;p&gt;French social contributions are not a flat percentage. The TNS regime layers base pension, complementary pension, sickness, family allowances and CSG-CRDS, several of them progressive, several capped at multiples of the &lt;code&gt;PASS&lt;/code&gt;. A SASU president is taxed as an employee but can route income through dividends at a flat 31.4%. The 2026 reform of the self-employed contribution base changed the assiette. Transcribe all of that from PDFs and you will get a handful of lines subtly wrong — and you'll never notice, because your wrong number still looks reasonable.&lt;/p&gt;

&lt;p&gt;The naive shortcut makes this concrete. Plenty of "brut to net" converters apply a fixed −25% for an executive salary. Run a real progressive calculation and that flat factor is off by several points at most salary levels, in both directions depending on where you land against the ceilings. "Looks about right" is exactly the failure mode you can't catch by eyeballing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The URSSAF ships an oracle — use it
&lt;/h2&gt;

&lt;p&gt;The French administration open-sources &lt;code&gt;modele-social&lt;/code&gt;, a &lt;a href="https://publi.codes" rel="noopener noreferrer"&gt;publicodes&lt;/a&gt; ruleset that powers &lt;code&gt;mon-entreprise.urssaf.fr&lt;/code&gt;, the URSSAF's own simulator. It is the closest thing to ground truth that exists for these calculations. The trick is to treat it not as a dependency I ship, but as a &lt;strong&gt;test oracle&lt;/strong&gt; : a reference my own engine has to agree with.&lt;/p&gt;

&lt;p&gt;So there's a comparison harness — &lt;code&gt;npm run compare&lt;/code&gt; — that instantiates the publicodes engine, asks it for the official figure on a grid of scenarios (micro BNC at several revenue levels, EI/TNS contributions, the income-tax schedule, SASU cost-to-net, CDI net and employer cost for cadre and non-cadre, even the civil-servant case), and prints my number beside it with the delta. Anything drifting past a few percent gets flagged. The goal isn't to match to the cent — the official engine models edge cases I deliberately don't — it's to stay inside a couple of percent across the realistic range, and to know immediately when a rate change or a refactor pushes me out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just ship publicodes?
&lt;/h2&gt;

&lt;p&gt;Fair question — if the reference engine is right there, why reimplement? Three reasons, all about control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inversion.&lt;/strong&gt; The product's best feature is "at what daily rate do I break even with my CDI?" That's a root-find: I run the forward model inside a binary search over the rate. Driving a general rules engine through dozens of inversions per interaction is the wrong tool; a direct function is trivial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency.&lt;/strong&gt; I wanted every result to expand into a line-by-line breakdown — this contribution, that tax, this deduction — with each parameter editable in an "advanced" panel. A purpose-built model exposes those internals naturally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight and reach.&lt;/strong&gt; The whole thing runs client-side with nothing sent to a server. A compact, audited engine keeps the page light; the heavy reference engine stays in the build, as the thing that grades my homework.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The payoff is also a GEO trick
&lt;/h2&gt;

&lt;p&gt;Calibration buys correctness. But generating &lt;em&gt;from&lt;/em&gt; the engine buys something else: numbers that can't contradict each other. The FAQ ("a freelancer beats a 55k CDI from about 410€/day in SASU") and the break-even table are both computed by the same functions that drive the live simulator — not typed into the copy by hand. Change a 2026 rate in one place and the prose, the table and the tool all move together.&lt;/p&gt;

&lt;p&gt;That self-consistency turns out to matter for getting cited by generative engines. An LLM summarizing "freelance vs CDI in France" will quote a page far more readily when its stated figures, its structured data, and its interactive tool all agree — because nothing on the page undercuts the claim. Internal contradiction is the fastest way to look untrustworthy to both a careful reader and a model. The cheapest way to never contradict yourself is to never write the number twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it still can't do
&lt;/h2&gt;

&lt;p&gt;A calibrated model is not an accountant. CFE, the family-quotient cap, first-year ACRE relief, mandatory &lt;em&gt;mutuelle&lt;/em&gt;, regional quirks — some of these are modeled, some deliberately aren't, and the gap is exactly why the harness flags differences instead of asserting equality. The honest framing on the site is "indicative, validated against the official engine within a couple of percent," not "tax advice." Knowing precisely where you diverge from ground truth is itself a feature; it's the difference between a confident wrong answer and a useful estimate with stated limits.&lt;/p&gt;

&lt;p&gt;The whole thing is open-source — engine, harness and all — at &lt;a href="https://github.com/aelmufti/is-freelance-worth-it-for-you" rel="noopener noreferrer"&gt;github.com/aelmufti/is-freelance-worth-it-for-you&lt;/a&gt;. If you spot a status where I drift from the URSSAF's engine, that's the bug report I most want to receive.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>publicodes</category>
      <category>testing</category>
      <category>fintech</category>
    </item>
    <item>
      <title>@let ended my async-pipe pyramid</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Sat, 13 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/let-ended-my-async-pipe-pyramid-3bc2</link>
      <guid>https://dev.to/aelmufti/let-ended-my-async-pipe-pyramid-3bc2</guid>
      <description>&lt;p&gt;Every Angular template has, at some point, contained this crime:&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;{{ (user$ | async)?.name }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ (user$ | async)?.email }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;[src]=&lt;/span&gt;&lt;span class="s"&gt;"(user$ | async)?.avatar"&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;Three subscriptions to the same observable, three independent async pipes, all to render one user. The "fix" we all learned was to abuse &lt;code&gt;*ngIf&lt;/code&gt; for its aliasing side effect — &lt;code&gt;*ngIf="user$ | async as user"&lt;/code&gt; — subscribing once and binding the result to a local name. It worked, but it was a hack: you were using a structural directive whose job is conditional rendering purely because it happened to create a scoped variable, and the whole block vanished the instant the value was falsy.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@let&lt;/code&gt; is the feature that should have existed the whole time. It declares a template variable. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@let user = user$ | async;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ user?.name }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{{ user?.email }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;[src]=&lt;/span&gt;&lt;span class="s"&gt;"user?.avatar"&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;One pipe, one subscription, a name you reuse everywhere below it. No &lt;code&gt;@if&lt;/code&gt; wrapper forced on you, no directive doing a job it wasn't designed for.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's reactive, and it's read-only
&lt;/h2&gt;

&lt;p&gt;Two properties make &lt;code&gt;@let&lt;/code&gt; behave the way you'd hope. It's &lt;em&gt;reactive&lt;/em&gt;: the right-hand side is re-evaluated when its dependencies change, so &lt;code&gt;@let count = counter()&lt;/code&gt; tracks the signal, and the async-pipe version updates on every emission. You're naming a live value, not snapshotting one.&lt;/p&gt;

&lt;p&gt;And it's &lt;em&gt;read-only&lt;/em&gt;. You cannot reassign a &lt;code&gt;@let&lt;/code&gt; from the template — there's no &lt;code&gt;user = somethingElse&lt;/code&gt;. This trips people who expect a JavaScript &lt;code&gt;let&lt;/code&gt;, but it's the right call: a template is a declaration of what the UI is, not a place to run imperative logic. If you need to change a value, you change the state it derives from, and the &lt;code&gt;@let&lt;/code&gt; follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope: after the line, inside the view
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;@let&lt;/code&gt; is available from its declaration onward, within the same view and any nested views below it. Two consequences worth internalizing. You can't reference it &lt;em&gt;before&lt;/em&gt; you declare it — order matters, top to bottom. And it doesn't leak &lt;em&gt;upward&lt;/em&gt; or sideways: a &lt;code&gt;@let&lt;/code&gt; declared inside an &lt;code&gt;@if&lt;/code&gt; block or an &lt;code&gt;@for&lt;/code&gt; loop exists only in there. That last bit is actually useful — inside a loop, &lt;code&gt;@let rowTotal = ...&lt;/code&gt; gives each iteration its own value without you thinking about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It does not replace &lt;a class="mentioned-user" href="https://dev.to/if"&gt;@if&lt;/a&gt; for null-narrowing
&lt;/h2&gt;

&lt;p&gt;This is the one mistake to avoid. &lt;code&gt;@let&lt;/code&gt; names a value; it does not &lt;em&gt;guarantee&lt;/em&gt; the value. An async pipe yields &lt;code&gt;null&lt;/code&gt; before its first emission, so &lt;code&gt;@let user = user$ | async&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt; on the first render. If the template below assumes a user exists, you'll be writing &lt;code&gt;user?.&lt;/code&gt; everywhere or hitting null reads.&lt;/p&gt;

&lt;p&gt;So the two features pair rather than compete. &lt;code&gt;@if&lt;/code&gt; still does the narrowing; &lt;code&gt;@let&lt;/code&gt; can carry the loaded value or a derived one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@if (user$ | async; as user) {
  @let initials = user.firstName[0] + user.lastName[0];
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"avatar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ initials }}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ user.name }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@if&lt;/code&gt; guarantees &lt;code&gt;user&lt;/code&gt; is present and non-null inside the block; the &lt;code&gt;@let&lt;/code&gt; gives you a clean name for a value you'd otherwise compute inline twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use a computed instead
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@let&lt;/code&gt; is for the &lt;em&gt;view&lt;/em&gt;. The moment a derived value is needed in TypeScript too — in a method, in another computed, in a guard — it belongs in a &lt;code&gt;computed()&lt;/code&gt; in the component, not in the template. The smell is duplication: if you'd write the same &lt;code&gt;@let&lt;/code&gt; expression in two separate templates, or you need it outside the template at all, promote it to &lt;code&gt;computed()&lt;/code&gt; and bind to that. Use &lt;code&gt;@let&lt;/code&gt; for the genuinely view-local stuff: aliasing a long property path, naming an async result, giving a loop iteration a readable per-row total. For that, it's exactly the right amount of power — and it finally retires the &lt;code&gt;*ngIf as&lt;/code&gt; hack I'd been apologizing for in code review for years.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>templates</category>
      <category>controlflow</category>
      <category>signals</category>
    </item>
    <item>
      <title>Incremental hydration: ship the HTML, hydrate on hover</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Fri, 12 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/incremental-hydration-ship-the-html-hydrate-on-hover-481h</link>
      <guid>https://dev.to/aelmufti/incremental-hydration-ship-the-html-hydrate-on-hover-481h</guid>
      <description>&lt;p&gt;Server-side rendering gets you a fast first paint: the browser receives real HTML and shows it immediately. Then hydration happens — Angular boots on the client, walks the entire component tree, and wires up every event listener so the page becomes interactive. The problem is the word "entire." Your footer, your three-tabs-deep settings panel, the comment section nobody scrolls to — all of it gets hydrated up front, competing for the main thread while the user is staring at the hero. That's the gap between "I can see it" and "I can click it," and it's mostly wasted work.&lt;/p&gt;

&lt;p&gt;Incremental hydration closes that gap by making hydration lazy, per block. You opt in 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="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;provideClientHydration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;withIncrementalHydration&lt;/span&gt;&lt;span class="p"&gt;())],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then mark the regions that can wait, using the &lt;code&gt;@defer&lt;/code&gt; syntax you already know — but with a &lt;code&gt;hydrate&lt;/code&gt; trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@defer (hydrate on viewport) {
  &lt;span class="nt"&gt;&amp;lt;app-comments&lt;/span&gt; &lt;span class="na"&gt;[postId]=&lt;/span&gt;&lt;span class="s"&gt;"postId"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comments render on the server as normal HTML — visible, indexable, no layout shift — but their JavaScript doesn't download or hydrate until the block scrolls into view. The main thread stays free for the part of the page the user is actually looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not the same as a plain &lt;a class="mentioned-user" href="https://dev.to/defer"&gt;@defer&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Worth being precise, because the syntax is shared and the behaviors are opposite. A regular &lt;code&gt;@defer (on viewport)&lt;/code&gt; renders &lt;em&gt;nothing&lt;/em&gt; on the server — you get a &lt;code&gt;@placeholder&lt;/code&gt; until the trigger fires, then the real content loads in. Great for cutting the initial bundle, but the deferred content isn't in the server HTML, so it's invisible to a crawler and pops in late for the user.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@defer (hydrate on ...)&lt;/code&gt; is the inverse. The content &lt;em&gt;is&lt;/em&gt; fully rendered on the server — it's there in the HTML from the first byte. What's deferred is only the hydration: the download and execution of its JavaScript. So you keep the SEO and the instant paint of SSR, and you also get the deferred-work win. It's the best of both, which is why it needed a new trigger keyword rather than reusing the old behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  hydrate never is a static island
&lt;/h2&gt;

&lt;p&gt;The trigger I find most quietly powerful is &lt;code&gt;hydrate never&lt;/code&gt;. Plenty of server-rendered content is genuinely static — a rendered markdown article, a pricing table, a marketing block. It has no interactivity to wire up, yet full hydration still spends cycles proving that to itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@defer (hydrate never) {
  &lt;span class="nt"&gt;&amp;lt;app-article-body&lt;/span&gt; &lt;span class="na"&gt;[markdown]=&lt;/span&gt;&lt;span class="s"&gt;"content"&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;&lt;code&gt;hydrate never&lt;/code&gt; says: this is finished, ship it as HTML and send no JavaScript for it, ever. It becomes a true static island in an otherwise interactive app. For a content-heavy page, that's a real cut to the JavaScript the client has to parse — the bytes for that subtree simply never arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clicks aren't lost — event replay catches them
&lt;/h2&gt;

&lt;p&gt;The obvious worry: if a button's JavaScript hasn't hydrated yet and the user clicks it, does the click vanish? This is exactly what event replay solves, and it's why incremental hydration leans on it. With &lt;code&gt;withEventReplay()&lt;/code&gt; (on by default in current setups), events that land on not-yet-hydrated content are captured and queued. The trigger fires, the block hydrates, and the queued events replay against the now-live listeners. From the user's side the click just worked, maybe a hair late. Without event replay, that early click really would be dropped — so treat the two features as a pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a different tool from afterNextRender
&lt;/h2&gt;

&lt;p&gt;Since both live under the SSR umbrella, it's worth drawing the line. &lt;code&gt;afterNextRender&lt;/code&gt; answers "my code touches &lt;code&gt;document&lt;/code&gt; and crashes on the server — when is it safe to run browser-only logic?" Incremental hydration answers a different question: "the whole page works, but when should each piece &lt;em&gt;become&lt;/em&gt; interactive?" One is about avoiding server-side errors; the other is about scheduling client-side interactivity. You'll often use both on the same app for entirely separate reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision rule
&lt;/h2&gt;

&lt;p&gt;Reach for &lt;code&gt;hydrate on viewport&lt;/code&gt; or &lt;code&gt;hydrate on interaction&lt;/code&gt; for heavy interactive widgets that sit below the fold or get used rarely — comment threads, data grids in a collapsed panel, a map at the bottom of the page. Use &lt;code&gt;hydrate never&lt;/code&gt; for server-rendered content with no interactivity at all. Leave the above-the-fold, immediately-interactive parts — your nav, your primary call to action — hydrating normally, because deferring those just adds latency to the thing the user reaches for first. The whole point is to stop spending the main thread on interactivity nobody has asked for yet.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>hydration</category>
      <category>ssr</category>
      <category>performance</category>
    </item>
    <item>
      <title>Are we cooked ?</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/are-we-cooked--524l</link>
      <guid>https://dev.to/aelmufti/are-we-cooked--524l</guid>
      <description>&lt;p&gt;It comes up in every dev conversation now, somewhere between the second coffee and the standup. A junior asks it sincerely. A senior asks it as a joke that lands a little too flat. Someone screenshots an agent building in twenty minutes what took their team a sprint, and the group chat goes: &lt;em&gt;are we cooked?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've given the reassuring answer at meetups and the doomer answer at 2am, and I no longer believe either. Here's the answer I actually believe, and it requires splitting the question in two — because "we" was never one group, and "cooked" was never one thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The job was never the typing
&lt;/h2&gt;

&lt;p&gt;Start with what's actually being automated. An LLM produces code — spectacularly well, some days. But look at where your last three production incidents came from. Mine didn't come from badly typed code. They came from a requirement that meant two different things to two different teams, a cache invalidation assumption nobody wrote down, and a third-party API that behaved differently under load. The code was fine. The &lt;em&gt;understanding&lt;/em&gt; had a hole in it.&lt;/p&gt;

&lt;p&gt;That's the job: translating fuzzy, contradictory human intent into a system that doesn't fall over, and holding the model of that system in your head while it grows past anyone's ability to read it whole. The typing was always just the visible part — the part you could film for a hiring ad. We're shocked that machines write code the way accountants were shocked that machines could add. Addition wasn't the job. The job was knowing which numbers to add, and noticing when a total couldn't possibly be right. The spreadsheet didn't kill accounting; it killed the version of accounting that was secretly just arithmetic. Hold that thought, because the parallel cuts both ways — some of what we call software engineering is secretly just arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inversion nobody priced in
&lt;/h2&gt;

&lt;p&gt;Here's the outside-the-box part, the thing I think most takes miss: AI didn't make software cheap. It made &lt;em&gt;generation&lt;/em&gt; cheap and left &lt;em&gt;verification&lt;/em&gt; expensive — and that inversion changes the profession more than any productivity number.&lt;/p&gt;

&lt;p&gt;Before: writing was slow, so writing was the bottleneck, so we organized everything around authors. Now a plausible implementation costs thirty seconds, and the question "is this plausible thing &lt;em&gt;correct&lt;/em&gt;?" costs what it always cost: attention, context, skepticism, the instinct that something is off in a diff that looks perfectly fine. The bottleneck moved from production to judgment. We all became editors-in-chief of a very fast, very confident, very junior writer who never gets tired and never says "I'm not sure."&lt;/p&gt;

&lt;p&gt;And editing is a &lt;em&gt;harder&lt;/em&gt; job than writing when you do it honestly. Anyone who's done real code review knows the trap: the subtle bug doesn't live in the code that looks wrong, it lives in the code that looks right. Reviewing AI output is that, all day, at volume. The "10x productivity" stories quietly assume the verification is free — that someone reads the generated code with the same care they'd have spent writing it. Mostly, nobody does. We're currently shipping a civilization-sized experiment on what happens when generation outruns verification, and the results aren't in yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Someone still has to sign
&lt;/h2&gt;

&lt;p&gt;Which is why the most durable thing about this job isn't a skill at all. When production goes down at 3am, when the regulator asks why the system did what it did, when a migration eats data — a human is accountable. Not metaphorically: contractually, legally, reputationally. An LLM cannot be responsible. It can't be fired, can't testify, can't feel the specific cold sweat of having broken prod. Responsibility is load-bearing in every engineering discipline, and it doesn't transfer to tools — the bridge engineer's stamp survived CAD.&lt;/p&gt;

&lt;p&gt;But notice what that implies, because it's not comfortable: signing for something you don't understand isn't accountability, it's liability cosplay. The moat isn't "being human." It's &lt;em&gt;understanding what you ship&lt;/em&gt;. The engineers who let that understanding atrophy — who approve what they can no longer explain — are converting themselves into a rubber stamp, and rubber stamps are very, very automatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is actually cooked
&lt;/h2&gt;

&lt;p&gt;So no, I don't think the profession is cooked. But one piece of it is on fire, and we're not talking about it enough: the ladder.&lt;/p&gt;

&lt;p&gt;Every senior engineer was forged the same way — years of boring tickets. The CRUD endpoint, the pagination bug, the form validation. We treated that work as grunt work, but it was secretly the apprenticeship: ten thousand small encounters with how systems actually fail, compounding into the instincts that make a senior senior. That's exactly the work AI eats first, and it eats it cheerfully. Why give the junior three days when the agent takes ten minutes?&lt;/p&gt;

&lt;p&gt;Run that policy for a decade and you get a profession with no new seniors — editors-in-chief who never wrote, signatories who never built the understanding their signature claims. Juniors aren't cooked because AI replaces them; they're cooked if we stop letting them do the work that was always more education than output. The fix isn't complicated, but it is a choice: keep giving humans work the machine could do, on purpose, the way pilots still hand-fly approaches the autopilot could handle — because the skill has to exist in a human for the day it's needed. Some team leads make that choice. The ones who don't are strip-mining their own future.&lt;/p&gt;

&lt;h2&gt;
  
  
  So: are we?
&lt;/h2&gt;

&lt;p&gt;Here's the split, then. If "the job" means converting tickets into code that probably works — yes, cooked, on a timeline measured in years, and pretending otherwise is a disservice to everyone planning a career. If "the job" means owning systems: deciding what to build, knowing why it's built that way, catching the confident wrong answer, signing for the result and meaning it — not cooked. In more demand than ever, actually, because the volume of software is exploding and every line of it eventually needs an adult.&lt;/p&gt;

&lt;p&gt;The discomfort is that nobody gets to claim the second job by seniority or by job title. You claim it by still understanding what you ship — which, in the age of infinite generated code, is a daily, deliberate act of swimming upstream. I use these tools all day. They wrote none of the decisions in this post, and if a day comes when I can't tell whether they got those right either — that's the day I'm cooked, and it won't have been the machine that did it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>softwareengineering</category>
      <category>opinion</category>
    </item>
    <item>
      <title>inject() and DestroyRef quietly killed my ngOnDestroy boilerplate</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/inject-and-destroyref-quietly-killed-my-ngondestroy-boilerplate-2bko</link>
      <guid>https://dev.to/aelmufti/inject-and-destroyref-quietly-killed-my-ngondestroy-boilerplate-2bko</guid>
      <description>&lt;p&gt;There's a constructor I've written a thousand times and don't write anymore:&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;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&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;Router&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;route&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&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;destroy$&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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;// ...and the ritual that goes with it&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;Plus the matching ritual at the bottom of the class: &lt;code&gt;ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }&lt;/code&gt;, and &lt;code&gt;takeUntil(this.destroy$)&lt;/code&gt; sprinkled on every subscription, and a code review comment when someone forgot one. None of that survives in the code I write today, and the replacements are three small APIs that compose into something genuinely better.&lt;/p&gt;

&lt;h2&gt;
  
  
  inject(): not just nicer syntax
&lt;/h2&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;UserPanel&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;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;private&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aesthetic win is obvious. The structural wins are the reason to switch. &lt;strong&gt;Inheritance stops hurting:&lt;/strong&gt; a base class that uses &lt;code&gt;inject()&lt;/code&gt; doesn't force every subclass to declare and forward constructor parameters — if you've ever touched a base component used by thirty subclasses and then spent the day updating thirty constructors, that sentence is the whole argument. &lt;strong&gt;Functional everything:&lt;/strong&gt; guards, resolvers and interceptors are plain functions now, and &lt;code&gt;inject()&lt;/code&gt; is the only way they get dependencies:&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;authGuard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanActivateFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&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;AuthService&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="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;()&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="nf"&gt;createUrlTree&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&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;And the one that changed how I structure code — &lt;strong&gt;composition helpers&lt;/strong&gt;. Because any function called from an injection context can itself call &lt;code&gt;inject()&lt;/code&gt;, you can extract cross-cutting patterns into plain functions:&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;injectQueryParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&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;Signal&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&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;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;return&lt;/span&gt; &lt;span class="nf"&gt;toSignal&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;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&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;name&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;initialValue&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="c1"&gt;// in any component:&lt;/span&gt;
&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;injectQueryParam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's dependency injection, RxJS interop and cleanup bundled in a named, testable, reusable unit. The React folks call these hooks and built a whole culture on them; Angular got the same power almost quietly.&lt;/p&gt;

&lt;h2&gt;
  
  
  DestroyRef: cleanup without the ceremony
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DestroyRef&lt;/code&gt; is injectable like anything else, and its one job is to run callbacks when the current scope is destroyed:&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;ChartPanel&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;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="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;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderChart&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;el&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;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;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&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 what's gone: implementing &lt;code&gt;OnDestroy&lt;/code&gt;, the override chain when a base class also had cleanup, the distance between acquiring a resource and releasing it. Setup and teardown sit next to each other — the resource's whole lifecycle is readable in one place. And because helpers can inject it, your composition functions clean up after themselves without the caller knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  takeUntilDestroyed(): the destroy$ ritual, retired
&lt;/h2&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;PriceTicker&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;ws&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;WebSocketService&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;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prices$&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;takeUntilDestroyed&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;p&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;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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;Called in a constructor or field initializer, it needs zero arguments — it picks up &lt;code&gt;DestroyRef&lt;/code&gt; from the injection context by itself. The &lt;code&gt;destroy$&lt;/code&gt; subject, its two-line &lt;code&gt;ngOnDestroy&lt;/code&gt;, and the review comment about the missing &lt;code&gt;takeUntil&lt;/code&gt;: all deleted. If you use it outside the constructor (say, in a callback), pass the ref explicitly — &lt;code&gt;takeUntilDestroyed(this.destroyRef)&lt;/code&gt; — which brings us to the rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule that bites everyone once
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;inject()&lt;/code&gt; only works in an &lt;em&gt;injection context&lt;/em&gt;: field initializers, the constructor, provider factories. Not in &lt;code&gt;ngOnInit&lt;/code&gt;. Not in a &lt;code&gt;setTimeout&lt;/code&gt;. Not in your event handler. The error message (&lt;code&gt;NG0203&lt;/code&gt;) is famous enough to be a meme, and everyone earns it exactly 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="nf"&gt;ngOnInit&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;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ NG0203 — too late, context is gone&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental model that makes it stick: the injection context isn't magic, it's a moment — the moment your class is being constructed, when Angular knows which injector is "current". After construction, that knowledge is gone. So grab everything you need during construction (field initializers are the natural place), and if you genuinely must resolve dependencies later, that's what &lt;code&gt;runInInjectionContext(injector, fn)&lt;/code&gt; is for — an explicit, greppable escape hatch instead of an accident.&lt;/p&gt;

&lt;p&gt;Put together, the pattern I now consider baseline for any component: dependencies as &lt;code&gt;inject()&lt;/code&gt; field initializers, subscriptions wearing &lt;code&gt;takeUntilDestroyed()&lt;/code&gt;, ad-hoc resources registered with &lt;code&gt;DestroyRef.onDestroy()&lt;/code&gt;, and anything repeated twice extracted into an &lt;code&gt;injectX()&lt;/code&gt; helper. Less ceremony, but that's the surface benefit. The real one is that lifecycle bugs — the leaked subscription, the chart that outlives its tab — stopped being a category of bug I find in review. The framework didn't make them rarer; it made them hard to write.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>dependencyinjection</category>
      <category>inject</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Where signals end and RxJS begins</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/where-signals-end-and-rxjs-begins-60k</link>
      <guid>https://dev.to/aelmufti/where-signals-end-and-rxjs-begins-60k</guid>
      <description>&lt;p&gt;Every few months someone declares RxJS dead, and every few months I open a codebase where someone took that literally — and rebuilt &lt;code&gt;debounceTime&lt;/code&gt; by hand with an effect, a signal, and a &lt;code&gt;setTimeout&lt;/code&gt;. It's twenty lines, it has a race condition, and it makes the case better than I can: signals didn't replace streams. They took over half the job, the half they're better at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The division of labor
&lt;/h2&gt;

&lt;p&gt;Ask one question about the thing you're modeling: is it a &lt;em&gt;value&lt;/em&gt; or an &lt;em&gt;occurrence&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;A value has a current state that's always meaningful to read: the logged-in user, the selected tab, the filter text, the cart. That's signal territory — synchronous reads, glitch-free derivation with &lt;code&gt;computed()&lt;/code&gt;, and the template just works. An occurrence is a thing that &lt;em&gt;happens&lt;/em&gt;: a keystroke, a websocket frame, a retry with backoff, a request that should cancel its predecessor. Asking for its "current value" doesn't even make sense — what's the current value of a click? That's a stream, and operators like &lt;code&gt;debounceTime&lt;/code&gt;, &lt;code&gt;switchMap&lt;/code&gt; and &lt;code&gt;retry&lt;/code&gt; are decades of distilled answers to time problems signals don't address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossing the border, both directions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;toSignal()&lt;/code&gt; turns a stream into state. Two details matter more than the docs make obvious. It &lt;em&gt;subscribes immediately&lt;/em&gt; — at the call site, not on first read — so calling it lives in an injection context and cleanup is handled for you. And the initial value question is forced on you, which is a feature: an observable may not have emitted yet, so you either provide &lt;code&gt;initialValue&lt;/code&gt;, accept &lt;code&gt;undefined&lt;/code&gt; in the type, or assert &lt;code&gt;requireSync&lt;/code&gt; for things like a &lt;code&gt;BehaviorSubject&lt;/code&gt; that emit on subscription.&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;prices&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prices$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;initialValue&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;toObservable()&lt;/code&gt; goes the other way, and the canonical use is the typeahead — which is also the cleanest demonstration of the whole philosophy:&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;query&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="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;results&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;query&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;300&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;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&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;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&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;initialValue&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;State enters as a signal. The &lt;em&gt;time problems&lt;/em&gt; — wait for the user to stop typing, drop stale requests — are handled by the two operators that exist precisely for them. The result comes back out as state. Each tool does the part it was built for, and the seam is two function calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The smell test
&lt;/h2&gt;

&lt;p&gt;When reviewing, I use a symmetric pair of smells. An &lt;code&gt;effect()&lt;/code&gt; that contains &lt;code&gt;setTimeout&lt;/code&gt;, manual cancellation flags, or "ignore this run if a newer one started" bookkeeping — that's a stream wearing a signal costume; it wants five lines of RxJS. Conversely, a service juggling a &lt;code&gt;BehaviorSubject&lt;/code&gt;, &lt;code&gt;scan&lt;/code&gt;, and &lt;code&gt;shareReplay&lt;/code&gt; just to expose "the current list with its loading flag" — that's state wearing a stream costume; it wants to be a signal (or, if HTTP is involved, a &lt;code&gt;resource()&lt;/code&gt;, which already ate most of my data-fetching streams — I wrote about that separately).&lt;/p&gt;

&lt;p&gt;So the honest status of RxJS in my code: smaller, and better. It left the places it was always awkward — component state, template consumption — and kept the places nothing else touches: coordinating things that happen over time. Fewer streams, but every one that remains is doing work only a stream can do. That's not a technology dying; that's a technology finding its actual shape.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>signals</category>
      <category>rxjs</category>
      <category>interop</category>
    </item>
    <item>
      <title>Auth, retry, logging: three interceptors, zero classes</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/auth-retry-logging-three-interceptors-zero-classes-3jh2</link>
      <guid>https://dev.to/aelmufti/auth-retry-logging-three-interceptors-zero-classes-3jh2</guid>
      <description>&lt;p&gt;Interceptors used to be the most ceremony-per-feature API in Angular: a class, an interface, a multi-provider registration with &lt;code&gt;HTTP_INTERCEPTORS&lt;/code&gt; and &lt;code&gt;multi: true&lt;/code&gt; — the line everyone copy-pasted and nobody could write from memory. The functional version is just... a function, registered in order:&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;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;withInterceptors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retryInterceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loggingInterceptor&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 are the three I end up writing on basically every project, with the details that distinguish "works in the demo" from "works in production".&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth: clone, don't mutate — and let some requests through
&lt;/h2&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;authInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpInterceptorFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&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;SKIP_AUTH&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;token&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;TokenStore&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;token&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;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;setHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests are immutable, so it's &lt;code&gt;clone()&lt;/code&gt; or nothing. The part worth copying is the first line: &lt;code&gt;SKIP_AUTH&lt;/code&gt; is an &lt;code&gt;HttpContextToken&lt;/code&gt;, and it solves the "but the login call itself shouldn't carry a token" problem without the URL-matching if-chains that grow hair over time:&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;SKIP_AUTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HttpContextToken&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="o"&gt;=&amp;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;// at the call site that needs the exception:&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&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;SKIP_AUTH&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;The exception is declared &lt;em&gt;where the exception is&lt;/em&gt;, not in a growing denylist inside the interceptor. Six months later, that's the difference between reading one line and archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retry: the interceptor that can double-charge someone
&lt;/h2&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;retryInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpInterceptorFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&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="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;retry&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;count&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;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&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;retryCount&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HttpErrorResponse&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;status&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4xx: our fault, don't retry&lt;/span&gt;
        &lt;span class="k"&gt;return&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;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&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;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 2s, 4s&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two guards carry all the weight. &lt;strong&gt;Only GETs:&lt;/strong&gt; a POST that times out may have &lt;em&gt;succeeded&lt;/em&gt; server-side — retry it and you've created the duplicate order / double payment incident that ends up with your name on the postmortem. &lt;strong&gt;Only 5xx and network errors:&lt;/strong&gt; retrying a 401 or a 404 is asking the same question louder. With those two rules, automatic retry goes from scary to boring — and a flaky corporate proxy mostly disappears from your error tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging: only the requests that deserve it
&lt;/h2&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;loggingInterceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpInterceptorFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;finalize&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;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&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;ms&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&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="s2"&gt;`[slow] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlWithParams&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="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;ms&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;ms`&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;Logging every request is noise nobody reads. Logging requests over a threshold gives you something I've found disproportionately useful: the slow-endpoint report assembles itself in the console while you develop, and the worst offenders become impossible to not know about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Order is not a detail
&lt;/h2&gt;

&lt;p&gt;The array order is execution order on the way out, reversed on the way back. With &lt;code&gt;[auth, retry, logging]&lt;/code&gt;: auth runs first, so &lt;em&gt;every retry attempt carries the token&lt;/em&gt; — flip them and a token refresh between attempts can send a stale header. And because logging sits innermost, it times each attempt rather than the sum. When an interceptor chain misbehaves, the order is the first thing I check, and it's the thing the type system can't check for you: the array compiles in any order. It just doesn't &lt;em&gt;work&lt;/em&gt; in any order.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>http</category>
      <category>interceptors</category>
      <category>rxjs</category>
    </item>
    <item>
      <title>Two letters that fixed my LCP: ngSrc</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/two-letters-that-fixed-my-lcp-ngsrc-5e1a</link>
      <guid>https://dev.to/aelmufti/two-letters-that-fixed-my-lcp-ngsrc-5e1a</guid>
      <description>&lt;p&gt;Images are where Core Web Vitals go to die. The biggest element on most pages is an image (that's literally what LCP measures, most of the time), the thing that jumps the layout around is usually an image arriving late, and the bytes that dwarf your carefully code-split bundle are — images. The fix in Angular costs two letters:&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;img&lt;/span&gt; &lt;span class="na"&gt;ngSrc=&lt;/span&gt;&lt;span class="s"&gt;"/assets/team.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"533"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"The team"&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;Same element, &lt;code&gt;ngSrc&lt;/code&gt; instead of &lt;code&gt;src&lt;/code&gt;, plus the directive imported. What you get for that is a small pile of best practices that you no longer have to remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lazy by default, and the one image that mustn't be
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;ngSrc&lt;/code&gt; image is &lt;code&gt;loading="lazy"&lt;/code&gt; and &lt;code&gt;decoding="async"&lt;/code&gt; unless told otherwise — correct for every image below the fold, which is most of them. The exception is the point: your LCP image, the hero, must be the opposite — fetched as early as possible. That's &lt;code&gt;priority&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;ngSrc=&lt;/span&gt;&lt;span class="s"&gt;"/assets/hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"..."&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;One attribute sets eager loading and &lt;code&gt;fetchpriority="high"&lt;/code&gt;, and the SSR pipeline emits a preload hint so the browser starts the download before it has even parsed down to the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag. And here's my favorite part of this directive's design: if you &lt;em&gt;forget&lt;/em&gt; — if the image Angular detects as your LCP element isn't marked &lt;code&gt;priority&lt;/code&gt; — it tells you in the console, in dev mode. An optimization that audits its own usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mandatory dimensions are the feature
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; are required, and people's first reaction is annoyance — mine was. But this is CLS prevention &lt;em&gt;by API design&lt;/em&gt;: with intrinsic dimensions the browser reserves the right box before a single byte arrives, and nothing jumps when the pixels land. The directive turned "remember to set dimensions" — a discipline that erodes — into "the build won't let you forget". (The numbers define the &lt;em&gt;ratio&lt;/em&gt;; CSS still controls display size.) For the genuinely-unknown case — a user-uploaded background, a CMS image — there's &lt;code&gt;fill&lt;/code&gt;, which absolutely-positions the image into a sized parent, paired with &lt;code&gt;object-fit&lt;/code&gt; in your CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The srcset you were never going to write by hand
&lt;/h2&gt;

&lt;p&gt;Serving a 1200px image to a 360px phone screen wastes most of the bytes. The honest fix is a responsive &lt;code&gt;srcset&lt;/code&gt;, and almost nobody writes those by hand because they're tedious and they rot. With a loader configured, the directive generates them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.config.ts — one line, pick your CDN&lt;/span&gt;
&lt;span class="nf"&gt;provideImgixLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://my-site.imgix.net/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="c1"&gt;// every ngSrc image now emits srcset variants automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Built-in loaders exist for the usual suspects (Imgix, Cloudinary, ImageKit, Netlify…), and a custom loader is a function mapping &lt;code&gt;(src, width)&lt;/code&gt; to a URL — ten lines if your backend can resize. This is the difference between the directive being "nice" and being a bandwidth line-item: the phone gets the 400px file, the retina desktop gets the 1600px one, and nobody on the team maintains a single srcset string.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest scope
&lt;/h2&gt;

&lt;p&gt;What &lt;code&gt;NgOptimizedImage&lt;/code&gt; doesn't do: convert formats, crush file sizes, or host anything — that's the CDN/build pipeline's job; the directive just asks for the right variant at the right moment. And on a page with three images, the gains are real but modest. The compounding case is the image-heavy app — listings, galleries, media — where lazy-by-default alone can cut initial page weight dramatically. Run Lighthouse before and after on one route. That number is how you sell the two-letter change to whoever approves the sprint.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>performance</category>
      <category>images</category>
      <category>lcp</category>
    </item>
    <item>
      <title>The dropdown that wouldn't reset — a linkedSignal story</title>
      <dc:creator>Ali</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/aelmufti/the-dropdown-that-wouldnt-reset-a-linkedsignal-story-31k1</link>
      <guid>https://dev.to/aelmufti/the-dropdown-that-wouldnt-reset-a-linkedsignal-story-31k1</guid>
      <description>&lt;p&gt;A search page. A list of results, one of them selected. The user types, the results change — and the selection now points at an item that no longer exists. Every Angular developer has shipped this bug at least once, because for years the framework gave us no primitive that fit the shape of the problem.&lt;/p&gt;

&lt;p&gt;Think about what the selection actually is. It's &lt;em&gt;derived&lt;/em&gt; from the results — when they change, it should reset. But it's also &lt;em&gt;writable&lt;/em&gt; — the user clicks to change it. &lt;code&gt;computed()&lt;/code&gt; gives you derived but read-only. &lt;code&gt;signal()&lt;/code&gt; gives you writable but disconnected. The selection is both, and both tools are wrong alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two workarounds we all wrote
&lt;/h2&gt;

&lt;p&gt;Workaround one: a writable signal plus an effect that resets it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;selected&lt;/span&gt; &lt;span class="o"&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;Result&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;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="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;results&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// subscribe to the list&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;selected&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="c1"&gt;// reset on change&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 works, until it doesn't: the effect runs &lt;em&gt;after&lt;/em&gt; change detection, so there's a frame where the template renders a selection pointing into the old list. Workaround two is sprinkling &lt;code&gt;selected.set(null)&lt;/code&gt; at every call site that touches the list — which holds up exactly until a new call site forgets.&lt;/p&gt;

&lt;h2&gt;
  
  
  What linkedSignal actually says
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&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;Result&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;selected&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="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;results&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="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="c1"&gt;// user clicks → plain write, like any signal&lt;/span&gt;
&lt;span class="nf"&gt;select&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;Result&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;selected&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;r&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;Read it as a sentence: "&lt;code&gt;selected&lt;/code&gt; follows &lt;code&gt;results&lt;/code&gt;, unless someone wrote to it more recently — and the moment &lt;code&gt;results&lt;/code&gt; changes again, following resumes." Derivation and overridability in one declaration, recomputed &lt;em&gt;synchronously&lt;/em&gt; with the source, so the stale-frame window from the effect version doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The previous-value trick
&lt;/h2&gt;

&lt;p&gt;Resetting to the first item is sometimes rude. If the user's selection survived the list update, keep it — and the long form hands you what you need:&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;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;linkedSignal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;results&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;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;previous&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;kept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;previous&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="nx"&gt;id&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;kept&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;list&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="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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The computation receives the new source value &lt;em&gt;and&lt;/em&gt; the previous state of the linked signal. "Keep the selection if it still exists, otherwise fall back" — the behavior users actually expect from a well-made UI, expressed in four lines, in one place, instead of scattered across resets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it doesn't belong
&lt;/h2&gt;

&lt;p&gt;Two boundaries keep it honest. If the value is purely derived and never user-written, that's &lt;code&gt;computed()&lt;/code&gt; — reaching for &lt;code&gt;linkedSignal&lt;/code&gt; there just advertises writability you don't want. And if the "source" is asynchronous — the selection should reset when an HTTP call returns — the deriving belongs in &lt;code&gt;resource()&lt;/code&gt; land, not here; linkedSignal is synchronous by nature.&lt;/p&gt;

&lt;p&gt;Small API, narrow purpose. But the bug it removes is one I'd been finding in code reviews for years, and now the fix is a type signature instead of a convention someone has to remember. That trade is the story of modern Angular in one primitive.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>signals</category>
      <category>linkedsignal</category>
      <category>state</category>
    </item>
  </channel>
</rss>
