<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Angular: From Zero to Expert]]></title><description><![CDATA[Your journey to Angular mastery starts here]]></description><link>https://www.codigotipado.com</link><image><url>https://substackcdn.com/image/fetch/$s_!AiG8!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8721161d-0d9e-4e1f-b7ac-fa66332ba0f9_1280x1280.png</url><title>Angular: From Zero to Expert</title><link>https://www.codigotipado.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 01 May 2026 15:14:34 GMT</lastBuildDate><atom:link href="https://www.codigotipado.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Amos Isaila]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[amosisaila@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[amosisaila@substack.com]]></itunes:email><itunes:name><![CDATA[Amos Isaila]]></itunes:name></itunes:owner><itunes:author><![CDATA[Amos Isaila]]></itunes:author><googleplay:owner><![CDATA[amosisaila@substack.com]]></googleplay:owner><googleplay:email><![CDATA[amosisaila@substack.com]]></googleplay:email><googleplay:author><![CDATA[Amos Isaila]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[You Can Now Control What Binds to Your Component Inputs in Angular 22]]></title><description><![CDATA[I've been using withComponentInputBinding() since Angular 16 and honestly, it's one of my favorite router features. No more injecting ActivatedRoute, no more subscribing to params.]]></description><link>https://www.codigotipado.com/p/you-can-now-control-what-binds-to</link><guid isPermaLink="false">https://www.codigotipado.com/p/you-can-now-control-what-binds-to</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 06 Apr 2026 21:40:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!feJ8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!feJ8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!feJ8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 424w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 848w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 1272w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!feJ8!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png" width="1200" height="685.7142857142857" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:768,&quot;width&quot;:1344,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1907019,&quot;alt&quot;:&quot;Angular 22: withComponentInputBinding&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/193396918?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22: withComponentInputBinding" title="Angular 22: withComponentInputBinding" srcset="https://substackcdn.com/image/fetch/$s_!feJ8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 424w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 848w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 1272w, https://substackcdn.com/image/fetch/$s_!feJ8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff14f6c86-facb-4895-8f9c-ed831252cf04_1344x768.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The short version</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;5fcbd6b5-aa81-4423-8ec6-790980f439b7&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">provideRouter(routes, withComponentInputBinding({ queryParams: false }))</code></pre></div><p>That&#8217;s it. Query params no longer bind to your component inputs. Path params and route data? Still work fine.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>So what was the problem?</h2><p>Let me show you. Say you have this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ad22c3a7-fd3a-494f-b41b-66fc5c190c30&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Route: { path: 'user/:id', component: UserComponent, data: { role: 'admin' } }
// URL: /user/42?tab=settings

@Component({ ... })
export class UserComponent {
  id = input&lt;string&gt;();       // &#8592; from :id
  tab = input&lt;string&gt;();      // &#8592; from ?tab=
  role = input&lt;string&gt;();     // &#8592; from route data
}</code></pre></div><p>All three sources &#8212; path params, query params, route data &#8212; bind to your inputs automatically. Sounds great until you realize that <strong>query params are completely user-controlled</strong>. Anyone can type whatever they want in the URL bar.</p><p>Picture this:</p>
      <p>
          <a href="https://www.codigotipado.com/p/you-can-now-control-what-binds-to">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 22: Debounced Signal]]></title><description><![CDATA[How Angular turned signal debouncing into a first-class primitive &#8212; and why it returns a Resource.]]></description><link>https://www.codigotipado.com/p/angular-22-debounced-signal</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-22-debounced-signal</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 30 Mar 2026 21:07:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!queC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!queC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!queC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!queC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!queC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!queC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!queC!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:937209,&quot;alt&quot;:&quot;Angular 22: Debounced&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/191804884?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22: Debounced" title="Angular 22: Debounced" srcset="https://substackcdn.com/image/fetch/$s_!queC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!queC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!queC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!queC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56551152-5bb8-4258-8e23-e7e92da1b75c_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Before you start diving into this new topic you can review the <a href="https://www.codigotipado.com/p/angular-19-resource-api-for-async">full guide of resource() that was introduced in Angular 19</a>.</p><h2>TL;DR</h2><p>Angular added a <code>debounced()</code> function that takes a signal and a wait time, and returns a <strong>Resource</strong>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;8502c48b-10d4-49ad-a9e5-8cd3bed622e6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">signal &#8594; debounced(signal, 500) &#8594; Resource&lt;T&gt;</code></pre></div><p>The resource&#8217;s <code>value()</code> holds the debounced value. Its <code>status</code> tells you whether the value is settled (<code>'resolved'</code>), still waiting (<code>'loading'</code>), or if the source threw (<code>'error'</code>). No RxJS, no <code>setTimeout</code> wrappers, no custom hooks. Just signals in, Resource out.</p><h2>The Problem</h2><p>You have a search input. The user types &#8220;Tokyo&#8221;. Without debouncing, you fire 5 HTTP requests: one per keystroke. That&#8217;s wasteful, potentially slow, and can cause race conditions if responses arrive out of order.</p><p><strong>The RxJS way:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;430be340-e35b-4da3-89a8-8b5f6e5dde3f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">searchControl.valueChanges.pipe(
  debounceTime(500),
  distinctUntilChanged(),
  switchMap(query =&gt; this.http.get(`/api/search?q=${query}`))
).subscribe(results =&gt; { ... });</code></pre></div><p><strong>The manual way:</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;5e1bb7d4-2c3c-45e3-8f87-2e3ad51a3a09&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">let timer: ReturnType&lt;typeof setTimeout&gt;;
function onInput(value: string) {
  clearTimeout(timer);
  timer = setTimeout(() =&gt; {
    // now do something with value
  }, 500);
}</code></pre></div><p>Both work. Neither integrates with signals. You end up bridging between observables or callbacks and signals, losing reactivity along the way.</p><p>The real gap: there was no signal-native way to debounce a value and get a reactive, composable result back.</p><h2>The Solution: <code>debounced()</code></h2><h3>The API</h3><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;c3dbf6d7-2d4f-438e-8a09-701469c06e99&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { debounced } from '@angular/core';

const search = signal('');
const debouncedSearch = debounced(() =&gt; search(), 500);</code></pre></div><p><code>debouncedSearch</code> is a <code>Resource&lt;string&gt;</code>. It has everything you&#8217;d expect:</p><ul><li><p><code>debouncedSearch.value()</code> &#8212; the debounced value (updates only after 500ms of silence)</p></li><li><p><code>debouncedSearch.status()</code> &#8212; <code>'resolved'</code> when settled, <code>'loading'</code> while waiting, <code>'error'</code> if the source threw</p></li><li><p><code>debouncedSearch.isLoading()</code> &#8212; <code>true</code> while a value is pending</p></li><li><p><code>debouncedSearch.error()</code> &#8212; the error, if the source signal threw</p></li></ul><h3>Full Signature</h3>
      <p>
          <a href="https://www.codigotipado.com/p/angular-22-debounced-signal">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 21.2: Resource Composition via Snapshots]]></title><description><![CDATA[A deep dive into Angular's new snapshot-based pattern for transforming and composing resources without losing their interface.]]></description><link>https://www.codigotipado.com/p/angular-212-resource-composition</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-212-resource-composition</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Sun, 22 Mar 2026 21:31:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cJhZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cJhZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cJhZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 424w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 848w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 1272w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cJhZ!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png" width="1200" height="674.0625" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:719,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:718675,&quot;alt&quot;:&quot;Angular 21.2: Resource Snapshost&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/191735733?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 21.2: Resource Snapshost" title="Angular 21.2: Resource Snapshost" srcset="https://substackcdn.com/image/fetch/$s_!cJhZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 424w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 848w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 1272w, https://substackcdn.com/image/fetch/$s_!cJhZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656702e6-2002-4a1a-bfe1-11d1ee072ac9_1280x719.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Before you start diving into this new topic you can review the <a href="https://www.codigotipado.com/p/angular-19-resource-api-for-async">full guide of resource() that was introduced in Angular 19</a>.</p><h2>TL;DR</h2><p>Angular added three things that let you take a resource, modify its behavior, and get back a real resource.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;2676756c-cf8b-4922-9f3f-f302192465b5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Resource  &#8594;  .snapshot  &#8594;  transform it  &#8594;  resourceFromSnapshots()  &#8594;  Resource</code></pre></div><p>The killer use case? <strong>Keep showing old data while new data loads</strong> &#8212; no more blank screens when switching between items. And it&#8217;s a generic utility you write once and reuse everywhere.</p><p><strong>PR</strong>: <a href="https://github.com/angular/angular/commit/1ba9b7ac5001b315cc9df78c518964dbf479d647">feat(core): resource composition via snapshots</a><br><strong>Authors</strong>: Alex Rickabaugh &amp; Jessica Janiuk<br><strong>Status</strong>: Experimental &#183; <strong>Angular 21.2+</strong></p><p><strong><a href="https://github.com/amosISA/angular-signal-forms/tree/angular-21.2">Code Repository Example in this article can be found here</a>.</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The Problem (in Plain English)</h2><p>Imagine you have a weather app. The user is looking at London&#8217;s weather. They click &#8220;Tokyo.&#8221; What happens?</p><p><strong>Before this PR:</strong></p><ol><li><p>The resource enters &#8220;loading&#8221; state</p></li><li><p><code>value()</code> immediately becomes <code>undefined</code></p></li><li><p>The weather card <strong>disappears</strong> &#8212; blank screen, skeleton loader</p></li><li><p>Tokyo&#8217;s data arrives, card re-appears</p></li></ol><p>That flash of empty content feels broken. Users hate it.</p><p>You might think: <em>&#8220;I&#8217;ll just keep the old value around in a separate signal.&#8221;</em> Sure, but then you lose the <code>Resource</code> interface. Your workaround doesn&#8217;t have <code>.isLoading()</code>, <code>.hasValue()</code>, <code>.status()</code>. It&#8217;s a one-off hack that doesn&#8217;t compose.</p><p><strong>The real problem:</strong> there was no way to take a <code>Resource</code>, tweak its behavior, and get back something that&#8217;s still a <code>Resource</code>.</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-212-resource-composition">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Migrating to Signal Forms: The SignalFormControl Bridge in Angular 21.2]]></title><description><![CDATA[You don't have to rewrite all your forms at once. Angular 21.2 ships two interop tools that let you migrate one field &#8212; or one form &#8212; at a time.]]></description><link>https://www.codigotipado.com/p/migrating-to-signal-forms-the-signalformcontrol</link><guid isPermaLink="false">https://www.codigotipado.com/p/migrating-to-signal-forms-the-signalformcontrol</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Sat, 14 Mar 2026 22:14:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!O2M7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O2M7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O2M7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O2M7!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1206915,&quot;alt&quot;:&quot;Angular 21.2.0: SignalFormControl&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/190912876?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 21.2.0: SignalFormControl" title="Angular 21.2.0: SignalFormControl" srcset="https://substackcdn.com/image/fetch/$s_!O2M7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!O2M7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9be8e0a9-02e1-43cc-b46c-eb1657b11e28_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>The Problem Nobody Talks About</strong></h2><p>Every time Angular introduces a new API, the community gets excited about greenfield projects. &#8220;Look how clean this is!&#8221; And it <em>is</em> clean. Signal forms are a genuine improvement over reactive forms &#8212; less boilerplate, better type safety, signals instead of observables.</p><p>But here&#8217;s the thing: most of us aren&#8217;t building new apps from scratch. We&#8217;re maintaining applications with dozens &#8212; sometimes hundreds &#8212; of reactive forms. Forms that work. Forms that are tested. Forms that have edge cases baked into them over years of production use.</p><p>The question isn&#8217;t &#8220;are signal forms better?&#8221; &#8212; they are. The question is: <strong>how do you get there without rewriting everything?</strong></p><p>Angular 21.2 answers this with two interop tools:</p><ol><li><p><code>SignalFormControl</code> &#8212; A signal-based control that extends <code>AbstractControl</code>, so it plugs directly into existing <code>FormGroup</code> and <code>FormArray</code> structures</p></li><li><p><code>compatForm</code> &#8212; A version of <code>form()</code> that accepts reactive <code>FormControl</code> instances as part of the data model</p></li></ol><p>Both are exported from <code>@angular/forms/signals/compat</code>. Both are experimental. And both solve different migration scenarios.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Understanding the Two Migration Paths</strong></h2><p>Before diving into code, it&#8217;s worth understanding when to use each approach.</p><p><code>SignalFormControl</code> is for when you want to <strong>migrate individual fields</strong> inside an existing reactive form. You replace one <code>FormControl</code> with a <code>SignalFormControl</code>, and the parent <code>FormGroup</code> doesn&#8217;t even notice &#8212; it just sees an <code>AbstractControl</code>.</p><p><code>compatForm</code> is for when you want to <strong>migrate the form itself</strong> to signal forms, but some fields are still <code>FormControl</code> instances that you&#8217;re not ready to convert yet.</p><p>Think of it this way:</p><ul><li><p>I want to add signal validation to one field in my reactive form &#8594; <code>SignalFormControl</code>Reactive form &#8594; signal field</p></li><li><p>I want to use <code>form()</code> but keep some existing <code>FormControl</code>s &#8594; <code>compatForm</code>Signal form &#8594; reactive fields</p></li></ul><p>Most teams will start with <code>SignalFormControl</code> because it&#8217;s the least disruptive. You change one field, ship it, observe, then do the next one.</p><h2><code>SignalFormControl</code><strong>: Your First Migration Step</strong></h2><h3><strong>What It Is</strong></h3><p><code>SignalFormControl</code> extends <code>AbstractControl</code> &#8212; the same base class that <code>FormControl</code>, <code>FormGroup</code>, and <code>FormArray</code> extend. This means it&#8217;s a drop-in replacement anywhere Angular&#8217;s reactive forms infrastructure expects a control.</p><p>Internally, it&#8217;s backed by a <code>WritableSignal&lt;T&gt;</code> and a signal forms <code>FieldTree</code>. It translates between the two worlds: when the reactive form reads <code>.value</code>, it gets the signal&#8217;s current value. When signal forms validation runs, the results are exposed through the reactive <code>.errors</code> getter.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;6bc72854-4132-46d2-84c1-805d70a23046&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { SignalFormControl } from '@angular/forms/signals/compat';</code></pre></div>
      <p>
          <a href="https://www.codigotipado.com/p/migrating-to-signal-forms-the-signalformcontrol">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 21.2: What's new]]></title><description><![CDATA[Angular 21.2.0 introduces TypeScript 6 support, exhaustive @switch checks, instanceof in templates, massive signal forms improvements, nested animations and a bunch of DX improvements.]]></description><link>https://www.codigotipado.com/p/angular-212-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-212-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Sun, 08 Mar 2026 22:41:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NRqH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NRqH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NRqH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NRqH!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:836942,&quot;alt&quot;:&quot;Angular 21.2 What's new&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/190231022?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 21.2 What's new" title="Angular 21.2 What's new" srcset="https://substackcdn.com/image/fetch/$s_!NRqH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!NRqH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc3a0eab3-c75f-426b-8da9-e1f297c4caf8_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2><strong>1. @angular/animations</strong></h2><h2><strong>2. @angular/build</strong></h2><h3><strong>a) Headless Option for Unit-Test Builder - <a href="https://github.com/angular/angular-cli/commit/ece30f2359c2dc794b0c9272447f623a121e88b0">PR</a></strong></h3><p>The unit-test builder now supports a <code>headless</code> option for browser-based tests. You can toggle between headless and headed mode without modifying your Vitest configuration.</p><pre><code><code>ng test --headless=false  # Run with visible browser for debugging
ng test                   # Default: headless</code></code></pre><h3><strong>b) Vitest Browser with Playwright OS Theme - <a href="https://github.com/angular/angular-cli/commit/cad7a7c0ff3778f04820a99ad0aa9d74f1067fd5">PR</a></strong></h3><p>When running Vitest browser tests with Playwright, the browser now respects your operating system&#8217;s theme (light/dark mode). This means your tests run in an environment that matches your system preferences, which can be relevant for testing theme-dependent UI behavior.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>3. @angular/common</strong></h2><h3><strong>a) Outlet Injector Option for </strong><code>ngTemplateOutlet</code><strong> - <a href="https://github.com/angular/angular/pull/55389">PR</a></strong></h3><p>To understand this feature, you first need to understand a quirk of how <code>ngTemplateOutlet</code> works with Angular&#8217;s dependency injection.</p><p><strong>The problem: templates resolve services from where they&#8217;re </strong><em><strong>defined</strong></em><strong>, not where they&#8217;re </strong><em><strong>rendered</strong></em><strong>.</strong></p><p>Imagine you define an <code>&lt;ng-template&gt;</code> at the root of your component. Inside that template, you try to <code>inject()</code> a service. Angular will look for that service starting from the <em>definition site</em> of the template (where the <code>&lt;ng-template&gt;</code> tag lives in the source code), not from where the template is actually stamped into the DOM via <code>*ngTemplateOutlet</code>.</p><p>Here&#8217;s a concrete example to make this tangible:</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-212-whats-new">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 21.1: What's new]]></title><description><![CDATA[This release enhances the new signal forms API with programmatic focus control, adds fall-through support to @switch control flow, and introduces spread operators in templates.]]></description><link>https://www.codigotipado.com/p/angular-211-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-211-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Thu, 22 Jan 2026 05:28:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RyIO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RyIO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RyIO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RyIO!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1319337,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/180478347?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RyIO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!RyIO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1a38b317-24a5-46bf-9611-cab929b9466e_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>1. @angular/common</strong></h2><h3>a) Custom Transformations for Cloudflare and Cloudinary Image Loaders - <a href="https://github.com/angular/angular/commit/d8790972bea4c59a208219dd36d158b5d7e4fdde">PR</a></h3><p>Adds support for custom transformations to Cloudinary and Cloudflare image loaders via a <code>transform</code> parameter.</p><p><a href="https://angular.dev/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage">You can learn more about How to Configure an image loader for NgOptimizedImage</a>.</p><h3>b) Custom Transformations for ImageKit and Imgix Loaders - <a href="https://github.com/angular/angular/commit/a6b8cb68afaded6999ee68f495512be1a9932ae4">PR</a></h3><p>Similar custom transformation support extends to ImageKit and Imgix, two other popular image CDN providers.</p><p><a href="https://angular.dev/guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage">You can learn more about How to Configure an image loader for NgOptimizedImage</a>.</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-211-whats-new">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Making Sense of Metadata in Angular Signal Forms]]></title><description><![CDATA[A practical guide using a real weather chatbot that validates cities against an API]]></description><link>https://www.codigotipado.com/p/making-sense-of-metadata-in-angular</link><guid isPermaLink="false">https://www.codigotipado.com/p/making-sense-of-metadata-in-angular</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 15 Dec 2025 07:11:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yt0Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;m going to walk you through metadata in Angular Signal Forms using a real project I built&#8212;a weather chatbot that checks if cities actually exist before letting you submit the form. No abstract examples, just working code solving actual problems.</p><p>Here&#8217;s what makes this interesting: the form doesn&#8217;t just validate that you typed something. It calls an API to verify the city exists in a weather database, manages the loading state while checking, caches results so you don&#8217;t hammer the server, and gives instant feedback. That&#8217;s where metadata becomes essential.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yt0Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yt0Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yt0Y!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09bd6998-b151-482a-966f-2482517567e3_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1021922,&quot;alt&quot;:&quot;Angular Signal Forms: Metadata&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/181649579?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular Signal Forms: Metadata" title="Angular Signal Forms: Metadata" srcset="https://substackcdn.com/image/fetch/$s_!yt0Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!yt0Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bd6998-b151-482a-966f-2482517567e3_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Core Problem</h2><p>Traditional validation answers simple questions: Is the field empty? Does it match this pattern? But modern forms need smarter behavior. When someone types &#8220;Barcelona, Spain,&#8221; you want to know:</p><ul><li><p>Does this city exist in your weather database?</p></li><li><p>Are we currently checking with the server?</p></li><li><p>Did we already validate this exact combination?</p></li><li><p>Should we show a spinner?</p></li></ul><p>Your form data is just <code>{ city: &#8220;Barcelona&#8221;, country: &#8220;Spain&#8221; }</code>. Everything else&#8212;the validation state, the API response, the loading indicators&#8212;that&#8217;s metadata. It&#8217;s the difference between what the user entered and what you need to know about what they entered.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Starting With Basic Validation</h2><p>Let&#8217;s look at the foundation. This is pure validation, no async behavior yet:</p><pre><code>const cityNameSchema = schema&lt;string&gt;((path) =&gt; {
  required(path, { message: &#8216;City is required&#8217; });
  minLength(path, 2, { message: &#8216;City must be at least 2 characters&#8217; });
  maxLength(path, 50, { message: &#8216;City name is too long&#8217; });
});

export const locationSchema = schema&lt;WeatherLocation&gt;((path) =&gt; {
  apply(path.city, cityNameSchema);
  apply(path.country, countryNameSchema);
});</code></pre><p>This checks structure and format. But it doesn&#8217;t verify if Barcelona is real or if someone made it up. For that, you need to talk to an API.</p><h2>Adding Real Validation</h2><p>Here&#8217;s where it gets interesting. We&#8217;re going to validate cities against a weather API as the user types:</p>
      <p>
          <a href="https://www.codigotipado.com/p/making-sense-of-metadata-in-angular">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 21.0.1: The Missing "Style Link" in Signal Forms 🔗]]></title><description><![CDATA[If you've been experimenting with the new Signal Forms in Angular 21, you might have noticed something missing. You set up your schemas, you wired up your signals, but the DOM is clean.]]></description><link>https://www.codigotipado.com/p/angular-211-the-missing-style-link</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-211-the-missing-style-link</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Tue, 02 Dec 2025 05:49:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!RVdM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RVdM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RVdM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RVdM!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:688922,&quot;alt&quot;:&quot;Angular 21.0.1 Signal Forms - Field Directive Classes with DI&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/180473602?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 21.0.1 Signal Forms - Field Directive Classes with DI" title="Angular 21.0.1 Signal Forms - Field Directive Classes with DI" srcset="https://substackcdn.com/image/fetch/$s_!RVdM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!RVdM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8168c31b-2a24-4bdd-a1af-c91990baf026_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you&#8217;ve been experimenting with the new <strong>Signal Forms</strong> in Angular 21, you might have noticed something missing. You set up your schemas, you wired up your signals, but when you looked at the DOM... it was clean.</p><p>Too clean.</p><p>In the old <strong>ReactiveForms</strong> world, Angular automatically slapped classes like <code>.ng-invalid</code>, <code>.ng-dirty</code>, and <code>.ng-touched</code> onto your inputs. CSS frameworks (like Bootstrap or Material) relied on these hooks to turn borders red or show error icons. With Signal Forms, that automatic behavior was gone&#8212;until now.</p><p>With the latest merge into core (<a href="https://github.com/angular/angular/commit/7d5c7cf99aa5c6490f8bea950b04bd56073582a1">Commit </a><code>7d5c7cf</code>), Angular 21.1 introduces <code>provideSignalFormsConfig</code>. This acts as a bridge between your pure logic signal state and your UI styling.</p><p>Let&#8217;s look at how this changes the game for our Weather Chatbot, specifically how we can use it to drive a <strong>&#8220;Tailwind-native&#8221;</strong> form experience without cluttering our templates.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The &#8220;Lazy&#8221; Way: Back to Basics</h2><p>If you just want the old behavior back because you have existing CSS that targets <code>.ng-invalid</code>, the Angular team provided a pre-baked constant.</p><p>In your <code>app.config.ts</code>:</p><pre><code><code>import { ApplicationConfig } from &#8216;@angular/core&#8217;;
import { provideSignalFormsConfig } from &#8216;@angular/forms/signals&#8217;;
import { NG_STATUS_CLASSES } from &#8216;@angular/forms/signals/compat&#8217;;

export const appConfig: ApplicationConfig = {
  providers: [
    // &#129668; Magic: Automatically adds ng-valid, ng-invalid, ng-dirty, etc.
    provideSignalFormsConfig({
      classes: NG_STATUS_CLASSES
    })
  ]
};</code></code></pre><p>But we are writing modern Angular. We can do better than legacy compatibility.</p><h2>The &#8220;Pro&#8221; Way: Semantic Configuration</h2><p>This new API allows us to map <strong>any CSS class</strong> to a <strong>predicate function</strong> that reads the field state. This is incredibly powerful.</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-211-the-missing-style-link">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 21: What's new]]></title><description><![CDATA[The most significant shift is the zoneless-by-default approach. The future is reactive, performant, and decidedly signal-driven&#8212;and Angular is leading the way.]]></description><link>https://www.codigotipado.com/p/angular-21-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-21-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Thu, 20 Nov 2025 20:15:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!s4ZX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s4ZX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s4ZX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s4ZX!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2116056,&quot;alt&quot;:&quot;Angular 21: What's new&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/171535173?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Angular 21: What's new" title="Angular 21: What's new" srcset="https://substackcdn.com/image/fetch/$s_!s4ZX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!s4ZX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde77d65f-d362-4dec-883e-6d48be2c76b7_1600x900.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>1. @angular/animations</strong></h2><h2><strong>2. @angular/common</strong></h2><h3>a) Zero-Configuration HTTP Services - <a href="https://github.com/angular/angular/pull/56212">PR</a></h3><p>HTTP services are now automatically provided in the <strong>root injector</strong>, eliminating the need for explicit <code>provideHttpClient()</code>.</p><pre><code>import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';

// Before
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient() // &#10060; Required for basic HTTP functionality
  ]
});

// After
bootstrapApplication(AppComponent, {
  providers: [
    // &#9989; HTTP services automatically available
  ]
});</code></pre><p><strong>Advanced Configuration:</strong> For custom interceptors, client configuration, or specific features, continue using <code>provideHttpClient()</code>:</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/p/angular-21-whats-new?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading Angular: From Zero to Expert! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/p/angular-21-whats-new?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.codigotipado.com/p/angular-21-whats-new?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><pre><code>bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor]),
      withFetch() // Custom features require explicit provider
    )
  ]
});</code></pre><h3>b) <strong>&#9888;&#65039; </strong>NgComponentOutlet NgModuleFactory Removal - <a href="https://github.com/angular/angular/pull/62838">PR</a></h3><p>The <code>ngModuleFactory</code> input of <code>NgComponentOutlet</code> was a legacy feature that relied on the deprecated <code>NgModuleFactory</code> class, creating unnecessary complexity and maintenance overhead.</p><p>Now <code>ngModuleFactory</code> input from <code>NgComponentOutlet</code> is removed, using only the modern <code>NgModule</code> approach.</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-21-whats-new">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Mastering the New debounce() API in Angular 21 Signal Forms]]></title><description><![CDATA[In this comprehensive guide, we'll explore the debounce() API through a real-world weather chatbot application, demonstrating how this feature transforms form performance and user experience.]]></description><link>https://www.codigotipado.com/p/mastering-the-new-debounce-api-in</link><guid isPermaLink="false">https://www.codigotipado.com/p/mastering-the-new-debounce-api-in</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Wed, 19 Nov 2025 05:26:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d1H-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d1H-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d1H-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d1H-!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1216738,&quot;alt&quot;:&quot;Angular Signal Forms Debounce&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/179322193?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular Signal Forms Debounce" title="Angular Signal Forms Debounce" srcset="https://substackcdn.com/image/fetch/$s_!d1H-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!d1H-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe35a7332-7925-4fe6-88b2-e934f5ed0cfd_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>The Problem We&#8217;ve All Been Ignoring</h2><p>Here&#8217;s something embarrassing: I had a search field in one of my apps that was hitting our ElasticSearch cluster <strong>on every single keystroke</strong>. A user typing &#8220;San Francisco&#8221; would trigger 13 API calls. Multiply that by a few hundred concurrent users, and our AWS bill was... let&#8217;s just say &#8220;uncomfortable.&#8221;</p><p>The old solution? RxJS operators like <code>debounceTime()</code> scattered throughout components, manual subscription management, and crossing your fingers that you didn&#8217;t create a memory leak. It worked, but it felt like duct tape on a leaky pipe.</p><p>Signal Forms&#8217; new <code>debounce()</code> rule takes a completely different approach. Instead of managing debouncing at the component level, it&#8217;s built directly into the form&#8217;s schema definition. The form itself knows when to synchronize user input with your data model.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>
      <p>
          <a href="https://www.codigotipado.com/p/mastering-the-new-debounce-api-in">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Building Your Own MCP Server: Angular Best Practices Refactoring Tool - Part 2]]></title><description><![CDATA[In Part 1, we explored the theory behind Model Context Protocol, now it's time to get our hands dirty.]]></description><link>https://www.codigotipado.com/p/building-your-own-mcp-server-angular</link><guid isPermaLink="false">https://www.codigotipado.com/p/building-your-own-mcp-server-angular</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Sat, 01 Nov 2025 15:42:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!q8a7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q8a7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q8a7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!q8a7!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:640327,&quot;alt&quot;:&quot;Build you Own MCP Server: Part 2&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/176709818?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Build you Own MCP Server: Part 2" title="Build you Own MCP Server: Part 2" srcset="https://substackcdn.com/image/fetch/$s_!q8a7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!q8a7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc1ae624-c88d-47bd-903b-568d348f78ac_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But here&#8217;s where we&#8217;re going to do something different from typical tutorials. Instead of building a generic weather API or todo list MCP server, we&#8217;re creating something immediately valuable for Angular developers: <strong>an MCP server with a powerful refactoring tool that enforces modern Angular and TypeScript best practices</strong>.</p>
      <p>
          <a href="https://www.codigotipado.com/p/building-your-own-mcp-server-angular">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Understanding Model Context Protocol (MCP) - Part 1]]></title><description><![CDATA[From API Chaos to Universal Standards: Understanding the MCP Foundation That Powers Modern AI Integration.]]></description><link>https://www.codigotipado.com/p/understanding-model-context-protocol</link><guid isPermaLink="false">https://www.codigotipado.com/p/understanding-model-context-protocol</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Fri, 10 Oct 2025 05:25:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VzPC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VzPC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VzPC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 424w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 848w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 1272w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VzPC!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:947222,&quot;alt&quot;:&quot;Understanding Model Context Protocol (MCP) - Part 1&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174313574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Understanding Model Context Protocol (MCP) - Part 1" title="Understanding Model Context Protocol (MCP) - Part 1" srcset="https://substackcdn.com/image/fetch/$s_!VzPC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 424w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 848w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 1272w, https://substackcdn.com/image/fetch/$s_!VzPC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f395057-cd77-43e0-b7c1-c5af7acaff19_2080x1170.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Birth of a New Standard: How MCP Came to Be</h2><p>Large Language Models (LLMs) like ChatGPT, Claude, and Gemini can write code, debug complex problems, and explain technical concepts with remarkable fluency. Yet despite their brilliance, they&#8217;ve been operating in isolation&#8212;powerful minds trapped in a room with no doors or windows.</p><p>Picture this: you have an AI assistant that can write flawless TypeScript, suggest perfect Angular component architectures, and identify bugs in milliseconds. But it can&#8217;t see your actual project files, can&#8217;t access your database, can&#8217;t read your API documentation, and has no idea what tools you&#8217;re using. It&#8217;s like hiring a world-class architect who has to design your house without ever seeing the plot of land, checking local building codes, or knowing what materials are available.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This has been the fundamental limitation of AI development tools&#8212;until now.</p><h3>The Genesis: Anthropic&#8217;s Vision</h3><p>In November 2024, Anthropic&#8212;the AI safety company founded by former OpenAI researchers and creators of Claude&#8212;unveiled something that would change the landscape of AI integration: the <strong><a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol (MCP)</a></strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pW1G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pW1G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 424w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 848w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 1272w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pW1G!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png" width="1200" height="174.8878923766816" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:195,&quot;width&quot;:1338,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:14042,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174313574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pW1G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 424w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 848w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 1272w, https://substackcdn.com/image/fetch/$s_!pW1G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F051a9328-2cab-4322-a07e-4d3695d61c86_1338x195.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>The team at Anthropic had been grappling with a persistent problem. As they worked on making Claude more useful for real-world tasks, they kept running into the same wall: every time they wanted to connect Claude to a new tool, service, or data source, they had to build a custom integration from scratch. Database access required one approach. File system operations needed another. API integrations each demanded unique implementations.</p><p>It was the API chaos problem we explored earlier, but at a massive scale.</p><h3>The Open Standard Revolution</h3><p>Rather than building yet another proprietary solution, Anthropic made a bold decision: they would create an open, universal protocol that any AI system could use to connect with any tool or service. On November 25, 2024, they released MCP as an open-source specification, complete with SDKs in Python and TypeScript.</p><p>The core idea was elegant: instead of building point-to-point integrations between every AI model and every service, create a standardized &#8220;language&#8221; that both sides could speak. Think of it as establishing USB-C for AI connectivity&#8212;one protocol to rule them all.</p><p>What made MCP truly revolutionary wasn&#8217;t just the technical specification. It was the philosophy behind it:</p><ul><li><p><strong>Open and Free</strong>: Anyone could implement it, no licensing fees, no vendor lock-in</p></li><li><p><strong>Bidirectional</strong>: Both AI systems and services could initiate communication</p></li><li><p><strong>Secure by Design</strong>: Built-in authorization and sandboxing capabilities</p></li><li><p><strong>Simple Yet Powerful</strong>: Easy enough for individual developers to implement, robust enough for enterprise systems</p></li></ul><h3>The Unprecedented Adoption</h3><p>Here&#8217;s where the story gets really interesting. In the tech world, standards battles typically drag on for years. Companies fight over competing specifications, each pushing their own solution. But something different happened with MCP.</p><p>By <strong>March 2025</strong>, just four months after its introduction, OpenAI&#8212;Anthropic&#8217;s biggest competitor&#8212;announced they were adopting MCP for GPT integrations. Let that sink in: a company competing directly with Anthropic chose to embrace their open standard rather than build their own.</p><p><strong>April 2025</strong> brought another bombshell: Google DeepMind integrated MCP support into their AI offerings. Microsoft followed in <strong>May 2025</strong>, incorporating MCP into Azure AI services and announcing plans to support it across their development tools ecosystem.</p><p>When fierce competitors this quickly agree on a standard, you&#8217;re witnessing something rare in tech history. It&#8217;s reminiscent of how HTTP became the universal protocol for the web, or how REST APIs became the de facto standard for web services.</p><h3>From Concept to Ecosystem</h3><p>Today, less than a year after its introduction, MCP has evolved from a novel idea into a thriving ecosystem. Development tools like Cursor, Windsurf, and Zed have built-in MCP support. Major SaaS companies are releasing official MCP servers. The community has created hundreds of integrations.</p><p>What started as Anthropic&#8217;s solution to their own integration challenges has become the foundation for how AI systems interact with the digital world.</p><p>And that&#8217;s exactly what we&#8217;re going to explore in this article: understanding the principles behind MCP and learning how to build our own MCP server using Angular, joining this revolution in AI integration.</p><h2>The Fundamental Problem: AI That Can Only "Talk"</h2><p>When you ask ChatGPT to "plan me a week-long holiday in Costa Rica," it can tell you exactly what to do, suggest amazing destinations, even help you create detailed itineraries&#8212;but it cannot actually open a browser, navigate to travel sites, check real-time availability, or complete bookings. LLMs are fundamentally text generators, not action-takers. This is essentially the limitation that Large Language Models (LLMs) like GPT face today.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XEXQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XEXQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 424w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 848w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 1272w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XEXQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png" width="812" height="457" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:457,&quot;width&quot;:812,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98676,&quot;alt&quot;:&quot;LLM Limitations&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174313574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="LLM Limitations" title="LLM Limitations" srcset="https://substackcdn.com/image/fetch/$s_!XEXQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 424w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 848w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 1272w, https://substackcdn.com/image/fetch/$s_!XEXQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F814889f0-5d27-4d96-9eb1-eba152c52b5e_812x457.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Enter AI Agents: The Hands and Feet of AI</h2><p>This is where AI agents come into play. Think of an AI agent as an intelligent assistant that combines the reasoning power of an LLM with the ability actually to&nbsp;<em>do</em>&nbsp;things. It's like giving that brilliant person in the room a set of tools, internet access, and the ability to interact with the outside world.</p><p>An AI agent follows a simple but powerful pattern:</p><ol><li><p><strong>Receive a request</strong> from the user</p></li><li><p><strong>Think about it</strong> using an LLM ("What kind of holiday experience does the user want?")</p></li><li><p><strong>Take actions</strong> by calling external services and APIs</p></li><li><p><strong>Think again</strong> about the results ("Do these options match their preferences? Do I need more information?")</p></li><li><p><strong>Repeat</strong> until the task is complete</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B-Qj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B-Qj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 424w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 848w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 1272w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B-Qj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png" width="601" height="454" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cdc9854d-6098-4312-8fe6-5730675014ee_601x454.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:454,&quot;width&quot;:601,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32811,&quot;alt&quot;:&quot;AI Agent Task Cycle&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174313574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="AI Agent Task Cycle" title="AI Agent Task Cycle" srcset="https://substackcdn.com/image/fetch/$s_!B-Qj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 424w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 848w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 1272w, https://substackcdn.com/image/fetch/$s_!B-Qj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdc9854d-6098-4312-8fe6-5730675014ee_601x454.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>For example, when you say "plan me an adventure holiday in Costa Rica," an AI agent might:</p><ul><li><p>Ask the LLM to extract details (budget, interests, group size, dates)</p></li><li><p>Call tourism APIs to find available activities and accommodations</p></li><li><p>Check weather services for seasonal considerations</p></li><li><p>Ask the LLM to analyze options against your adventure preferences</p></li><li><p>Present a complete itinerary with bookable options</p></li></ul><h2>The API Chaos Problem</h2><p>Here's where things get complicated. Every travel service has its own API:</p><ul><li><p>Airbnb uses <code>/v2/listings/search</code></p></li><li><p>TripAdvisor uses <code>/experiences/search</code></p></li><li><p>National Parks Service uses <code>/parks/activities</code></p></li><li><p>Local tour operators each have unique endpoints</p></li></ul><pre><code>// The nightmare of integrating multiple travel APIs - each one is different!

class HolidayPlanningAgent {
  
  async findAccommodations(destination, checkin, checkout, guests) {
    let results = [];
    
    // Airbnb - uses Bearer token, POST request
    const airbnbResponse = await fetch(`https://api.airbnb.com/v2/listings/search`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${this.airbnbToken}` },
      body: JSON.stringify({
        location: destination,        // calls it 'location'
        check_in: checkin,           // underscore format
        adults: guests
      })
    });
    // Returns: { listings: [{ price_per_night, coordinates: {lat, lng} }] }
    
    // Booking.com - uses Basic auth, GET request  
    const bookingResponse = await fetch(`https://distribution-xml.booking.com/2.4/json/hotelAvailability`, {
      method: 'GET',
      headers: { 'Authorization': `Basic ${this.bookingAuth}` },
      params: {
        city: destination,           // calls it 'city'
        checkin_date: checkin,      // different naming
        room1: `A,A,${guests}`      // completely different format
      }
    });
    // Returns: { result: [{ min_total_price, latitude, longitude }] }

    // Hotels.com - uses API key, different date format
    const hotelsResponse = await fetch(`https://api.ean.com/ean-services/rs/hotel/v3/list`, {
      headers: { 'Authorization': `EAN APIKey=${this.hotelsKey}` },
      params: {
        destinationString: destination,    // yet another name
        arrivalDate: checkin.replace(/-/g, '/'),  // wants MM/DD/YYYY format
        RoomGroup: `(A,${guests})`        // parentheses format
      }
    });
    // Returns: { HotelListResponse: { HotelList: { HotelSummary: [{ lowRate }] } } }
    
    return results; // Each API returns completely different data structures
  }

  async findActivities(destination, type) {
    // TripAdvisor
    await fetch(`https://api.tripadvisor.com/api/partner/2.0/experiences/search`, {
      params: { location: destination, category: this.mapToTripAdvisorCategory(type) }
    });
    // Returns: { data: [{ experience_id, title, price_from }] }

    // GetYourGuide  
    await fetch(`https://www.getyourguide.com/api/v1/activities`, {
      method: 'POST',
      body: JSON.stringify({
        data: { type: 'activity-search', attributes: { destination } }
      })
    });
    // Returns JSON API format: { data: [{ id, attributes: { title, price: { amount } } }] }

    // National Parks
    await fetch(`https://developer.nps.gov/api/v1/activities/parks`, {
      params: { q: destination }  // calls it 'q' instead of 'destination'
    });
    // Returns: { data: [{ parks: [{ fullName, entranceFees: [{ cost }] }] }] }
  }

  // Every API needs different category mappings
  mapToTripAdvisorCategory(type) {
    return { 'adventure': 'outdoor-activities', 'culture': 'cultural-tours' }[type];
  }
}

/*
The Problem: 
- 6 different authentication methods (Bearer, Basic, API Key)
- 6 different request formats (POST vs GET, different JSON structures)
- 6 different parameter names for the same concept (location vs city vs destinationString vs q)
- 6 different response formats to parse and normalize
- 6 different error handling approaches
- Constant maintenance as each API evolves independently

Result: You spend more time on API integration than actual holiday planning logic!
*/</code></pre><p>Each returns data in different formats, requires different authentication, and has different parameter names. If you want your AI agent to work with hundreds of accommodation providers, activity companies, restaurants, and attractions, you'd need to write custom integration code for each one.</p><p>This is like having a universal remote control that needs a different button layout for every device&#8212;it's technically possible but practically unsustainable.</p><h2>Model Context Protocol: The Universal Translator</h2><p>Model Context Protocol (MCP) solves this problem by creating a standardized way for AI agents to communicate with external services. Instead of writing custom code for each travel API, you create an MCP server that acts as a translator between the standardized MCP language and the service's specific API.</p><p>Think of MCP as creating a universal interface. Instead of your AI agent needing to learn 1,000 different ways to say "find adventure activities," there's now one standard way that works with any MCP-compatible service.</p><h2>The Three Pillars of MCP</h2><p>Every MCP server exposes three types of capabilities:</p><h3>1. Tools - The Actions</h3><p>Tools are the things your AI agent can <em>do</em>. These are functions that perform actions or retrieve data:</p><ul><li><p><code>search_accommodations(location, checkin, checkout, guests)</code></p></li><li><p><code>find_activities(destination, activity_type, difficulty_level)</code></p></li><li><p><code>get_restaurant_recommendations(location, cuisine, budget)</code></p></li><li><p><code>check_weather_forecast(destination, dates)</code></p></li></ul><h3>2. Resources - The Context</h3><p>Resources are static information that helps the AI make better decisions:</p><ul><li><p>Destination guides and local customs</p></li><li><p>Activity difficulty ratings and requirements</p></li><li><p>Seasonal travel recommendations</p></li><li><p>Visa and vaccination requirements</p></li><li><p>Currency exchange rates and tipping guides</p></li></ul><h3>3. Prompts - The Wisdom</h3><p>Prompts are pre-written instructions that help the AI use the tools correctly:</p><ul><li><p>"When planning adventure trips, always check weather patterns and seasonal accessibility"</p></li><li><p>"For family holidays, prioritize activities suitable for all age groups mentioned"</p></li><li><p>"When suggesting accommodations, consider proximity to planned activities"</p></li></ul><h2>The Architecture: Client Meets Server</h2><p>MCP follows a simple client-server model:</p><p><strong>MCP Client</strong> (your AI agent or IDE):</p><ul><li><p>Discovers what an MCP server can do</p></li><li><p>Sends requests to perform actions</p></li><li><p>Receives responses and data</p></li></ul><p><strong>MCP Server</strong> (the service provider):</p><ul><li><p>Exposes tools, resources, and prompts</p></li><li><p>Handles the actual API calls to external services</p></li><li><p>Returns standardized responses</p></li></ul><p>The communication happens through JSON-RPC, a lightweight protocol that's like having a standardized conversation format:</p><div class="preformatted-block" data-component-name="PreformattedTextBlockToDOM"><label class="hide-text" contenteditable="false">Text within this block will maintain its original spacing when published</label><pre class="text">Client: "Can you search for adventure activities?"
Server: "Yes, I can do that. Give me destination, activity type, and difficulty level."
Client: "Find zip-lining activities in Costa Rica for intermediate level"
Server: "Here are 8 zip-lining tours matching your criteria..."</pre></div><h2>The Communication Protocol: JSON-RPC 2.0</h2><p>Before diving into the MCP specification itself, we need to understand how MCP clients and servers actually communicate with each other. MCP uses JSON-RPC 2.0 as its communication protocol&#8212;think of it as the "language" that clients and servers speak when exchanging information.</p><h3>What is JSON-RPC?</h3><p><a href="https://www.jsonrpc.org/specification">JSON-RPC</a> (<strong>JSON Remote Procedure Call</strong>) is a lightweight, stateless protocol that allows a client to call methods on a remote server and receive responses. It's essentially a standardized way to say "run this function with these parameters" across a network or between processes.</p><p>The "2.0" refers to the specification version, which defines exactly how requests and responses should be formatted. This standardization ensures that any JSON-RPC 2.0 client can communicate with any JSON-RPC 2.0 server, regardless of what programming language they're built with.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Z2HB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Z2HB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 424w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 848w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 1272w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Z2HB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png" width="824" height="498" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:498,&quot;width&quot;:824,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:75367,&quot;alt&quot;:&quot;JSON-RPC&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174313574?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="JSON-RPC" title="JSON-RPC" srcset="https://substackcdn.com/image/fetch/$s_!Z2HB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 424w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 848w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 1272w, https://substackcdn.com/image/fetch/$s_!Z2HB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78d72b8e-aef1-4976-a873-10dd4ccc3581_824x498.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h3>The Basic Structure</h3><p>Every <a href="https://en.wikipedia.org/wiki/JSON-RPC#Implementations">JSON-RPC request</a> must include four elements:</p><pre><code>// JSON-RPC 2.0 Request Structure
{
  "jsonrpc": "2.0",
  "method": "search_hotels",
  "params": {
    "destination": "Costa Rica",
    "checkin": "2024-12-15",
    "checkout": "2024-12-22",
    "guests": 2
  },
  "id": 1
}

// JSON-RPC 2.0 Response Structure (Success)
{
  "jsonrpc": "2.0",
  "result": {
    "hotels": [
      {
        "id": "hotel_123",
        "name": "Rainforest Lodge",
        "price_per_night": 150,
        "location": "Manuel Antonio",
        "rating": 4.5
      },
      {
        "id": "hotel_456", 
        "name": "Beach Resort",
        "price_per_night": 200,
        "location": "Guanacaste",
        "rating": 4.2
      }
    ]
  },
  "id": 1
}

// JSON-RPC 2.0 Response Structure (Error)
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": "Missing required parameter: destination"
  },
  "id": 1
}</code></pre><h3>Transport Mechanisms</h3><p>JSON-RPC 2.0 is transport-agnostic, meaning the protocol doesn't dictate how messages are delivered. MCP supports two primary transport methods:</p><p><strong>Standard I/O (stdio)</strong>: Perfect for local development and testing. The MCP server runs as a subprocess, and messages are exchanged through standard input/output pipes. This is lightweight, secure, and ideal for IDEs like Cursor or VS Code extensions.</p><p><strong>HTTP</strong>: Used when the MCP server is hosted remotely or needs to serve multiple clients simultaneously. Messages are sent as HTTP POST requests, making it easy to deploy MCP servers as web services.</p><h3>MCP-Specific Conventions</h3><p>While MCP uses standard JSON-RPC 2.0, it defines specific method names and parameter structures:</p><p><strong>Discovery Methods</strong>:</p><ul><li><p><code>tools/list</code> - Get available tools</p></li><li><p><code>resources/list</code> - Get available resources</p></li><li><p><code>prompts/list</code> - Get available prompts</p></li></ul><p><strong>Action Methods</strong>:</p><ul><li><p><code>tools/call</code> - Execute a specific tool</p></li><li><p><code>resources/read</code> - Retrieve resource content</p></li><li><p><code>prompts/get</code> - Get a specific prompt</p></li></ul><p><strong>Notification Methods</strong> (no response expected):</p><ul><li><p><code>notifications/initialized</code> - Client signals it's ready</p></li><li><p><code>notifications/progress</code> - Server reports progress updates</p></li></ul><h3>Error Handling</h3><p>JSON-RPC 2.0 defines standard error codes that MCP extends:</p><ul><li><p><code>-32700</code> Parse error (invalid JSON)</p></li><li><p><code>-32600</code> Invalid request (malformed JSON-RPC)</p></li><li><p><code>-32601</code> Method not found</p></li><li><p><code>-32602</code> Invalid params</p></li><li><p><code>-32603</code> Internal error</p></li><li><p><code>-32000 to -32099</code> Server-defined errors (MCP uses these for domain-specific issues)</p></li></ul><p>This standardized approach means client applications can handle errors consistently across different MCP servers.</p><p>The beauty of this protocol choice is that it abstracts away all the complexity we saw in our API chaos examples. Instead of learning different authentication schemes, parameter formats, and response structures for each service, developers work with one consistent JSON-RPC interface while MCP servers handle the underlying API complexity behind the scenes.</p><h2>The Ecosystem Effect</h2><p>The real power of MCP emerges when it becomes widespread. Imagine a world where:</p><ul><li><p>Every major travel platform provides an MCP server</p></li><li><p>Tourism boards offer MCP servers for their destinations</p></li><li><p>AI development tools automatically discover and connect to travel MCP servers</p></li><li><p>Building a holiday planning agent is as simple as configuring which destinations and services you want to include</p></li><li><p>Your agent can seamlessly work with any new tourism service that supports MCP</p></li></ul><p>We're moving toward a future where AI agents can plan and coordinate complex holiday experiences as easily as humans browse travel websites&#8212;and MCP is the standard that makes it possible.</p><h2>What's Next?</h2><p>Now that you understand the foundational concepts of MCP, you're ready to dive into the technical implementation. In the next article, we'll explore how to build your own MCP server, create custom tools, and integrate everything into a working AI agent system (basically into our Angular project).</p><p>The beauty of MCP is that once you understand these core concepts, the implementation becomes a matter of following the specification&#8212;and that's where the real fun begins.</p><h2>Thanks for reading so far &#128591;</h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don&#8217;t forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://x.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Mastering Angular 21 Signal Forms: A Deep Dive into the Experimental API]]></title><description><![CDATA[Angular 21 introduces one of the most significant improvements to form handling since the framework's inception: Signal-Based Forms (Experimental).]]></description><link>https://www.codigotipado.com/p/mastering-angular-21-signal-forms</link><guid isPermaLink="false">https://www.codigotipado.com/p/mastering-angular-21-signal-forms</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Thu, 02 Oct 2025 05:30:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xcmO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xcmO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png" data-component-name="Image2ToDOM"><div class="image2-inset image2-full-screen"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xcmO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 424w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 848w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xcmO!,w_5760,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;full&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2433764,&quot;alt&quot;:&quot;Angular 21 Signal Forms&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/174589083?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-fullscreen" alt="Angular 21 Signal Forms" title="Angular 21 Signal Forms" srcset="https://substackcdn.com/image/fetch/$s_!xcmO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 424w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 848w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 1272w, https://substackcdn.com/image/fetch/$s_!xcmO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cd1ab87-2da9-4833-9868-58155e85cb26_2560x1440.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>&#127381; <em>Last updated for Angular 22.0.0 &#8212; includes</em> <code>FormRoot</code> <em>directive, submission options, parse errors,</em> <code>transformedValue()</code><em>,</em> <code>SignalFormControl</code> <em>bridge,</em> <code>debounce('blur')</code><em>,</em> <code>reloadValidation()</code><em>,</em> <code>getError()</code><em>,</em> <code>ngNoCva</code><em>, FVC template/reactive interop, inline debounce for async validators, and lazy field instantiation.</em></p><h2>Introduction</h2><p>Traditional Angular forms, while powerfu&#8230;</p>
      <p>
          <a href="https://www.codigotipado.com/p/mastering-angular-21-signal-forms">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Angular 20.2.0: What's new]]></title><description><![CDATA[Angular 20.2.0 introduces cleaner templates, smarter tooling, and improved debugging as some of its new features.]]></description><link>https://www.codigotipado.com/p/angular-2020-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-2020-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Thu, 21 Aug 2025 04:34:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XfdL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XfdL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XfdL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XfdL!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:168124,&quot;alt&quot;:&quot;Angular 20.2&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/168611854?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 20.2" title="Angular 20.2" srcset="https://substackcdn.com/image/fetch/$s_!XfdL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!XfdL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e593aef-3c59-4d16-8677-3dd8f5795906_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>1. &#128293; @angular/core</strong></h2><h3>a) Support TypeScript 5.9</h3><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>b) Angular Components Now Use Real Tag Names in Tests - <a href="https://github.com/angular/angular/pull/62283">PR</a></h3><p>Until now, there was a subtle but important difference between how your components behaved in tests versus production:</p><p><strong>In Production:</strong> angular creates components using tag names inferred from their selectors:</p><pre><code><code>@Component({selector: 'user-profile', template: '...'})
// Creates actual &lt;user-profile&gt; element</code></code></pre><p><strong>In Tests (Previously):</strong> TestBed always wrapped components in generic <code>&lt;div&gt;</code> elements:</p><pre><code><code>const fixture = TestBed.createComponent(UserProfileComponent);
// Always created &lt;div&gt; regardless of selector</code></code></pre><p>This mismatch could hide CSS styling issues, accessibility problems, or any logic that depended on the actual element tag name.</p><p>Angular now offers the <code>inferTagName</code> option to align test behavior with production:</p><pre><code><code>// Option 1: Per-component basis
const fixture = TestBed.createComponent(UserProfileComponent, {
  inferTagName: true
});
// Now creates &lt;user-profile&gt; element in tests

// Option 2: Configure globally for all tests
TestBed.configureTestingModule({
  inferTagName: true,
  // ... other config
});

@Component({selector: 'my-button'}) 
// &#8594; Creates &lt;my-button&gt;

@Component({selector: 'custom-input[type="text"]'}) 
// &#8594; Creates &lt;custom-input&gt;

@Component({selector: '[data-widget]'}) 
// &#8594; Falls back to &lt;div&gt; (no tag name in selector)

@Component({template: '...'}) 
// &#8594; Creates &lt;ng-component&gt; (no selector)</code></code></pre><p></p><h3>c) Property-to-Attribute Mapping - <a href="https://github.com/angular/angular/pull/62630">PR</a></h3><p>Previously, developers faced a choice between verbose syntax and potential SSR problems when working with ARIA attributes:</p><pre><code><code>// Verbose but SSR-safe
&lt;button [attr.aria-label]="buttonText"&gt;

// Clean but potential SSR issues  
&lt;button [ariaLabel]="buttonText"&gt;</code></code></pre><p>The problem: ARIA DOM properties don't always correctly reflect as HTML attributes during server-side rendering, potentially breaking accessibility for users with assistive technologies.</p><p>Angular now supports clean property binding syntax that automatically renders as proper HTML attributes:</p><pre><code><code>// All of these now work identically and render correctly on SSR
&lt;button [aria-label]="buttonText"&gt;     // New simplified syntax
&lt;button [ariaLabel]="buttonText"&gt;      // Existing camelCase
&lt;button [attr.aria-label]="buttonText"&gt; // Explicit attribute binding</code></code></pre><p>The enhancement includes intelligent mapping for all standard ARIA properties:</p><pre><code><code>@Component({
  template: `
    &lt;div [ariaLabel]="label"
         [ariaExpanded]="isExpanded" 
         [ariaDisabled]="isDisabled"
         [aria-hidden]="isHidden"&gt;
      &lt;!-- All render as proper aria-* attributes --&gt;
    &lt;/div&gt;
  `
})</code></code></pre><p><strong>Automatic conversions include:</strong></p><ul><li><p><code>ariaLabel</code> &#8594; <code>aria-label</code></p></li><li><p><code>ariaExpanded</code> &#8594; <code>aria-expanded</code></p></li><li><p><code>ariaHasPopup</code> &#8594; <code>aria-haspopup</code></p></li><li><p>Plus 30+ other ARIA properties</p></li></ul><p>The system intelligently prioritizes component inputs over attribute binding:</p><pre><code><code>@Component({
  selector: 'custom-button'
})
class CustomButton {
  @Input() ariaLabel!: string; // This takes precedence
}

// Binds to component input, not HTML attribute
&lt;custom-button [ariaLabel]="text"&gt;</code></code></pre><p>This feature particularly shines in SSR scenarios where proper attribute rendering is crucial for accessibility:</p><pre><code><code>// Server renders: &lt;button aria-label="Save Document"&gt;
// Client hydrates seamlessly with identical markup
&lt;button [aria-label]="saveLabel"&gt;</code></code></pre><p></p><h3>d) Promote zoneless to stable</h3><p>As of Angular v20.2, Zoneless (<code>provideZonelessChangeDetection</code>) Angular is now stable and includes improvements in error handling and server-side rendering.</p><p></p><h3>e) Control Flow Enhancement: <code>as</code> Aliases in <code>@else if</code> Blocks - <a href="https://github.com/angular/angular/pull/63047">PR</a></h3><pre><code>&lt;!-- &#9989; Enhanced: as works in @else if blocks --&gt;
@if (user$ | async; as user) {
  &lt;h1&gt;Welcome, {{user.name}}&lt;/h1&gt;
  &lt;p&gt;Role: {{user.role}}&lt;/p&gt;
} @else if (userRole$ | async; as role) {
  &lt;!-- &#127881; Now we can use 'as' here too! --&gt;
  &lt;p&gt;Loading user data for {{role}}...&lt;/p&gt;
  &lt;p&gt;Please wait while we fetch your {{role}} profile...&lt;/p&gt;
} @else {
  &lt;p&gt;Please log in&lt;/p&gt;
}</code></pre><p></p><h2><strong>2. &#128293; @angular/common/http</strong></h2><h3>a) Add referrer &amp; integrity support for fetch requests in httpResource - <a href="https://github.com/angular/angular/pull/62461">PR</a></h3><p>Currently, Angular's <code>httpResource</code> does not expose the <code>referrer</code> and <code>integrity</code> options from the underlying Fetch API.</p><p>Exposing these options would provide developers with finer control over the request's <strong>referrer</strong> and <strong>subresource integrity validation</strong>, which are important for ensuring <strong>security</strong>, <strong>privacy</strong>, and <strong>trust</strong> in critical resource fetching scenarios.</p><pre><code>httpResource(() =&gt; ({
  url: '${CDN_DOMAIN}/assets/data.json',
  method: 'GET',
  referrer: 'no-referrer',
  integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GhEXAMPLEKEY='
}));</code></pre><p></p><h3>b) New <code>redirected</code> Property - <a href="https://github.com/angular/angular/pull/62675">PR</a></h3><p>Angular's HttpClient now provides complete visibility into HTTP redirects with a new <code>redirected</code> property that aligns with the native Fetch API.</p><pre><code>@Injectable({
  providedIn: 'root'
})
export class ApiService {
  constructor(private http: HttpClient) {}
  
  getUserProfile(userId: string) {
    return this.http.get(`/api/users/${userId}`, { 
      observe: 'response' 
    }).pipe(
      tap(response =&gt; {
        if (response.redirected) {
          console.log('User profile request was redirected');
          console.log('Original URL:', `/api/users/${userId}`);
          console.log('Final URL:', response.url);
          
          // Track redirect for analytics
          this.trackRedirect('user-profile', response.url);
        }
      }),
      map(response =&gt; response.body)
    );
  }
  
  private trackRedirect(endpoint: string, finalUrl: string | null) {
    // Send redirect data to analytics service
    analytics.track('http_redirect', {
      endpoint,
      finalUrl,
      timestamp: new Date().toISOString()
    });
  }
}</code></pre><p></p><h2><strong>3. &#128293; @angular/language-service</strong></h2><h3>a) Angular Language Service Now Detects Deprecated APIs in Templates - <a href="https://github.com/angular/angular/pull/62054">PR</a></h3><pre><code>export class LegacyComponent {
  /**
   * @deprecated Use newProp instead
   */
  @Input() oldProp: string;
}

&lt;!-- Your IDE will now show a suggestion diagnostic here --&gt;
&lt;legacy-component [oldProp]="someValue"&gt;&lt;/legacy-component&gt;
                   ~~~~~~~ 'oldProp' is deprecated. Use newProp instead</code></pre><p></p><h3>b) Auto-Import for Angular Attributes: No More Manual Directive Imports - <a href="https://github.com/angular/angular/pull/62797">PR</a></h3><p>When you use directive attributes in your templates, the IDE will now automatically suggest importing the directive for you.</p><pre><code>&lt;!-- Type this: --&gt;
&lt;div appHighlight="yellow"&gt;
     &#8593; 
&#128161; Quick Fix: Import HighlightDirective from '@app/highlight'

// Automatically added to your component:
import { HighlightDirective } from '@app/highlight';

@Component({
  imports: [HighlightDirective], // &#8592; Added automatically!
  template: `&lt;div appHighlight="yellow"&gt;Content&lt;/div&gt;`
})
export class MyComponent { }</code></pre><p></p><h2><strong>4. &#128293; @angular/service-worker</strong></h2><h3>a) Take Control of Service Worker Updates: Angular's New updateViaCache Configuration Option - <a href="https://github.com/angular/angular/pull/62721">PR</a></h3><p>The new <code>updateViaCache</code> option allows you to specify exactly when the browser should check its HTTP cache when updating service workers or any scripts imported via <code>importScripts()</code>. This translates to:</p><p><strong>Improved Performance Control</strong>: choose between <code>'imports'</code>, <code>'all'</code>, or <code>'none'</code> to optimize your update strategy based on your application's specific needs.</p><p><strong>Better Development Experience</strong>: gain more predictable behavior during development cycles, especially when testing service worker updates.</p><p><strong>Production Optimization</strong>: fine-tune caching strategies for production deployments where update timing is critical.</p><pre><code>export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      updateViaCache: 'imports', // New cache control option
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};</code></pre><p></p><h3>b) New proactive storage monitoring system that prevents cache failures before they happen - <a href="https://github.com/angular/angular/pull/62737">PR</a></h3><p>The system monitors storage usage and alerts when capacity reaches 95% of the available quota:</p><pre><code>private async detectStorageFull() {
  try {
    const estimate = await navigator.storage.estimate();
    const { quota, usage } = estimate;
    
    // Handle cases where quota or usage might be undefined
    if (typeof quota !== 'number' || typeof usage !== 'number') {
      return;
    }
    
    // Consider storage "full" if usage is &gt;= 95% of quota
    // This provides a safety buffer before actual storage exhaustion
    const usagePercentage = (usage / quota) * 100;
    const isStorageFull = usagePercentage &gt;= 95;
    
    if (isStorageFull) {
      this.debugHandler.log(
        'Storage is full or nearly full',
        `DataGroup(${this.config.name}@${this.config.version}).detectStorageFull()`,
      );
    }
  } catch {
    // Error estimating storage, possibly by unsupported browser.
  }
}</code></pre><h4>For PWA Applications</h4><pre><code><code>// Example of how this helps PWAs maintain offline functionality
if (storageNearFull) {
  // Implement cleanup strategies
  // Prioritize critical resources
  // Notify user about storage constraints
}</code></code></pre><h4>For Content-Heavy Apps</h4><pre><code><code>// Applications with large caching needs benefit from early warnings
if (storageApproachingLimit) {
  // Implement cache eviction policies
  // Compress cached data
  // Switch to selective caching strategies
}</code></code></pre><p>Developers now get clear indicators when storage issues occur:</p><pre><code><code>Storage is full or nearly full -DataGroup(api@v1.2.3).detectStorageFull()</code></code></pre><p></p><h3>c) Real-Time Version Failure Notifications - <a href="https://github.com/angular/angular/commit/6d011687ec1fa2b8f0211379bb98adc8e02f4e9a">PR</a></h3><p>Previously, when a Service Worker version encountered critical failures, applications would experience degraded functionality without clear visibility into the root cause. The system introduces a new <code>VersionFailedEvent</code> that provides comprehensive failure information:</p><pre><code>/**
 * An event emitted when a specific version of the app has encountered a critical failure
 * that prevents it from functioning correctly.
 */
export interface VersionFailedEvent {
  type: 'VERSION_FAILED';
  version: {hash: string; appData?: object};
  error: string;
}</code></pre><h4>Automatic Client Notification</h4><p>When a version fails, the Service Worker automatically notifies all affected clients. Applications can listen for version failures using the existing <code>SwUpdate</code> service:</p><pre><code>@Component({
  selector: 'app-root',
  template: `
    &lt;div class="app"&gt;
      @if (versionError()) {
        &lt;div class="error-banner"&gt;
          &lt;h3&gt;Application Update Issue&lt;/h3&gt;
          &lt;p&gt;{{versionError()}}&lt;/p&gt;
          &lt;button (click)="handleVersionFailure()"&gt;
            Refresh Application
          &lt;/button&gt;
        &lt;/div&gt;
      }
      
      &lt;router-outlet /&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .error-banner {
      background: #fee;
      border: 1px solid #fcc;
      border-radius: 4px;
      padding: 16px;
      margin: 8px;
      color: #c66;
    }
    
    .error-banner button {
      background: #c66;
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 4px;
      cursor: pointer;
      margin-top: 8px;
    }
  `]
})
export class AppComponent implements OnInit {
  versionError = signal&lt;string | null&gt;(null);
  
  constructor(private swUpdate: SwUpdate) {}
  
  ngOnInit() {
    // Listen for all version events
    this.swUpdate.versionUpdates.subscribe(event =&gt; {
      switch (event.type) {
        case 'VERSION_FAILED':
          this.handleVersionFailure(event);
          break;
        case 'VERSION_READY':
          this.handleVersionReady(event);
          break;
        case 'VERSION_DETECTED':
          this.handleVersionDetected(event);
          break;
      }
    });
  }
  
  private handleVersionFailure(event: VersionFailedEvent) {
    console.error('Service Worker version failed:', event);
    
    // Set user-friendly error message
    this.versionError.set(
      `Application version ${event.version.hash.substring(0, 8)} has encountered an error. ` +
      `Please refresh to restore full functionality.`
    );
    
    // Optional: Report to error monitoring service
    this.reportVersionFailure(event);
  }
  
  private handleVersionReady(event: VersionReadyEvent) {
    // Clear any previous errors when new version is ready
    this.versionError.set(null);
    console.log('New version ready:', event.latestVersion.hash);
  }
  
  private handleVersionDetected(event: VersionDetectedEvent) {
    console.log('New version detected:', event.latestVersion.hash);
  }
  
  handleVersionFailure() {
    // Force page reload to get latest version
    window.location.reload();
  }
  
  private reportVersionFailure(event: VersionFailedEvent) {
    // Example: Send to monitoring service
    // errorService.report({
    //   type: 'service-worker-version-failure',
    //   version: event.version.hash,
    //   error: event.error,
    //   userAgent: navigator.userAgent,
    //   timestamp: new Date().toISOString()
    // });
  }
}</code></pre><h4>Testing the Feature</h4><p>The new functionality includes comprehensive test coverage:</p><pre><code><code>it('processes version failed events with cache corruption error', (done) =&gt; {
  update.versionUpdates.subscribe((event) =&gt; {
    expect(event.type).toEqual('VERSION_FAILED');
    expect((event as VersionFailedEvent).version).toEqual({
      hash: 'B',
      appData: {name: 'test-app'},
    });
    expect((event as VersionFailedEvent).error).toContain('Cache corruption detected');
    done();
  });
  
  mock.sendMessage({
    type: 'VERSION_FAILED',
    version: {
      hash: 'B',
      appData: {name: 'test-app'},
    },
    error: 'Cache corruption detected during resource fetch',
  });
});</code></code></pre><p></p><h3>d) Better Service Worker Debugging: Angular Now Catches Message Errors - <a href="https://github.com/angular/angular/pull/62834">PR</a></h3><p>When Service Workers receive corrupted or badly formatted messages, they would previously fail silently. Instead of silent failures, you now get clear logs when messages fail:</p><pre><code>[SW] Message error occurred - data could not be deserialized
[SW] Driver.onMessageError(origin: https://myapp.com)</code></pre><p></p><h3>e) Modern Service Workers: Angular Adds ES Module Support - <a href="https://github.com/angular/angular/pull/62831">PR</a></h3><p>Angular Service Workers now support ES modules with a new <code>type</code> configuration option, bringing modern JavaScript features like <code>import</code> and <code>export</code> to your Service Worker scripts.</p><pre><code>// Enable ES modules in Service Workers
export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      type: 'module', // Enable ES module features
      scope: '/app',
      updateViaCache: 'imports'
    }),
  ],
};</code></pre><p></p><h3>f) Service Worker Error Handling: unhandled promise rejections - <a href="https://github.com/angular/angular/pull/63059">PR</a></h3><p>Unhandled promise rejections happen when a Promise fails but there's no <code>.catch()</code> block or error handling to deal with it:</p><pre><code>// Before: Silent failure scenario
class DataSyncService {
  syncUserPreferences() {
    // This could fail silently if the API is down
    fetch('/api/sync-preferences', {
      method: 'POST',
      body: JSON.stringify(this.preferences)
    })
    .then(response =&gt; response.json())
    .then(result =&gt; {
      // Update local cache
      this.updateLocalCache(result);
    });
    // No .catch() - failures would be silent!
  }
}

// After: With the new logging, you'd see in DevTools:
// "Unhandled promise rejection occurred: NetworkError: Failed to fetch"</code></pre><p></p><h2><strong>5. &#128293; @angular/compiler-cli</strong></h2><h3>a) Smart Template Diagnostics: Catch Function Reference Mistakes Before Runtime - <a href="https://github.com/angular/angular/pull/59191">PR</a></h3><p>How many times have you written something like this and wondered why it displays <code>[Function]</code> instead of the actual value?</p><pre><code>@Component({
  template: `&lt;p&gt;Welcome {{ getUserName }}&lt;/p&gt;` // Missing parentheses!
})
class WelcomeComponent {
  getUserName(): string {
    return 'Sarah';
  }
}</code></pre><h4>The Solution: NG8117 Diagnostic</h4><p>Angular now automatically detects this pattern and shows a clear warning:</p><p><strong>&#10060; Before:</strong> Silent runtime behavior displaying <code>[Function]</code><br><strong>&#9989; Now:</strong> Compile-time warning with diagnostic NG8117</p><p></p><h2><strong>6. &#128293; @angular/animations</strong></h2><p>Say goodbye to @angular/animations (60KB bundle impact) and hello to native CSS animations with <code>animate.enter</code> and <code>animate.leave</code> in Angular 20.2!</p><p><a href="https://www.codigotipado.com/p/say-hello-to-native-css-animations">Read more here</a>.</p><p></p><h2><strong>7. &#128293; @angular/forms</strong></h2><h3>a) FormArray Gets Efficient Multi-Control Push Support - <a href="https://github.com/angular/angular/pull/57102">PR</a></h3><p>Previously, adding multiple controls to a FormArray required individual <code>push()</code> calls, each triggering change detection and validation events:</p><pre><code>// Old approach - inefficient for large datasets
const formArray = new FormArray([]);
const newControls = [
  new FormControl('value1'),
  new FormControl('value2'),
  new FormControl('value3'),
  // ... potentially hundreds more
];

// Each push triggers valueChanges, statusChanges, and validation
newControls.forEach(control =&gt; {
  formArray.push(control); // Triggers events every time!
});</code></pre><p>The <code>push()</code> method now supports both individual and batch operations:</p><pre><code>// Before: Only single controls
push(control: TControl, options?: { emitEvent?: boolean }): void

// After: Single controls OR arrays of controls  
push(control: TControl | Array&lt;TControl&gt;, options?: { emitEvent?: boolean }): void</code></pre><p></p><h2><strong>8. &#128293; @angular/router</strong></h2><h3>a) Router Goes Reactive: New <code>currentNavigation</code> Signal Replaces Deprecated Method - <a href="https://github.com/angular/angular/pull/62971">PR</a></h3><pre><code>// Old approach - complex and inefficient
export class App {
  private router = inject(Router);
  
  // Required complex Observable setup
  isNavigating = toSignal(this.router.events.pipe(
    map(() =&gt; !!this.router.getCurrentNavigation()) // Deprecated method
  ));
  
  // Manual state checking
  checkNavigationState() {
    const nav = this.router.getCurrentNavigation(); // Deprecated
    return nav ? 'Navigating...' : 'Idle';
  }
}

// New approach - simple and reactive
export class App {
  private router = inject(Router);
  
  // Clean, reactive navigation state
  isNavigating = computed(() =&gt; !!this.router.currentNavigation());
  
  // Derive any navigation state reactively
  navigationState = computed(() =&gt; {
    const nav = this.router.currentNavigation();
    return nav ? 'Navigating...' : 'Idle';
  });
}</code></pre><p></p><h2><strong>9. &#128293; @angular/platform-browser</strong></h2><h3>a) Warns About Hydration and Blocking Navigation Conflicts - <a href="https://github.com/angular/angular/pull/62963">PR</a></h3><p>When building Angular applications with server-side rendering (SSR), developers sometimes unknowingly combine features that don't work well together:</p><pre><code>// This configuration causes subtle runtime issues
bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(), // Enable hydration
    provideRouter(routes, 
      withEnabledBlockingInitialNavigation() // Enable blocking navigation
    )
  ]
});

// Console output:
// &#9888;&#65039; Configuration error: found both hydration and enabledBlocking initial navigation 
//    in the same application, which is a contradiction.</code></pre><p></p><h3>b) Angular Introduces <code>IsolatedShadowDom</code> Encapsulation - <a href="https://github.com/angular/angular/pull/62723">PR</a></h3><p><strong>&#9888;&#65039; Note: This feature was not included in 20.2, but it may be included in the next 21.0 release.</strong></p><p>Ever tried building a reusable component only to have it break when used in different projects?:</p><pre><code>// Your beautiful blue button component
@Component({
  template: '&lt;button class="btn"&gt;Click me&lt;/button&gt;',
  styles: ['.btn { background: blue; color: white; }'],
  encapsulation: ViewEncapsulation.ShadowDom
})

/* Their global styles */
.btn { background: red !important; }</code></pre><p>Your blue button becomes red! &#128561; Shadow DOM was supposed to prevent this, but Angular's implementation had a leak. The solution:</p><pre><code>// Now with TRUE isolation
@Component({
  template: '&lt;button class="btn"&gt;Click me&lt;/button&gt;',
  styles: ['.btn { background: blue; color: white; }'],
  encapsulation: ViewEncapsulation.IsolatedShadowDom // &#127881;
})

// Result: Your button stays blue EVERYWHERE! &#128153;</code></pre><h4>When to Choose Each Mode</h4><h5>Use <code>ViewEncapsulation.ShadowDom</code> when:</h5><ul><li><p>You need backward compatibility with existing applications</p></li><li><p>You want some global styles to be available in your component</p></li><li><p>You're gradually migrating to Shadow DOM</p></li></ul><h5>Use <code>ViewEncapsulation.IsolatedShadowDom</code> when:</h5><ul><li><p>Building reusable component libraries</p></li><li><p>Creating embeddable widgets for third-party sites</p></li><li><p>Need guaranteed style isolation</p></li><li><p>Following web standards precisely</p></li><li><p>Building micro-frontends that must be completely isolated</p></li></ul><p></p><h2><strong>10. &#128293; @angular/cli</strong></h2><p>a) New <a href="https://github.com/angular/angular-cli/releases/tag/20.2.0-rc.0">Angular MCP features</a></p><p>Angular CLI just added powerful AI integration capabilities through MCP (Model Context Protocol) server functionality.</p><p><a href="https://www.codigotipado.com/p/angular-cli-202-meets-ai-the-complete">Read more here</a>.</p><p></p><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://x.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p>]]></content:encoded></item><item><title><![CDATA[Angular CLI MCP Server: The Complete Guide to All Available Tools]]></title><description><![CDATA[How Angular just revolutionized AI-assisted development with Model Context Protocol.]]></description><link>https://www.codigotipado.com/p/angular-cli-202-meets-ai-the-complete</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-cli-202-meets-ai-the-complete</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 18 Aug 2025 05:46:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nvvt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nvvt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nvvt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nvvt!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:1953521,&quot;alt&quot;:&quot;Angular 20.2: New MCP Features&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/171242313?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 20.2: New MCP Features" title="Angular 20.2: New MCP Features" srcset="https://substackcdn.com/image/fetch/$s_!nvvt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!nvvt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c311789-2b2d-4c5f-a11f-9fb6c9853621_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Angular CLI 20.2 quietly introduced something that could fundamentally change how we develop Angular applications: Model Context Protocol (MCP) server functionality. If you&#8217;re wondering what tools are available and how to use them, you&#8217;re in the right place.</p><p>If you want to learn more about the beginning of the MCP servers and how Angular integrated its own, you can do it <a href="https://www.codigotipado.com/p/angular-cli-2010-now-includes-ai">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Prerequisites &amp; Core Concept</h2><p>Before diving into the tools, it is essential to understand the roles of the three main components and what you need to get started:</p><h3>Essential Prerequisites</h3><ul><li><p><strong>Node.js &amp; npm</strong>: The Angular CLI MCP server is a Node.js process, so ensure you have a recent version of Node.js (v18 or newer is typically recommended).</p></li><li><p><strong>Angular CLI</strong>: The server functionality is built into the latest versions of the Angular CLI (@angular/cli).</p></li><li><p><strong>A Compatible AI Client</strong>: You must be using an IDE or editor with an AI agent that supports the Model Context Protocol (MCP), such as VS Code (with GitHub Copilot/Agent mode) or Cursor.</p></li></ul><p>Configure your MCP following the <a href="https://angular.dev/ai/mcp">official Angular MCP guide</a>.</p><h3>Important: Two Ways to Configure</h3><p>Throughout this guide, you&#8217;ll see <strong>two ways</strong> to enable tools and modes:</p><ol><li><p><strong>Command Line</strong> - For testing or one-time use:</p></li></ol><pre><code><code>ng mcp --read-only -E modernize</code></code></pre><ol start="2"><li><p><strong>Configuration File</strong> - For persistent setup in your IDE:</p></li></ol><pre><code><code>{
   &#8220;servers&#8221;: {
     &#8220;angular-cli&#8221;: {
       &#8220;command&#8221;: &#8220;npx&#8221;,
       &#8220;args&#8221;: [&#8221;-y&#8221;, &#8220;@angular/cli&#8221;, &#8220;mcp&#8221;, &#8220;--read-only&#8221;, &#8220;-E&#8221;, &#8220;modernize&#8221;]
     }
   }
}</code></code></pre><p><strong>Most users will use the configuration file approach</strong> as it persists across IDE sessions.</p>
      <p>
          <a href="https://www.codigotipado.com/p/angular-cli-202-meets-ai-the-complete">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Say Hello to Native CSS Animations and Goodbye to @angular/animations]]></title><description><![CDATA[The framework is deprecating the @angular/animations package in favor of a new approach: native CSS animations integration through animate.enter and animate.leave.]]></description><link>https://www.codigotipado.com/p/say-hello-to-native-css-animations</link><guid isPermaLink="false">https://www.codigotipado.com/p/say-hello-to-native-css-animations</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 04 Aug 2025 16:55:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Ulkf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ulkf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ulkf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ulkf!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:238823,&quot;alt&quot;:&quot;Angular 20.2.0: Native CSS Animations&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/170098799?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 20.2.0: Native CSS Animations" title="Angular 20.2.0: Native CSS Animations" srcset="https://substackcdn.com/image/fetch/$s_!Ulkf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!Ulkf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F660821fa-0af8-4eb3-b42d-803eaff9bbaa_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Why @angular/animations Is Being Retired</h2><p>After eight years of service, @angular/animations is showing its age. Created before modern CSS features like <code>@keyframes</code> and hardware-accelerated transforms were widely supported, the package solved important problems, but now creates new ones:</p><p><strong>Size Impact</strong>: the package adds approximately 60KB to your bundle size<br><strong>Performance Issues</strong>: animations run in JavaScript without hardware acceleration<br><strong>Learning Curve</strong>: Angular-specific API that doesn't translate to other frameworks<br><strong>Integration Friction</strong>: difficlt to use with popular third-party animation libraries like GSAP or Anime.js</p><p>Meanwhile, the web platform has evolved dramatically, offering native animation capabilities that are faster, smaller, and more widely applicable.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Meet Your New Animation System: animate.enter and animate.leave</h2><p>Starting in Angular 20.2.0, two powerful new features provide everything you need for smooth, performant animations:</p><h3>Basic Usage: CSS Class Application</h3><p>The simplest approach applies CSS animation classes automatically:</p><pre><code>@Component({
  template: `
    @if (showMessage()) {
      &lt;div animate.enter="slide-in" animate.leave="fade-out"&gt;
        Welcome to the future of Angular animations!
      &lt;/div&gt;
    }
    &lt;button (click)="toggle()"&gt;Toggle Message&lt;/button&gt;
  `,
  styles: [`
    .slide-in {
      animation: slideIn 0.3s ease-out;
    }
    
    .fade-out {
      animation: fadeOut 0.2s ease-in;
    }
    
    @keyframes slideIn {
      from { transform: translateY(-20px); opacity: 0; }
      to { transform: translateY(0); opacity: 1; }
    }
    
    @keyframes fadeOut {
      from { opacity: 1; }
      to { opacity: 0; }
    }
  `]
})
export class MessageComponent {
  showMessage = signal(false);
  
  toggle(): void {
    this.showMessage.update((v: boolean) =&gt; !v);
  }
}</code></pre><h3>Advanced Control with Animation Functions</h3><p>For complex animations or third-party library integration, use function-based control:</p><pre><code>@Component({
  template: `
    &lt;div class="animation-playground"&gt;
      @if (showElement()) {
        &lt;div class="animated-box"
             (animate.enter)="handleEnterAnimation($event)"
             (animate.leave)="handleLeaveAnimation($event)"&gt;
          &lt;h3&gt;Advanced Animation&lt;/h3&gt;
          &lt;p&gt;Powered by GSAP integration&lt;/p&gt;
        &lt;/div&gt;
      }
      
      &lt;button (click)="toggleElement()"&gt;
        {{showElement() ? 'Hide' : 'Show'}} Element
      &lt;/button&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .animated-box {
      width: 300px;
      height: 200px;
      background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
      border-radius: 12px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      color: white;
      margin: 20px 0;
    }
  `]
})
export class AdvancedAnimationComponent {
  showElement = signal(false);

  handleEnterAnimation(event: AnimationCallbackEvent): void {
    // Using GSAP for complex enter animation
    gsap.fromTo(event.target, 
      {
        scale: 0,
        rotation: -180,
        opacity: 0
      },
      {
        scale: 1,
        rotation: 0,
        opacity: 1,
        duration: 0.8,
        ease: "back.out(1.7)",
        onComplete: () =&gt; {
          // Animation complete callback is automatic for enter animations
          console.log('Enter animation completed!');
        }
      }
    );
  }

  handleLeaveAnimation(event: AnimationCallbackEvent): void {
    // Complex leave animation with staggered effects
    const timeline = gsap.timeline({
      onComplete: () =&gt; {
        // Must call this to complete the removal process
        event.animationComplete();
      }
    });

    timeline
      .to(event.target, {
        scale: 1.1,
        duration: 0.1,
        ease: "power2.out"
      })
      .to(event.target, {
        scale: 0,
        rotation: 180,
        opacity: 0,
        duration: 0.5,
        ease: "power2.in"
      });
  }

  toggleElement(): void {
    this.showElement.update((v: boolean) =&gt; !v);
  }
}</code></pre><h3>Host Element Animations</h3><p>Apply animations directly to component host elements:</p><pre><code>@Component({
  selector: 'notification-card',
  template: `
    &lt;div class="notification-content"&gt;
      &lt;h4&gt;{{title()}}&lt;/h4&gt;
      &lt;p&gt;{{message()}}&lt;/p&gt;
      &lt;button (click)="dismiss()"&gt;&#215;&lt;/button&gt;
    &lt;/div&gt;
  `,
  host: {
    '[animate.enter]': 'enterAnimation',
    '[animate.leave]': 'leaveAnimation',
    'class': 'notification-card'
  },
  styles: [`
    :host {
      display: block;
      background: white;
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.15);
      padding: 16px;
      margin: 8px 0;
      max-width: 400px;
    }
    
    .slide-in-right {
      animation: slideInRight 0.3s ease-out;
    }
    
    .slide-out-right {
      animation: slideOutRight 0.3s ease-in;
    }
    
    @keyframes slideInRight {
      from { transform: translateX(100%); opacity: 0; }
      to { transform: translateX(0); opacity: 1; }
    }
    
    @keyframes slideOutRight {
      from { transform: translateX(0); opacity: 1; }
      to { transform: translateX(100%); opacity: 0; }
    }
  `]
})
export class NotificationCardComponent {
  title = input.required&lt;string&gt;();
  message = input.required&lt;string&gt;();
  type = input&lt;'success' | 'warning' | 'error'&gt;('success');
  autoClose = input&lt;boolean&gt;(true);
  
  dismissed = output&lt;void&gt;();

  enterAnimation = 'slide-in-right';
  leaveAnimation = 'slide-out-right';

  // Auto-close functionality using signals
  private autoCloseTimer = signal&lt;ReturnType&lt;typeof setTimeout&gt; | null&gt;(null);

  constructor() {
    // Set up auto-close when enabled
    effect(() =&gt; {
      if (this.autoClose()) {
        const timer = setTimeout(() =&gt; this.dismiss(), 5000);
        this.autoCloseTimer.set(timer);
      }
    });
  }

  dismiss() {
    const timer = this.autoCloseTimer();
    if (timer) {
      clearTimeout(timer);
      this.autoCloseTimer.set(null);
    }
    this.dismissed.emit();
  }
}

// Usage in parent component with signal inputs
@Component({
  template: `
    &lt;div class="notifications"&gt;
      @for (notification of notifications(); track notification.id) {
        &lt;notification-card 
          [title]="notification.title"
          [message]="notification.message"
          [type]="notification.type"
          [autoClose]="notification.autoClose"
          (dismissed)="removeNotification(notification.id)" /&gt;
      }
    &lt;/div&gt;
    
    &lt;div class="controls"&gt;
      &lt;button (click)="addSuccessNotification()"&gt;Add Success&lt;/button&gt;
      &lt;button (click)="addWarningNotification()"&gt;Add Warning&lt;/button&gt;
      &lt;button (click)="addErrorNotification()"&gt;Add Error&lt;/button&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .notifications {
      position: fixed;
      top: 20px;
      right: 20px;
      z-index: 1000;
    }
    
    .controls {
      margin: 20px;
    }
    
    .controls button {
      margin-right: 10px;
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
  `]
})
export class NotificationContainerComponent {
  notifications = signal&lt;Array&lt;{
    id: number;
    title: string;
    message: string;
    type: 'success' | 'warning' | 'error';
    autoClose: boolean;
  }&gt;&gt;([]);
  
  private nextId = signal(1);

  addSuccessNotification() {
    this.addNotification({
      title: 'Success!',
      message: 'Operation completed successfully!',
      type: 'success',
      autoClose: true
    });
  }

  addWarningNotification() {
    this.addNotification({
      title: 'Warning',
      message: 'Please review your settings.',
      type: 'warning',
      autoClose: false
    });
  }

  addErrorNotification() {
    this.addNotification({
      title: 'Error',
      message: 'Something went wrong. Please try again.',
      type: 'error',
      autoClose: false
    });
  }

  private addNotification(notificationData: Omit&lt;any, 'id'&gt;) {
    const newNotification = {
      id: this.nextId(),
      ...notificationData
    };
    
    this.nextId.update(id =&gt; id + 1);
    this.notifications.update(notifications =&gt; 
      [...notifications, newNotification]
    );
  }

  removeNotification(id: number) {
    this.notifications.update(notifications =&gt; 
      notifications.filter(n =&gt; n.id !== id)
    );
  }
}</code></pre><p></p><h2>Testing Support</h2><p>Angular provides an <code>ANIMATIONS_DISABLED</code> token for test environments:</p><pre><code><code>TestBed.configureTestingModule({
  providers: [
    { provide: ANIMATIONS_DISABLED, useValue: true }
  ]
});</code></code></pre><p>When disabled, animations complete immediately while still triggering all lifecycle events.</p><p></p><h2>Migration Strategy</h2><p>For existing @angular/animations users, Angular provides a comprehensive migration guide. The most common patterns translate directly:</p><p><strong>Before (Angular Animations):</strong></p><pre><code><code>@Component({
  animations: [
    trigger('slideIn', [
      transition(':enter', [
        style({ transform: 'translateX(-100%)' }),
        animate('300ms ease-in', style({ transform: 'translateX(0%)' }))
      ])
    ])
  ]
})</code></code></pre><p><strong>After (Native Animations):</strong></p><pre><code><code>@Component({
  template: `&lt;div animate.enter="slide-in"&gt;`,
  styles: [`
    .slide-in {
      animation: slideIn 300ms ease-in;
    }
    @keyframes slideIn {
      from { transform: translateX(-100%); }
      to { transform: translateX(0%); }
    }
  `]
})
</code></code></pre><p></p><h2>The Future is Bright</h2><p>This change represents more than just a new API&#8212;it's Angular's commitment to embracing web standards while providing the developer experience you expect. By moving to native CSS animations, Angular applications become:</p><ul><li><p><strong>Faster</strong>: hardware-accelerated animations</p></li><li><p><strong>Smaller</strong>: no large animation runtime</p></li><li><p><strong>More Portable</strong>: skills transfer across frameworks</p></li><li><p><strong>More Flexible</strong>: easy third-party library integration</p></li></ul><p>The <code>animations</code> field in component decorators is officially deprecated as of Angular 20.2 and will be removed in version 23, giving developers a clear migration timeline.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D1mY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D1mY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 424w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 848w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 1272w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D1mY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png" width="1456" height="1430" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1430,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:297253,&quot;alt&quot;:&quot;Benefits of Angular Native CSS Animations&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/170098799?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Benefits of Angular Native CSS Animations" title="Benefits of Angular Native CSS Animations" srcset="https://substackcdn.com/image/fetch/$s_!D1mY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 424w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 848w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 1272w, https://substackcdn.com/image/fetch/$s_!D1mY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a56ee36-1f1c-4715-9d4a-a3eb905721b5_2016x1980.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://x.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p>]]></content:encoded></item><item><title><![CDATA[Angular CLI 20.1.0 now Includes AI Integration via Model Context Protocol! 🤖]]></title><description><![CDATA[The Angular CLI 20.1.0 now includes an experimental Model Context Protocol (MCP) server that lets AI assistants understand and work with your project.]]></description><link>https://www.codigotipado.com/p/angular-cli-2010-now-includes-ai</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-cli-2010-now-includes-ai</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Sun, 03 Aug 2025 22:53:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZAxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Dawn of the LLM Era</h2><p><strong>Large Language Models</strong> (LLMs) like ChatGPT, Claude, and Gemini have fundamentally changed how we think about software interaction. These AI systems, trained on vast amounts of text data, can understand and generate human-like responses. But here's the thing: they're incredibly powerful yet frustratingly isolated. They can write brilliant code, explain complex concepts, and solve problems, but they can't do anything in your development environment.</p><p>Imagine having a brilliant coding assistant who can tell you exactly how to fix a bug but can't see your actual codebase, can't run your tests, and can't understand your project structure. That's been the reality for most AI coding tools until now.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZAxZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 424w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 848w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 1272w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png" width="721" height="613" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:613,&quot;width&quot;:721,&quot;resizeWidth&quot;:721,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Isolated LLM power. Born of MCP.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="Isolated LLM power. Born of MCP." title="Isolated LLM power. Born of MCP." srcset="https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 424w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 848w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 1272w, https://substackcdn.com/image/fetch/$s_!ZAxZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1197af8-b7da-42f6-bb05-fd1206f5c58a_721x613.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Enter the Model Context Protocol</h2><p>In November 2024, Anthropic (the company behind Claude) introduced something called the <strong>Model Context Protocol</strong>. Think of MCP as the missing bridge between AI models and the real world of development tools. It's an open standard that allows AI systems to securely connect to external data sources, tools, and services.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p>Here's what makes MCP revolutionary: instead of building custom integrations for every single tool, MCP provides a universal language that AI systems can use to communicate with any compatible service. It's like creating a USB-C standard for AI connectivity.</p><p>The protocol caught fire quickly. By March 2025, even OpenAI (Anthropic's biggest competitor) had adopted it. Google DeepMind followed suit in April. When fierce competitors agree on a standard this quickly, you know something big is happening.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E0ar!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E0ar!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 424w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 848w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 1272w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E0ar!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png" width="724.5625" height="362.28125" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:528,&quot;width&quot;:1056,&quot;resizeWidth&quot;:724.5625,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Universal AI Connectivity: MCP&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="Universal AI Connectivity: MCP" title="Universal AI Connectivity: MCP" srcset="https://substackcdn.com/image/fetch/$s_!E0ar!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 424w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 848w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 1272w, https://substackcdn.com/image/fetch/$s_!E0ar!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F91af6079-1fd7-4642-b37a-a3da4db4600e_1056x528.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Angular's Smart Move</h2><p>Now, here's where Angular's implementation gets interesting. The Angular team didn't just add MCP support &#8211; they've positioned Angular as an AI-first development platform. When you run <code>ng mcp</code>, you're not just starting a server; you're making your entire Angular workspace available to AI systems in a structured, secure way.</p><p>The current implementation provides two key capabilities:</p><p><strong>1. Angular Best Practices as AI Knowledge:</strong> The MCP server exposes Angular's official best practices and coding guidelines directly to AI systems. This means when an AI is helping you code, it has immediate access to the latest Angular conventions, TypeScript patterns, and framework-specific guidance.</p><p><strong>2. Project Structure Awareness:</strong> The server can list and understand your Angular workspace structure, reading from your <code>angular.json</code> file. This gives AI systems spatial awareness of your project &#8211; they know what apps and libraries you have, how they're organized, and can provide contextually relevant suggestions.</p><h2>Getting Started</h2><p>Run <code>ng mcp</code> and you'll get instructions for configuring your AI host to connect to your Angular workspace:</p><pre><code>ng mcp

// If it's the first time, it will print this:                                                                            

To start using the Angular CLI MCP Server, add this configuration to your host:

{
  "servers": {
    "angular-cli": {
      "command": "npx",
      "args": ["@angular/cli", "mcp"]
    }
  }
}</code></pre><p>In my case, I&#8217;m using VS Code, and this is the configuration I need to add: <strong>.vscode/mcp.json</strong>. To check the configuration for other IDEs, follow the <a href="https://angular.dev/ai/mcp">Angular guide</a>.</p><p>Now, if you go to your Copilot in Agent mode (look at <strong>tools)</strong>, you have enabled those features:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GHzV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GHzV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 424w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 848w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 1272w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GHzV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png" width="747" height="119" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:119,&quot;width&quot;:747,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29973,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/169933249?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GHzV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 424w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 848w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 1272w, https://substackcdn.com/image/fetch/$s_!GHzV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d9c530-98b5-4694-9e6f-6d4969b85986_747x119.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>From now on, if you chat (Copilot, Amazon Q&#8230;), these features will be applied. Remember that you can do the same in other IDEs or desktop LLM clients.</p><h2>Conclusion</h2><p>Angular's MCP support might seem like a small feature update, but it represents something much bigger: the transformation of Angular from a framework you use to build applications into a platform for AI-assisted development.</p><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://x.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p>]]></content:encoded></item><item><title><![CDATA[Angular's Animation Revolution: Say Hello to animate.in and animate.out 🎬]]></title><description><![CDATA[After eight years of @angular/animations, it's time for a fresh approach that embraces modern CSS capabilities while keeping the developer experience we love.]]></description><link>https://www.codigotipado.com/p/angulars-animation-revolution-say</link><guid isPermaLink="false">https://www.codigotipado.com/p/angulars-animation-revolution-say</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Fri, 18 Jul 2025 03:31:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/SfXk9rIpEC8" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Problem with Current Animations</h2><p>Let's be honest - <code>@angular/animations</code> has been showing its age. Here's what's been bothering developers:</p><ul><li><p><strong>Heavy bundle impact</strong>: 60kb of JavaScript just for animations</p></li><li><p><strong>No hardware acceleration</strong>: Animations run in JavaScript instead of leveraging the GPU</p></li><li><p><strong>Angular-only skills</strong>: The API doesn't translate to other frameworks or vanilla web development</p></li><li><p><strong>Third-party integration pain</strong>: Want to use GSAP or Anime.js? Good luck with that.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>Enter the New Champions: animate.in and animate.out</h2><p>The Angular team is introducing two new native framework features that will revolutionize how we animate elements entering and leaving the DOM.</p><h3>The Sweet Syntax</h3><pre><code>&lt;!-- CSS Classes (Simple and Clean) --&gt;
&lt;my-component animate.out="fade-out" /&gt;
&lt;my-component [animate.in]="dynamicAnimationClasses" /&gt;

&lt;!-- JavaScript Functions (For the Power Users) --&gt;
&lt;my-component (animate.out)="customExitAnimation($event)" /&gt;
&lt;my-component (animate.in)="handleEntryAnimation($event)" /&gt;</code></pre><h3>Why This Matters</h3><p><strong>animate.out</strong> solves the biggest pain point in CSS animations - you can't easily animate an element out of the DOM because... well, once it's removed, it's gone! This feature delays element removal until your animation completes.</p><p><strong>animate.in</strong> provides symmetry and consistency, automatically removing CSS classes when animations finish. While CSS now has <code>@starting-styles</code> for this, having both features creates a cohesive API.</p><h2>Real-World Examples</h2><h3>CSS-Based Animations</h3><pre><code>&lt;div class="card" 
     animate.in="slide-in-from-left"
     animate.out="fade-out-scale"&gt;
  &lt;h3&gt;I animate beautifully!&lt;/h3&gt;
&lt;/div&gt;</code></pre><pre><code>.slide-in-from-left {
  animation: slideIn 0.3s ease-out;
}

.fade-out-scale {
  animation: fadeOutScale 0.25s ease-in;
}</code></pre><h3>Third-Party Library Integration</h3><pre><code>// GSAP Animation
animateWithGSAP = (event: AnimationEvent) =&gt; {
  gsap.to(event.element, {
    rotation: 360,
    scale: 0,
    duration: 0.5,
    ease: "back.in",
    onComplete() {
      event.animationComplete();
    }
  });
}</code></pre><pre><code>&lt;div (animate.out)="animateWithGSAP($event)"&gt;
  Powered by GSAP!
&lt;/div&gt;</code></pre><h2>What's Different About This Approach?</h2><h3>&#128640; <strong>Performance First</strong></h3><ul><li><p>Native CSS animations with hardware acceleration</p></li><li><p>Tree-shakeable and optimized</p></li><li><p>No 60kb animation library dependency</p></li></ul><h3>&#127919; <strong>Platform-Aligned</strong></h3><ul><li><p>Works with modern CSS features like <code>@starting-styles</code></p></li><li><p>Skills transfer to other frameworks and vanilla web dev</p></li><li><p>Embraces web standards</p></li></ul><h3>&#128295; <strong>Developer Experience</strong></h3><ul><li><p>Simple syntax that follows Angular conventions</p></li><li><p>Works with CSS classes or JavaScript functions</p></li><li><p>Host binding support for reusable animation directives</p></li></ul><h3>&#129514; <strong>Testing-Friendly</strong></h3><ul><li><p>Animations can be disabled in test environments</p></li><li><p>Maintains element lifecycle for reliable testing</p></li></ul><h2>The Great Naming Debate</h2><p>The community is split on naming conventions:</p><ul><li><p><strong>Team animate.in/out</strong>: Short, clear, and follows the dot notation pattern</p></li><li><p><strong>Team animate.enter/leave</strong>: More descriptive and aligns with existing animation terminology</p></li></ul><p>The Angular team seems to be leaning toward <code>animate.in</code> and <code>animate.out</code> to maintain consistency with other dot-notation bindings like <code>class.</code> and <code>style.</code>.</p><h2>What This Means for Your Apps</h2><h3>Migration Path</h3><p>The Angular team is creating comprehensive migration guides from <code>@angular/animations</code> to pure CSS. The old system will coexist during the transition period, so no rush!</p><h3>Bundle Size Impact</h3><p>Removing the animations package dependency could save you <strong>60kb</strong> of JavaScript - that's significant for performance, especially on mobile devices.</p><h3>Future-Proofing</h3><p>As web standards evolve (hello, scoped view transitions!), this approach positions Angular to adopt new capabilities without major API changes.</p><h2>Key Takeaways</h2><ol><li><p><strong>@angular/animations is being deprecated</strong> - but not immediately removed</p></li><li><p><strong>animate.in and animate.out are coming</strong> - built into the framework core</p></li><li><p><strong>CSS-first approach</strong> - embrace modern web platform capabilities</p></li><li><p><strong>Third-party library friendly</strong> - use GSAP, Anime.js, or whatever you prefer</p></li><li><p><strong>Performance wins</strong> - hardware acceleration and smaller bundles</p></li></ol><h2>Try It Out</h2><p>The Angular team has provided a <a href="https://stackblitz.com/~/github.com/thePunderWoman/animate-prototype">StackBlitz prototype</a> where you can experiment with the new features (CSS classes only for now).</p><p>Check the <a href="https://github.com/angular/angular/discussions/62212">complete RFC here</a>.</p><h2>The Bottom Line</h2><p>This RFC represents Angular's commitment to evolving with web standards while maintaining the developer experience that makes the framework special. By embracing CSS-native animations and providing escape hatches for complex scenarios, Angular is positioning itself for the next generation of web applications.</p><p>If you are interested in a guide on old Angular animations, I have a complete tutorial about it on YouTube.</p><div id="youtube2-SfXk9rIpEC8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;SfXk9rIpEC8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/SfXk9rIpEC8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://x.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p><p></p>]]></content:encoded></item><item><title><![CDATA[Angular 20.1.0: What's new]]></title><description><![CDATA[Native Browser Powers Meet Angular: Fetch API Integration, Live Signal Graphs, and the Complete Developer Experience Transformation.]]></description><link>https://www.codigotipado.com/p/angular-2010-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-2010-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Mon, 14 Jul 2025 04:29:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nW50!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nW50!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nW50!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!nW50!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!nW50!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!nW50!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nW50!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:203880,&quot;alt&quot;:&quot;Angular 20.1&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/165521860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 20.1" title="Angular 20.1" srcset="https://substackcdn.com/image/fetch/$s_!nW50!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!nW50!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!nW50!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!nW50!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcce0dae7-95ca-435a-b0a5-2e3a344a368a_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>&#128293; @angular/core</strong></h2><h3>1. New <code>destroyed</code> Property on <code>DestroyRef (#61849)</code></h3><p>Angular 20.1.0 adds a <code>destroyed</code> property to <code>DestroyRef</code> that lets you check if a component is already destroyed before registering cleanup callbacks, preventing runtime errors.</p><pre><code>import { Component, inject, signal, ChangeDetectionStrategy } from '@angular/core';
import { DestroyRef } from '@angular/core';

@Component({
  selector: 'app-safe-cleanup',
  template: `
    &lt;div class="cleanup"&gt;
      &lt;p&gt;Status: {{ status() }}&lt;/p&gt;
      &lt;button 
        type="button"
        (click)="handleAsyncOperation()"
        [disabled]="loading()"&gt;
        Start Operation
      &lt;/button&gt;
    &lt;/div&gt;
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SafeCleanupComponent {
  private readonly _destroyRef = inject(DestroyRef);
  
  loading = signal(false);
  status = signal('Ready');

  handleAsyncOperation(): void {
    this.loading.set(true);
    
    setTimeout(() =&gt; {
      // Check before registering cleanup
      if (!this._destroyRef.destroyed) {
        this._destroyRef.onDestroy(() =&gt; {
          console.log('Cleanup executed');
        });
        
        this.loading.set(false);
        this.status.set('Completed');
      }
    }, 2000);
  }
}</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h3>2. TestBed: New Bindings Support &#129514; (<a href="https://github.com/angular/angular/pull/62040">#62040</a>)</h3><p>Angular's <code>TestBed.createComponent()</code> now supports direct component bindings, eliminating the need for wrapper components in tests.</p><pre><code>interface TestComponentOptions {
  bindings?: Binding[];
}

TestBed.createComponent&lt;T&gt;(
  component: Type&lt;T&gt;, 
  options?: TestComponentOptions
): ComponentFixture&lt;T&gt;

// Examples
it('should bind to inputs', () =&gt; {
  const value = signal(10);
  const fixture = TestBed.createComponent(MyComponent, {
    bindings: [inputBinding('value', value)]
  });
  
  fixture.detectChanges();
  expect(fixture.componentInstance.value).toBe(10);
  
  value.set(20); // Reactive updates work!
  fixture.detectChanges();
  expect(fixture.componentInstance.value).toBe(20);
});

it('should bind to outputs', () =&gt; {
  let clickCount = 0;
  const fixture = TestBed.createComponent(MyComponent, {
    bindings: [outputBinding('click', () =&gt; clickCount++)]
  });
  
  // Trigger component event
  fixture.componentInstance.click.emit();
  expect(clickCount).toBe(1);
});

it('should support two-way binding', () =&gt; {
  const value = signal('initial');
  const fixture = TestBed.createComponent(MyComponent, {
    bindings: [twoWayBinding('value', value)]
  });
  
  // Changes propagate both ways automatically
  value.set('updated');
  fixture.detectChanges();
  expect(fixture.componentInstance.value).toBe('updated');
});</code></pre><p></p><h3>3. Add destroyed property to EnvironmentInjector - <a href="https://github.com/angular/angular/pull/61951">PR</a></h3><p>Ever wondered if that dynamically created <code>EnvironmentInjector</code> is still alive? Angular's latest enhancement adds a <code>destroyed</code> property to <code>EnvironmentInjector</code>, giving you the same lifecycle awareness that <code>DestroyRef</code> brought to components.</p><h4>The Problem This Solves</h4><p>When working with dynamic injectors (especially in complex applications with lazy loading or dynamic component creation), developers often faced a black box situation:</p><pre><code>// Before: No way to know if injector is still valid
const dynamicInjector = createEnvironmentInjector([SomeService], parentInjector);

// Later in code... is this safe?
// &#129335;&#8205;&#9792;&#65039; Could throw if destroyed
const service = dynamicInjector.get(SomeService);

// After: Crystal clear lifecycle management
const dynamicInjector = createEnvironmentInjector([SomeService], parentInjector);

// Check if it's still alive
if (!dynamicInjector.destroyed) {
  const service = dynamicInjector.get(SomeService); // &#9989; Safe!
}

// Register cleanup callbacks
dynamicInjector.onDestroy(() =&gt; {
  console.log('Injector is being destroyed!');
  // Cleanup logic here
});</code></pre><p></p><h2><strong>&#128293; @angular/common</strong></h2><h3>1. NgOptimizedImage: New <code>decoding</code> Attribute Support (<a href="https://github.com/angular/angular/pull/61905">#61905</a>)</h3><p>Angular's <code>NgOptimizedImage</code> directive now supports the native HTML <code>decoding</code> attribute, giving developers control over image decoding behavior for better performance optimization.</p><pre><code>@Input() decoding?: 'sync' | 'async' | 'auto';

&lt;!-- Default behavior (auto) --&gt;
&lt;img ngSrc="hero.jpg" width="800" height="400"&gt;

&lt;!-- Priority image (automatically gets sync) --&gt;
&lt;img ngSrc="hero.jpg" width="800" height="400" priority&gt;

&lt;!-- Custom decoding strategy --&gt;
&lt;img ngSrc="gallery.jpg" width="300" height="200" decoding="async"&gt;</code></pre><p><strong>Smart Defaults:</strong></p><ul><li><p><strong>Default images</strong>: <code>decoding="auto"</code> (browser decides)</p></li><li><p><strong>Priority images</strong>: <code>decoding="sync"</code> (immediate decoding for faster LCP)</p></li><li><p><strong>Custom override</strong>: Developers can specify any valid value</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!biic!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!biic!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 424w, https://substackcdn.com/image/fetch/$s_!biic!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 848w, https://substackcdn.com/image/fetch/$s_!biic!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 1272w, https://substackcdn.com/image/fetch/$s_!biic!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!biic!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png" width="1004" height="187" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:187,&quot;width&quot;:1004,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40991,&quot;alt&quot;:&quot;NgOptimizedImage new decoded property in Angular 20.1.0&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/165521860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="NgOptimizedImage new decoded property in Angular 20.1.0" title="NgOptimizedImage new decoded property in Angular 20.1.0" srcset="https://substackcdn.com/image/fetch/$s_!biic!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 424w, https://substackcdn.com/image/fetch/$s_!biic!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 848w, https://substackcdn.com/image/fetch/$s_!biic!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 1272w, https://substackcdn.com/image/fetch/$s_!biic!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a8df987-62f5-4289-8afc-53f146801a7a_1004x187.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Performance benefits</figcaption></figure></div><p></p><h3>2. NgComponentOutlet Just Learned Module Federation Magic - <a href="https://github.com/angular/angular/pull/54764">PR</a></h3><p>Ever tried to dynamically load a component from a remote micro-frontend and watched it fail miserably because it couldn't find its services? That dependency injection nightmare just got solved.</p><p>NgComponentOutlet now supports a custom EnvironmentInjector input, making it a first-class citizen for Module Federation and micro-frontend architectures.</p><pre><code>@Component({
  selector: 'tenant-dashboard',
  template: `
    &lt;!-- Each tenant gets their own component with their own services --&gt;
    &lt;div *ngFor="let tenant of tenants"&gt;
      &lt;ng-container 
        *ngComponentOutlet="tenantComponent;
                            environmentInjector: tenant.injector;
                            inputs: { tenantId: tenant.id }"&gt;
      &lt;/ng-container&gt;
    &lt;/div&gt;
  `
})
export class MultiTenantDashboard {
  tenants: Array&lt;{
    id: string;
    injector: EnvironmentInjector;
  }&gt; = [];
  
  tenantComponent = TenantSpecificComponent;
  
  async loadTenant(tenantConfig: TenantConfig) {
    // Load tenant-specific module with its own services
    const tenantModule = await import(`./tenants/${tenantConfig.name}.module`);

    // &#9989; Gets its own injection context!
    const tenantInjector = createEnvironmentInjector(
      tenantModule.providers,
      this.currentInjector
    );
    
    this.tenants.push({
      id: tenantConfig.id,
      injector: tenantInjector
    });
  }
}</code></pre><p></p><h2><strong>&#128293; @service-worker</strong></h2><h3>1. Service Worker Notification Close Events (<a href="https://github.com/angular/angular/pull/61442">#61442</a>)</h3><p>Adds support for tracking when push notifications are closed or dismissed. Now you can react when users dismiss notifications, not just when they click them.</p><p>Before this feature, you could only track when users <strong>clicked</strong> notifications. Now you can also track when they <strong>dismiss</strong> them, giving you complete visibility into notification interactions. </p><p></p><h3>2. Push Subscription Change Detection (<a href="https://github.com/angular/angular/pull/61856">#61856</a>)</h3><p>When browsers refresh or invalidate push subscriptions, your app can now react automatically to maintain reliable notifications.</p><p>Push subscriptions can change <strong>without user action</strong>:</p><ul><li><p>Browser updates subscription keys</p></li><li><p>Security tokens expire and refresh</p></li><li><p>Network changes trigger re-registration</p></li><li><p>Browser maintenance invalidates old subscriptions</p></li></ul><p>Previously, your app wouldn't know about these changes, causing push notifications to silently fail.</p><p></p><h2><strong>&#128293; @angular/build</strong></h2><h3>1. Customizable Code Coverage Reporters (<a href="https://github.com/angular/angular-cli/commit/1159cf08103081d2b851e59bc1c5fb200f114982">PR</a>)</h3><p>Adds a <code>codeCoverageReporters</code> option to the experimental <code>unit-test</code> builder, allowing you to customize how test coverage results are generated and formatted.</p><p>Previously, you could only enable/disable code coverage. Now you can:</p><ul><li><p>Choose specific output formats (HTML, LCOV, XML, etc.)</p></li><li><p>Generate multiple report formats simultaneously</p></li><li><p>Customize individual reporter options</p></li><li><p>Better integrate with CI/CD pipelines and external tools</p></li></ul><p>This only works if you set<strong> codeCoverage </strong>option to true:  </p><pre><code><code>// angular.json - Basic Configuration
{
  "projects": {
    "my-app": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "buildTarget": "my-app:build",
            "tsConfig": "tsconfig.spec.json",
            "runner": "vitest",
            "codeCoverage": true,
            "codeCoverageReporters": ["html", "lcov", "text-summary"]
          }
        }
      }
    }
  }
}

// Advanced Configuration with Custom Options
{
  "projects": {
    "my-app": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "buildTarget": "my-app:build",
            "tsConfig": "tsconfig.spec.json",
            "runner": "vitest",
            "codeCoverage": true,
            "codeCoverageReporters": [
              "text-summary",
              ["html", { "subdir": "html-report" }],
              ["lcov", { "file": "lcov.info" }],
              ["cobertura", { "file": "cobertura.xml" }]
            ]
          }
        }
      }
    }
  }
}</code></code></pre><h4>Available Reporter Types</h4><h5>1. HTML Reporter</h5><pre><code><code>{
  "codeCoverageReporters": [
    ["html", {
      "subdir": "coverage-html",
      "skipEmpty": false
    }]
  ]
}</code></code></pre><p><strong>Output</strong>: Interactive HTML report with detailed file-by-file coverage <strong>Best for</strong>: Local development, detailed analysis</p><h5>2. LCOV Reporter</h5><pre><code><code>{
  "codeCoverageReporters": [
    ["lcov", {
      "file": "lcov.info",
      "projectRoot": "src/"
    }]
  ]
}</code></code></pre><p><strong>Output</strong>: LCOV format compatible with many tools <strong>Best for</strong>: CI/CD integration, SonarQube, Codecov</p><h5>3. Text Summary</h5><pre><code><code>{
  "codeCoverageReporters": ["text-summary"]
}</code></code></pre><p><strong>Output</strong>: Console summary with overall percentages <strong>Best for</strong>: Quick CLI feedback</p><h5>4. Cobertura XML</h5><pre><code><code>{
  "codeCoverageReporters": [
    ["cobertura", {
      "file": "cobertura-coverage.xml"
    }]
  ]
}</code></code></pre><p><strong>Output</strong>: XML format for Jenkins, Azure DevOps <strong>Best for</strong>: Enterprise CI/CD systems</p><p></p><h2><strong>&#128293; @angular/compiler</strong></h2><h3>1. Enhanced Binary Assignment Operators in Templates - <a href="https://github.com/angular/angular/pull/62064">PR</a></h3><p>Angular 20.1.0 will introduce expanded support for <strong>binary assignment operators</strong> in templates, bringing Angular's expression syntax closer to standard JavaScript and making template code more concise and intuitive.</p><pre><code>&lt;button (click)="counter += 1"&gt;Increment&lt;/button&gt;
&lt;button (click)="counter -= 1"&gt;Decrement&lt;/button&gt;
&lt;button (click)="value *= 2"&gt;Double&lt;/button&gt;</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TASl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TASl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 424w, https://substackcdn.com/image/fetch/$s_!TASl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 848w, https://substackcdn.com/image/fetch/$s_!TASl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 1272w, https://substackcdn.com/image/fetch/$s_!TASl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TASl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png" width="1142" height="522" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:522,&quot;width&quot;:1142,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:139385,&quot;alt&quot;:&quot;Enhanced Binary Assignment Operators in Angular Templates&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/165521860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Enhanced Binary Assignment Operators in Angular Templates" title="Enhanced Binary Assignment Operators in Angular Templates" srcset="https://substackcdn.com/image/fetch/$s_!TASl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 424w, https://substackcdn.com/image/fetch/$s_!TASl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 848w, https://substackcdn.com/image/fetch/$s_!TASl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 1272w, https://substackcdn.com/image/fetch/$s_!TASl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5055bb9d-c6c6-41a3-9fa2-dd98296edff4_1142x522.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2><strong>&#128293; @angular/common/http</strong></h2><p>Both <strong>HttpClient</strong> and <strong>httpResource</strong> now expose key Fetch API options that were previously impossible to control.</p><h3><strong>&#127919; What's New:</strong></h3><ul><li><p><code>priority</code> &#8211; Tell the browser which requests are critical vs background</p></li><li><p><code>cache</code> &#8211; Control browser caching behavior (<code>force-cache</code>, <code>no-store</code>, etc.)</p></li><li><p><code>credentials</code> &#8211; Fine-tune cookie and authentication handling</p></li><li><p><code>timeout</code> &#8211; Built-in request timeouts (no more manual AbortController!)</p></li><li><p><code>mode &amp; redirect</code> &#8211; Precise CORS and redirect control</p></li><li><p><code>keepalive</code> &#8211; Requests survive page navigation</p></li></ul><pre><code>// Before: Basic HTTP with limited control
this.http.get('/api/data');

// After: Browser-native performance tuning
this.http.get('/api/critical-data', {
  priority: 'high',        // &#127950;&#65039; VIP network treatment
  cache: 'force-cache',    // &#9889; Instant subsequent loads  
  timeout: 2000,          // &#9201;&#65039; No hanging requests
  keepalive: true         // &#128260; Survives navigation
});

// Same powers in httpResource!
const data = httpResource(() =&gt; ({
  url: '/api/live-data',
  priority: 'auto',
  cache: 'no-store'       // Always fresh
}));</code></pre><p>&#127959;&#65039; <strong>The Complete Fetch Feature Matrix</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8EOP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8EOP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 424w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 848w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 1272w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8EOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png" width="1371" height="514" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:514,&quot;width&quot;:1371,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:158479,&quot;alt&quot;:&quot;Angular 20.1.0: Complete Fetch Feature Matrix&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/165521860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Angular 20.1.0: Complete Fetch Feature Matrix" title="Angular 20.1.0: Complete Fetch Feature Matrix" srcset="https://substackcdn.com/image/fetch/$s_!8EOP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 424w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 848w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 1272w, https://substackcdn.com/image/fetch/$s_!8EOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe5c05ed6-f15f-4b66-b57a-221347e7a93f_1371x514.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>*Timeout is an Angular addition - fetch doesn't have native timeout</em></p><p>This isn't just about adding features - it's about <strong>fundamentally upgrading Angular's HTTP layer</strong> to match modern web platform capabilities. Every single fetch API feature is now available in Angular, with the same ease of use developers expect.</p><p><strong>The result:</strong> Angular applications can now achieve <strong>native browser performance</strong> for HTTP operations while maintaining Angular's developer experience and ecosystem benefits.</p><p></p><h2><strong>&#128293; @language-service</strong></h2><h3>1. add semantic tokens for templates - <a href="https://github.com/angular/angular/pull/60260">PR</a></h3><p>Ever envied how JetBrains IDEs make Angular component tags pop with perfect syntax highlighting while VS Code treats them like regular HTML? That gap just closed.</p><p>Angular's Language Service now provides semantic tokens for templates, bringing contextual syntax highlighting that understands your code's meaning, not just its text patterns.</p><pre><code>&lt;!-- All tags look the same --&gt;
&lt;div&gt;Regular HTML&lt;/div&gt;
&lt;app-user-card&gt;Angular Component&lt;/app-user-card&gt;
&lt;button&gt;Another HTML element&lt;/button&gt;

&lt;!-- Now with semantic awareness! --&gt;
&lt;div&gt;Regular HTML&lt;/div&gt;           &lt;!-- HTML element color --&gt;
&lt;app-user-card&gt;Component&lt;/app-user-card&gt;  &lt;!-- Class/component color --&gt;  
&lt;button&gt;HTML element&lt;/button&gt;     &lt;!-- HTML element color --&gt;</code></pre><p></p><h3>2. Support to fix missing required inputs diagnostic - <a href="https://github.com/angular/angular/commit/5d2e85920e714560e8d06bfb9c41d9312eeaae3b">PR</a></h3><p>Imagine typing <code>&lt;app-user-card&gt;&lt;/app-user-card&gt;</code> and VS Code immediately says: <em>"Hey, you forgot the required </em><code>user</code><em> input. Want me to fix that?"</em> That's exactly what just happened.</p><p>Angular's Language Service now provides <strong>intelligent quick fixes</strong> for missing required inputs, turning template errors into one-click solutions.</p><pre><code>&lt;!-- Before --&gt;
&lt;app-user-profile&gt;&lt;/app-user-profile&gt;
!--  ~~~~~~~~~~~~~~~~ &#128680; Red squiggly line appears --&gt;

&lt;!-- After quick fix --&gt;
&lt;app-user-profile [user]=""&gt;&lt;/app-user-profile&gt;
&lt;!--                 ~~~~~~~~ &#10024; Added automatically! --&gt;</code></pre><p></p><h2><strong>&#128293; @angular/router</strong></h2><h3>1. Run loadComponent and loadChildren functions in the route's injection context - <a href="https://github.com/angular/angular/commit/9833d9ea47b717293c9df0d8a5c285a3c4ad35d0">PR</a></h3><p>Ever tried using <code>inject()</code> inside a <code>loadChildren</code> or <code>loadComponent</code> function and hit this frustrating error?</p><pre><code>// NG0203: inject() must be called from an injection context

// &#10060; This would throw an error
{
  path: 'dashboard',
  loadComponent: () =&gt; {
    const config = inject(FeatureConfig); // &#128165; NG0203 Error!
    return config.useAdvancedDashboard 
      ? import('./advanced-dashboard.component')
      : import('./basic-dashboard.component');
  }
}

// &#9989; This works perfectly!
{
  path: 'dashboard', 
  loadComponent: () =&gt; {
    const config = inject(FeatureConfig); // &#127919; Works like magic!
    return config.useAdvancedDashboard
      ? import('./advanced-dashboard.component')
      : import('./basic-dashboard.component');
  }
}</code></pre><p><a href="https://netbasal.medium.com/injection-aware-lazy-loading-for-loadcomponent-and-loadchildren-in-angular-3fddec149400">Here is a good article </a>with examples.</p><p></p><h2><strong>&#128293; @angular/cli</strong></h2><h3>1. MCP Server Implementation - <a href="https://github.com/angular/angular-cli/commit/dc45c186ec16e345b75ffcd57961a8e0cfd4b649">PR</a></h3><p>An initial experimental implementation of a Model Context Protocol (MCP) server is now available as a command within the Angular CLI.</p><p>Check out my post about the <a href="https://www.codigotipado.com/p/angular-cli-2010-now-includes-ai">comprehensive guide to Angular MCP Server</a>, including the latest best practices.</p><p></p><h2><strong>&#128293; Angular Material (components)</strong></h2><h3>1. CDK Drag-Drop Anchor Elements - cdk - <a href="https://github.com/angular/components/pull/31288">PR</a></h3><p>Adds <code>cdkDropListHasAnchor</code> to the CDK drag-drop module. This creates an "anchor" element that stays in the original position when dragging items between lists, perfect for copy operations.</p><p><strong>Before</strong>: When dragging items between lists, the placeholder moves with the item, making it look like the item is being moved (not copied).</p><p><strong>After</strong>: With anchor elements, the original position is preserved visually, making copy operations feel more natural.</p><pre><code>import { Component, signal } from '@angular/core';
import { CdkDragDrop, moveItemInArray, copyArrayItem, CdkDrag, CdkDropList } from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-shopping-cart',
  template: `
    &lt;div class="shopping-container"&gt;
      &lt;div class="shopping-container__section"&gt;
        &lt;h3&gt;Available Products&lt;/h3&gt;
        
        &lt;div
          cdkDropList
          [cdkDropListData]="products()"
          [cdkDropListConnectedTo]="[cartList]"
          cdkDropListSortingDisabled
          cdkDropListHasAnchor
          class="product-list"
          (cdkDropListDropped)="_handleProductDrop($event)"&gt;
          
          @for (product of products(); track product.id) {
            &lt;div class="product-item" cdkDrag [cdkDragData]="product"&gt;
              &lt;div class="product-item__info"&gt;
                &lt;h4&gt;{{ product.name }}&lt;/h4&gt;
                &lt;p class="product-item__price"&gt;\${{ product.price }}&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class="product-item__stock"&gt;
                Stock: {{ product.stock }}
              &lt;/div&gt;
            &lt;/div&gt;
          }
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="shopping-container__section"&gt;
        &lt;h3&gt;Shopping Cart ({{ cartTotal() }} items)&lt;/h3&gt;
        
        &lt;div
          cdkDropList
          #cartList="cdkDropList"
          [cdkDropListData]="cart()"
          class="cart-list"
          (cdkDropListDropped)="_handleCartDrop($event)"&gt;
          
          @for (item of cart(); track item.id) {
            &lt;div class="cart-item" cdkDrag&gt;
              &lt;div class="cart-item__info"&gt;
                &lt;h4&gt;{{ item.name }}&lt;/h4&gt;
                &lt;p class="cart-item__quantity"&gt;Qty: {{ item.quantity }}&lt;/p&gt;
              &lt;/div&gt;
              &lt;div class="cart-item__total"&gt;
                \${{ (item.price * item.quantity).toFixed(2) }}
              &lt;/div&gt;
              &lt;button 
                type="button"
                class="cart-item__remove"
                (click)="_removeFromCart(item.id)"&gt;
                &#215;
              &lt;/button&gt;
            &lt;/div&gt;
          }
          
          @if (cart().length === 0) {
            &lt;div class="cart-list__empty"&gt;
              Drag products here to add to cart
            &lt;/div&gt;
          }
        &lt;/div&gt;
        
        @if (cart().length &gt; 0) {
          &lt;div class="cart-summary"&gt;
            &lt;div class="cart-summary__total"&gt;
              Total: \${{ cartTotalPrice().toFixed(2) }}
            &lt;/div&gt;
            &lt;button 
              type="button"
              class="cart-summary__checkout"
              (click)="_checkout()"&gt;
              Checkout
            &lt;/button&gt;
          &lt;/div&gt;
        }
      &lt;/div&gt;
    &lt;/div&gt;
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ShoppingCartComponent {
  products = signal([
    { id: 1, name: 'Wireless Headphones', price: 99.99, stock: 15 },
    { id: 2, name: 'Smartphone Case', price: 24.99, stock: 8 },
    { id: 3, name: 'USB-C Cable', price: 12.99, stock: 25 },
    { id: 4, name: 'Portable Charger', price: 49.99, stock: 12 },
    { id: 5, name: 'Bluetooth Speaker', price: 79.99, stock: 6 }
  ]);
  
  cart = signal&lt;Array&lt;{
    id: number;
    name: string;
    price: number;
    quantity: number;
  }&gt;&gt;([]);
  
  cartTotal = computed(() =&gt; 
    this.cart().reduce((sum, item) =&gt; sum + item.quantity, 0)
  );
  
  cartTotalPrice = computed(() =&gt;
    this.cart().reduce((sum, item) =&gt; sum + (item.price * item.quantity), 0)
  );

  _handleProductDrop(event: CdkDragDrop&lt;any[]&gt;): void {
    // Products can only be copied to cart, not rearranged
    if (event.previousContainer !== event.container) {
      const product = event.previousContainer.data[event.previousIndex];
      this._addToCart(product);
    }
  }

  _handleCartDrop(event: CdkDragDrop&lt;any[]&gt;): void {
    if (event.previousContainer === event.container) {
      // Reorder items in cart
      const cartItems = [...this.cart()];
      moveItemInArray(cartItems, event.previousIndex, event.currentIndex);
      this.cart.set(cartItems);
    } else {
      // Add product from product list
      const product = event.previousContainer.data[event.previousIndex];
      this._addToCart(product);
    }
  }

  _addToCart(product: any): void {
    const currentCart = [...this.cart()];
    const existingItem = currentCart.find(item =&gt; item.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      currentCart.push({
        id: product.id,
        name: product.name,
        price: product.price,
        quantity: 1
      });
    }
    
    this.cart.set(currentCart);
  }

  _removeFromCart(productId: number): void {
    this.cart.update(cart =&gt; cart.filter(item =&gt; item.id !== productId));
  }

  _checkout(): void {
    console.log('Checkout with items:', this.cart());
    // Reset cart after checkout
    this.cart.set([]);
  }
}</code></pre><p>This feature significantly improves the UX for copy-based drag-and-drop interactions, making it clear that items remain in their original location.</p><p></p><h3>2. Material Chips Edit Icon - material - <a href="https://github.com/angular/components/pull/31041">PR</a></h3><p>adds an optional edit icon to Material Design chips using the <code>matChipEdit</code> directive. This makes editable chips much more discoverable and user-friendly.</p><p><strong>Before</strong>: Users had to double-click or focus+Enter to edit chips - not very discoverable.</p><p><strong>After</strong>: Clear visual edit button shows that chips are editable and provides direct interaction.</p><pre><code>// Before: Only double-click editing (hidden functionality)
&lt;mat-chip-row [editable]="true"&gt;{{ tag.name }}&lt;/mat-chip-row&gt;

// After: Visible edit button (discoverable functionality)
&lt;mat-chip-row [editable]="true"&gt;
  &lt;button matChipEdit&gt;
    &lt;mat-icon&gt;edit&lt;/mat-icon&gt;
  &lt;/button&gt;
  {{ tag.name }}
&lt;/mat-chip-row&gt;</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jpce!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jpce!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 424w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 848w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 1272w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jpce!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png" width="998" height="239" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:239,&quot;width&quot;:998,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67305,&quot;alt&quot;:&quot;Angular Material Edit Chips&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/165521860?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Angular Material Edit Chips" title="Angular Material Edit Chips" srcset="https://substackcdn.com/image/fetch/$s_!Jpce!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 424w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 848w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 1272w, https://substackcdn.com/image/fetch/$s_!Jpce!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F066c2164-fd41-4e3a-bf4b-b054aa589f1b_998x239.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Image from the PR</figcaption></figure></div><p></p><h2>&#128269; <strong>Experimental Signal &amp; Effects Graph in DevTools</strong></h2><p>Angular 20.1 introduces <strong>live signal dependency visualization</strong> right in your browser! This experimental feature lets you see your reactive signal graph in real-time as your app runs.</p><h3><strong>&#127919; How to Enable:</strong></h3><ol><li><p>Open Angular DevTools</p></li><li><p>Click the gear icon &#9881;&#65039;</p></li><li><p>Toggle <strong>"Enable Signals &amp; Effects Graph"</strong></p></li></ol><h3><strong>&#128064; What You'll See:</strong></h3><p>Each signal and effect appears as a labeled box showing:</p><ul><li><p><strong>Name</strong> and <strong>current value</strong></p></li><li><p><strong>Dependency arrows</strong> connecting related signals</p></li><li><p><strong>Color coding</strong> by type:</p><ul><li><p>&#128309; Regular signals (blue)</p></li><li><p>&#128994; Computed signals (green)</p></li><li><p>&#128308; Linked signals (red)</p></li><li><p>&#9899; Effects (dark gray)</p></li></ul></li></ul><pre><code>// Now you can visually see this dependency chain:
const count = signal(0);                    // &#128309; Blue box
const doubled = computed(() =&gt; count() * 2); // &#128994; Green box (arrow from count)
const effect = effect(() =&gt; {               // &#9899; Gray box (arrow from doubled)
  console.log('Doubled:', doubled());
});</code></pre><div id="youtube2-cM8nhRY2Jzk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;cM8nhRY2Jzk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/cM8nhRY2Jzk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Check this video from Igor <a href="https://www.linkedin.com/in/igorsedov/">Sedov</a>.</p><p></p><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you really liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://twitter.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p>]]></content:encoded></item><item><title><![CDATA[Angular 20: What's new]]></title><description><![CDATA[Angular 20 represents a significant milestone in the framework's evolution, introducing groundbreaking features that enhance developer experience, performance, and application architecture.]]></description><link>https://www.codigotipado.com/p/angular-20-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-20-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Tue, 03 Jun 2025 13:40:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!AWb3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AWb3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AWb3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AWb3!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:815410,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AWb3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!AWb3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abb0275-2efa-401d-bb31-c78b6f68c8e0_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>&#128293; @angular/core</h2><h3>1. New features for Dynamically-Created Components (<a href="https://github.com/angular/angular/pull/60137">#60137</a>)</h3><ul><li><p>Input binding</p></li><li><p>Support listening to outputs</p></li><li><p>Two-way bindings</p></li><li><p>Ability to apply directives</p></li></ul><pre><code>import {createComponent, signal, inputBinding, outputBinding} from '@angular/core';

const canClose = signal(false);

// Create MyDialog
createComponent(MyDialog, {
  bindings: [
    // Bind a signal to the `canClose` input.
    inputBinding('canClose', canClose),

    // Listen for the `onClose` event specifically on the dialog.
    outputBinding&lt;Result&gt;('onClose', result =&gt; console.log(result)),
  ],
  directives: [
    // Apply the `FocusTrap` directive to `MyDialog` without any bindings.
    FocusTrap,

    // Apply the `HasColor` directive to `MyDialog` and bind the `red` value to its `color` input.
    // The callback to `inputBinding` is invoked on each change detection.
    {
      type: HasColor,
      bindings: [inputBinding('color', () =&gt; 'red')]
    }
  ]
});</code></pre><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Angular: From Zero to Expert is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h3>2. Enhanced Error Handling: provideBrowserGlobalErrorListeners() (<a href="https://github.com/angular/angular/pull/60704">#60704</a>)</h3><p>Previously, certain types of errors could slip through Angular's error handling system:</p><ul><li><p><strong>Zone.js applications</strong>: errors occurring outside the Angular Zone wouldn't be caught by the framework</p></li><li><p><strong>Zoneless applications</strong>: uncaught errors not explicitly handled by the framework could go unnoticed</p></li><li><p><strong>Promise rejections</strong>: unhandled promise rejections at the window level weren't forwarded to your error handler</p></li></ul><p>These "escaped" errors would only appear in the browser console, making them harder to track and debug in production applications.</p><h4>How It Works</h4><p>The new provider installs global event listeners on the browser window for two critical error events:</p><ul><li><p><code>unhandledrejection</code> - Catches unhandled promise rejections</p></li><li><p><code>error</code> - Catches uncaught JavaScript errors</p></li></ul><p>When these events occur, they're automatically forwarded to your application's <code>ErrorHandler</code>, ensuring consistent error reporting across your entire application.</p><h4>Implementation</h4><p>Adding global error handling to your application is straightforward:</p><pre><code>import { bootstrapApplication } from '@angular/platform-browser';
import { provideBrowserGlobalErrorListeners } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideBrowserGlobalErrorListeners(),
    // ... other providers
  ]
});</code></pre><p></p><h3>3. TypeScript versions less than 5.8 are no longer supported. (<a href="https://github.com/angular/angular/pull/60197">#60197</a>)</h3><p></p><h3>4. New TestBed.tick() method (<a href="https://github.com/angular/angular/pull/60993">#60993</a>)</h3><p>The <code>TestBed.tick()</code> method is designed to mirror the behaviour of <code>ApplicationRef.tick()</code> in your unit tests. It synchronises the application state with the DOM, ensuring that all pending changes are processed and reflected in your test environment.</p><p>Previously, Angular testing relied on <code>TestBed.flushEffects()</code> to handle pending effects in tests. However, this method had limitations:</p><ul><li><p><strong>Limited scope</strong>: only handled impacts, not the complete synchronisation cycle</p></li><li><p><strong>Inconsistent behaviour</strong>: didn't match the production application's synchronisation logic</p></li><li><p><strong>Confusing naming</strong>: the method name didn't indicate its broader impact on the test state</p></li></ul><pre><code>/ Before (Angular 19 and earlier)
TestBed.flushEffects(); // Only flushes effects
fixture.detectChanges(); // Separate step for change detection

// After (Angular 20)
TestBed.tick(); // Handles effects, change detection, and DOM sync</code></pre><p><strong>&#9888;&#65039; BREAKING CHANGE</strong>: <code>TestBed.flushEffects()</code> has been removed in Angular 20. All existing usages must be updated to use <code>TestBed.tick()</code>.</p><p></p><h3>5. New Stable APIs</h3><ul><li><p><code>toObservable()</code></p></li><li><p><code>effect()</code></p></li><li><p><code>linkedSignal()</code></p></li><li><p><code>toSignal()</code></p></li><li><p><code>incremental hydration api</code></p></li><li><p><code>withI18nSupport()</code></p><ul><li><p>Enables support for hydrating i18n blocks</p></li></ul></li><li><p>afterRender() &#10140; renamed to <strong>afterEveryRender</strong>() <strong>&#9888;&#65039;</strong></p><ul><li><p><a href="https://www.codigotipado.com/p/angular-afterrender-and-afternextrender">Learn more about afterEveryRender() here</a></p><div id="youtube2-WeW9zXzrSSY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;WeW9zXzrSSY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/WeW9zXzrSSY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div></li></ul></li></ul><p></p><h3>6. Enhanced Change Detection Debugging: provideCheckNoChangesConfig() (<a href="https://github.com/angular/angular/pull/60906">#60906</a>)</h3><p>This provider helps developers catch subtle bugs related to change detection by periodically verifying that no expressions have changed after they were checked. It's particularly valuable for:</p><ul><li><p><strong>Zoneless applications</strong>: ensuring state changes are properly detected</p></li><li><p><strong>OnPush component debugging</strong>: surfacing hidden errors in optimised components</p></li><li><p><strong>Unidirectional data flow validation</strong>: catching side effects that violate Angular's change detection model</p></li></ul><pre><code>// Before (Angular 19 - Experimental)
import { provideExperimentalCheckNoChangesForDebug } from '@angular/core';

// After (Angular 20 - Developer Preview)
import { provideCheckNoChangesConfig } from '@angular/core';

// Exhaustive checking (recommended for thorough debugging)
provideCheckNoChangesConfig({
  exhaustive: true,
  interval: 1000  // Check every second
})

// Non-exhaustive checking (lighter performance impact)
provideCheckNoChangesConfig({
  exhaustive: false
})</code></pre><p>The <code>useNgZoneOnStable</code> option has been removed as it wasn't found to be generally more helpful than the <code>interval</code> approach:</p><pre><code>// &#10060; No longer available in Angular 20
provideExperimentalCheckNoChangesForDebug({
  useNgZoneOnStable: true  // This option is removed
})

// &#9989; Use interval-based checking instead
provideCheckNoChangesConfig({
  exhaustive: true,
  interval: 1000
})</code></pre><h4>Understanding the Configuration Options</h4><h5>Non-Exhaustive Mode (<code>exhaustive: false</code>)</h5><pre><code><code>provideCheckNoChangesConfig({ exhaustive: false })</code></code></pre><ul><li><p><strong>Behaviour</strong>: only checks components marked for change detection</p></li><li><p><strong>Performance</strong>: lighter impact, similar to production behaviour</p></li><li><p><strong>Use case</strong>: general debugging without deep inspection of OnPush components</p></li></ul><h5>Exhaustive Mode (<code>exhaustive: true</code>)</h5><pre><code><code>provideCheckNoChangesConfig({ 
  exhaustive: true,
  interval: 2000  // Optional: periodic checking
})</code></code></pre><ul><li><p><strong>Behaviour</strong>: Treats ALL components as if they use <code>ChangeDetectionStrategy.Default</code></p></li><li><p><strong>Coverage</strong>: Checks all views attached to <code>ApplicationRef</code> and their descendants</p></li><li><p><strong>Benefits</strong>: Surfaces errors hidden by OnPush optimisation</p></li><li><p><strong>Use case</strong>: Thorough debugging, especially for OnPush component issues</p></li></ul><h4>Practical Usage Examples</h4><h5>Basic Setup for Zoneless Applications</h5><pre><code><code>import { bootstrapApplication } from '@angular/platform-browser';
import { provideCheckNoChangesConfig } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    provideCheckNoChangesConfig({
      exhaustive: true,
      interval: 5000  // Check every 5 seconds
    }),
    // ... other providers
  ]
});</code></code></pre><h5>Debugging OnPush Component Issues</h5><pre><code><code>@Component({
  selector: 'app-problematic',
  template: `&lt;div&gt;{{ computedValue }}&lt;/div&gt;`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProblematicComponent {
  private _counter = 0;
  
  get computedValue() {
    // &#9888;&#65039; This violates unidirectional data flow
    return ++this._counter;
  }
}

// Configuration to catch this issue
provideCheckNoChangesConfig({
  exhaustive: true  // Will detect the violation in OnPush components
})</code></code></pre><h5>Development vs Production Configuration</h5><pre><code><code>// app.config.ts
import { isDevMode } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    // Only enable in development
    ...(isDevMode() ? [
      provideCheckNoChangesConfig({
        exhaustive: true,
        interval: 3000
      })
    ] : []),
    // ... other providers
  ]
};</code></code></pre><p></p><h3><code>7. DOCUMENT</code> Token Moves to Core (<a href="https://github.com/angular/angular/pull/60663">#60663</a>)</h3><p>Previously, the <code>DOCUMENT</code> token was located in <code>@angular/common</code>, which created several issues:</p><ul><li><p><strong>Unnecessary dependency</strong>: applications needed to install <code>@angular/common</code> to access the document</p></li><li><p><strong>SSR complications</strong>: Server-side rendering scenarios often only need document access without the full common package</p></li><li><p><strong>Architectural inconsistency</strong>: a fundamental browser API token was placed in a package focused on common directives and pipes</p></li></ul><p>The <code>DOCUMENT</code> token now lives in <code>@angular/core</code> where it belongs, alongside other fundamental injection tokens and platform abstractions.</p><pre><code>// Before (Angular 19 and earlier)
import { DOCUMENT } from '@angular/common';
import { inject } from '@angular/core';

// After (Angular 20 - recommended)
import { DOCUMENT, inject } from '@angular/core';</code></pre><p></p><h3>8. Zoneless Change Detection: From Experimental to Developer Preview (<a href="https://github.com/angular/angular/pull/60748">#60748</a>)</h3><pre><code>bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(),
    // ... other providers
  ]
});</code></pre><p></p><h3><code>9. InjectFlags</code> Removal (<a href="https://github.com/angular/angular/pull/60318">#60318</a>)</h3><p><code>InjectFlags</code> controlled <strong>how</strong> and <strong>where</strong> Angular's dependency injection system would search for dependencies. They were essentially configuration options that modified the injection behaviour.</p><pre><code>// Only search up to the host component
const service = inject(MyService, InjectFlags.Host);</code></pre><p>Angular 20 removes the deprecated <code>InjectFlags</code> enum, completing the transition to a more modern, type-safe dependency injection API using object literals.</p><pre><code>// &#10060; No longer available in Angular 20
import { inject, InjectFlags } from '@angular/core';

// Before (deprecated approach)
const service = inject(MyService, InjectFlags.Optional | InjectFlags.Host);

// &#9989; Modern approach (Angular 20)
import { inject } from '@angular/core';

const service = inject(MyService, { optional: true, host: true });</code></pre><h4>Affected APIs</h4><p>All public injection APIs no longer accept <code>InjectFlags</code>:</p><ul><li><p><code>inject()</code> function</p></li><li><p><code>Injector.get()</code> method</p></li><li><p><code>EnvironmentInjector.get()</code> method</p></li><li><p><code>TestBed.get()</code> method</p></li><li><p><code>TestBed.inject()</code> method</p></li></ul><p></p><h3>10. Complete Removal of TestBed.get() (<a href="https://github.com/angular/angular/pull/60414/files">#60414</a>)</h3><pre><code>// TestBed.get() - Not type safe
const service = TestBed.get(MyService); // Returns 'any'
service.someMethod(); // No TypeScript checking, runtime errors possible

// TestBed.inject() - Fully type safe
const service = TestBed.inject(MyService); // Returns MyService
service.someMethod(); // TypeScript validates this exists</code></pre><p></p><h3>11. Task Management Made Stable: <code>PendingTasks</code> Injectable (<a href="https://github.com/angular/angular/pull/60716">#60716</a>)</h3><p><code>PendingTasks</code> is an Angular service that allows you to track ongoing asynchronous operations in your application. It's particularly valuable for:</p><ul><li><p><strong>Server-Side Rendering</strong>: Ensuring all async operations complete before serialization</p></li><li><p><strong>Testing</strong>: Waiting for all pending operations to finish</p></li><li><p><strong>Application State Management</strong>: Tracking when your app is "idle"</p></li><li><p><strong>Performance Monitoring</strong>: Understanding async operation lifecycle</p></li></ul><h4>What's Now Stable</h4><pre><code><code>import { PendingTasks } from '@angular/core';

@Component({...})
export class MyComponent {
  private pendingTasks = inject(PendingTasks);
  
  // &#9989; Stable API
  trackAsyncOperation() {
    const removeTask = this.pendingTasks.add();
    
    this.performAsyncWork().finally(() =&gt; {
      removeTask(); // Clean up when done
    });
  }
}</code></code></pre><h4>What Remains in Developer Preview</h4><pre><code><code>// &#9888;&#65039; Still in developer preview
this.pendingTasks.run(async () =&gt; {
  // Async work here
  await this.performAsyncWork();
});</code></code></pre><p>The <code>run()</code> method remains in developer preview due to ongoing questions about:</p><ul><li><p>Return value handling</p></li><li><p>Error handling strategies</p></li><li><p>Potential replacement with a more context-aware task API</p></li></ul><p></p><h3>12. Node.js Version Support Update (<a href="https://github.com/angular/angular/pull/60545">#60545</a>)</h3><p>Angular 20 updates its Node.js version requirements, dropping support for Node.js v18 (which reaches End-of-Life in April 2025) and establishing new minimum version requirements to ensure developers benefit from the latest Node.js features, security updates, and performance improvements.</p><pre><code>// Angular 20
{
  "engines": {
    "node": "^20.11.1 || &gt;=22.11.0"
  }
}</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!125U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!125U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 424w, https://substackcdn.com/image/fetch/$s_!125U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 848w, https://substackcdn.com/image/fetch/$s_!125U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 1272w, https://substackcdn.com/image/fetch/$s_!125U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!125U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png" width="732" height="131" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d986b84f-5146-4efa-8171-0edca6de61e1_732x131.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:131,&quot;width&quot;:732,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17592,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!125U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 424w, https://substackcdn.com/image/fetch/$s_!125U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 848w, https://substackcdn.com/image/fetch/$s_!125U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 1272w, https://substackcdn.com/image/fetch/$s_!125U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd986b84f-5146-4efa-8171-0edca6de61e1_732x131.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Version Support Matrix</figcaption></figure></div><p>&#9888;&#65039; You must check that your pipelines (CI/CD) work correctly with the new Node.js version.</p><pre><code># Install and use Node.js v20 LTS
nvm install 20
nvm use 20

# Or install Node.js v22 LTS
nvm install 22
nvm use 22

# Verify version
node --version</code></pre><pre><code>// package.json
{
  "name": "my-angular-app",
  "engines": {
    "node": "&gt;=20.11.1",
    "npm": "&gt;=10.0.0"
  }
}</code></pre><p></p><h3>13. Deprecate the <code>ngIf</code>/<code>ngFor</code>/<code>ngSwitch</code> structural directives (<a href="https://github.com/angular/angular/pull/60492">#60492</a>)</h3><pre><code>// &#10060; Deprecated in Angular 20, removal planned for Angular 22
*ngIf
*ngFor  
*ngSwitch, *ngSwitchCase, *ngSwitchDefault

// &#9989; Recommended: Control Flow Blocks
@if
@for
@switch, @case, @default</code></pre><p></p><h3>14. DOM Optimization: <code>ng-reflect</code> Attributes Removed by Default (<a href="https://github.com/angular/angular/pull/60973">#60973</a>)</h3><p><code>ng-reflect-*</code> attributes were originally introduced as a debugging aid to help developers understand what values Angular was binding to component properties and directives. They appeared in the DOM during development to show the current state of data bindings.</p><pre><code>&lt;!-- Example of ng-reflect attributes (old behavior) --&gt;
&lt;div my-directive [someProperty]="currentValue"&gt;
  &lt;!-- Angular would add: --&gt;
  &lt;!-- ng-reflect-some-property="current-value" --&gt;
&lt;/div&gt;

&lt;ng-template [ngIf]="showContent"&gt;
  &lt;!-- Angular would add: --&gt;
  &lt;!-- bindings={ "ng-reflect-ng-if": "true" } --&gt;
&lt;/ng-template&gt;

&lt;!-- Before: Complex components created verbose DOM --&gt;
&lt;my-component 
  [data]="complexObject"
  [config]="configuration"
  [options]="userOptions"&gt;
  
  &lt;!-- Multiple ng-reflect attributes made DOM hard to read --&gt;
  &lt;!-- ng-reflect-data="[object Object]" --&gt;
  &lt;!-- ng-reflect-config="[object Object]" --&gt;
  &lt;!-- ng-reflect-options="[object Object]" --&gt;
&lt;/my-component&gt;

&lt;!-- After: Clean, readable DOM --&gt;
&lt;my-component 
  [data]="complexObject"
  [config]="configuration"
  [options]="userOptions"&gt;
  &lt;!-- Clean HTML for better developer experience --&gt;
&lt;/my-component&gt;</code></pre><p>If you want to restore the behaviour use <code>provideNgReflectAttributes()</code>:</p><pre><code>// To restore ng-reflect behavior (development only)
import { bootstrapApplication } from '@angular/platform-browser';
import { provideNgReflectAttributes } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    provideNgReflectAttributes(), // Enables ng-reflect in dev mode
    // ... other providers
  ]
});</code></pre><p></p><h3>15. Chrome DevTools Performance Integration (<a href="https://github.com/angular/angular/pull/60789">#60789</a>)</h3><p>Angular 20 adds the <code>ng.enableProfiling()</code> global utility that exposes Angular's internal performance data to Chrome DevTools, creating a dedicated Angular track in the Performance timeline alongside other browser metrics.</p><h4>Unified Performance Analysis</h4><ul><li><p><strong>Single Tool</strong>: No more switching between Angular DevTools and Chrome DevTools</p></li><li><p><strong>Correlated Data</strong>: See Angular events in context with other browser performance metrics</p></li><li><p><strong>Production Debugging</strong>: Works with minified production code</p></li></ul><h4>Angular-Specific Insights</h4><ul><li><p>Component rendering lifecycle</p></li><li><p>Change detection cycles</p></li><li><p>Event listener execution</p></li><li><p>Component instantiation</p></li><li><p>Provider instantiation</p></li><li><p>Dependency injection profiling</p></li></ul><h4>Visual Indicators</h4><ul><li><p><strong>Color-coded entries</strong> to distinguish between:</p><ul><li><p>Developer-authored TypeScript code</p></li><li><p>Angular compiler-generated code</p></li></ul></li><li><p><strong>Dedicated Angular track</strong> at the bottom of the performance timeline</p></li></ul><p></p><h2>&#128293; @angular/common</h2><h3>1. NgTemplateOutlet Accepts Undefined Inputs (<a href="https://github.com/angular/angular/pull/61404">#61404</a>)</h3><p>Angular 20 improves the <code>NgTemplateOutlet</code> directive by accepting <code>undefined</code> inputs alongside <code>null</code>, addressing a long-standing TypeScript compatibility issue and making the directive more ergonomic to use with modern Angular patterns like signals and ViewChild.</p><pre><code>// Before (Angular 19 and earlier)
export class NgTemplateOutlet&lt;C = unknown&gt; {
  @Input() ngTemplateOutlet: TemplateRef&lt;C&gt; | null = null;
  @Input() ngTemplateOutletContext: C | null = null;
  @Input() ngTemplateOutletInjector: Injector | null = null;
}

// After (Angular 20)
export class NgTemplateOutlet&lt;C = unknown&gt; {
  @Input() ngTemplateOutlet: TemplateRef&lt;C&gt; | null | undefined = null;
  @Input() ngTemplateOutletContext: C | null | undefined = null;
  @Input() ngTemplateOutletInjector: Injector | null | undefined = null;
}

// undefined: represents non-existence, state of being unset
let template: TemplateRef | undefined; // Not initialized
if (template) { /* use template */ } // Natural check

// null: represents an explicit value of "nothingness"
let template: TemplateRef | null = null; // Explicitly set to null
if (template !== null) { /* use template */ } // Explicit null check needed

// Before: Required nullish coalescing or type assertion
@Component({
  template: `
    &lt;ng-template #myTemplate&gt;Content&lt;/ng-template&gt;
    &lt;ng-container [ngTemplateOutlet]="template ?? null"&gt;&lt;/ng-container&gt;
  `
})
export class MyComponent {
  @ViewChild('myTemplate') template?: TemplateRef&lt;any&gt;;
  
  // Had to use ?? null to satisfy type checker
  get templateOrNull() {
    return this.template ?? null;
  }
}

// After: Direct usage without type gymnastics
@Component({
  template: `
    &lt;ng-template #myTemplate&gt;Content&lt;/ng-template&gt;
    &lt;ng-container [ngTemplateOutlet]="template"&gt;&lt;/ng-container&gt;
  `
})
export class MyComponent {
  @ViewChild('myTemplate') template?: TemplateRef&lt;any&gt;;
  
  // Works directly - no type conversion needed!
}</code></pre><p></p><h3>2. ViewportScroller with ScrollOptions Support (<a href="https://github.com/angular/angular/pull/61002">#61002</a>)</h3><pre><code>// Before (Angular 19 and earlier)
abstract class ViewportScroller {
  abstract scrollToPosition(position: [number, number]): void;
  abstract scrollToAnchor(anchor: string): void;
}

// After (Angular 20)
abstract class ViewportScroller {
  abstract scrollToPosition(position: [number, number], options?: ScrollOptions): void;
  abstract scrollToAnchor(anchor: string, options?: ScrollOptions): void;
}</code></pre><h4>ScrollOptions Interface</h4><pre><code>interface ScrollOptions {
  behavior?: 'auto' | 'instant' | 'smooth';
  block?: 'start' | 'center' | 'end' | 'nearest';
  inline?: 'start' | 'center' | 'end' | 'nearest';
}</code></pre><pre><code>@Component({
  selector: 'app-landing-page',
  template: `
    &lt;nav class="fixed-header"&gt;
      &lt;a (click)="navigateToSection('hero')"&gt;Home&lt;/a&gt;
      &lt;a (click)="navigateToSection('about')"&gt;About&lt;/a&gt;
      &lt;a (click)="navigateToSection('services')"&gt;Services&lt;/a&gt;
      &lt;a (click)="navigateToSection('contact')"&gt;Contact&lt;/a&gt;
    &lt;/nav&gt;

    &lt;section id="hero" class="hero-section"&gt;...&lt;/section&gt;
    &lt;section id="about" class="about-section"&gt;...&lt;/section&gt;
    &lt;section id="services" class="services-section"&gt;...&lt;/section&gt;
    &lt;section id="contact" class="contact-section"&gt;...&lt;/section&gt;
  `
})
export class LandingPageComponent {
  private viewportScroller = inject(ViewportScroller);

  navigateToSection(sectionId: string) {
    this.viewportScroller.scrollToAnchor(sectionId, {
      behavior: 'smooth',
      block: 'start' // Align to top of viewport
    });
  }
}</code></pre><p></p><h3>3. Suspicious Date Pattern Validation (<a href="https://github.com/angular/angular/pull/59798">#59798</a>)</h3><p>Angular 20 introduces runtime validation for date formatting patterns in development mode, helping developers catch common mistakes that lead to incorrect date displays. This enhancement specifically targets the misuse of week-based year patterns that cause subtle but critical date formatting errors.</p><h4>Common Mistake: <code>Y</code> vs <code>y</code></h4><pre><code><code>// &#10060; WRONG: Using week-based year (Y) instead of calendar year (y)
formatDate(new Date('2024-12-31'), 'YYYY-MM-dd', 'en');
// Returns: "2025-12-31" (INCORRECT!)

// &#9989; CORRECT: Using calendar year (y)
formatDate(new Date('2024-12-31'), 'yyyy-MM-dd', 'en');
// Returns: "2024-12-31" (CORRECT!)</code></code></pre><h4>Why This Happens</h4><p>Week-based year (<code>Y</code>) differs from calendar year (<code>y</code>) for a few days around January 1st:</p><pre><code><code>// Week-based year examples (using Y)
formatDate('2024-01-01', 'YYYY', 'en'); // Returns "2024" &#10003;
formatDate('2024-12-30', 'YYYY', 'en'); // Returns "2025" &#10060; Wrong!
formatDate('2024-12-31', 'YYYY', 'en'); // Returns "2025" &#10060; Wrong!

// Calendar year examples (using y)
formatDate('2024-01-01', 'yyyy', 'en'); // Returns "2024" &#10003;
formatDate('2024-12-30', 'yyyy', 'en'); // Returns "2024" &#10003;
formatDate('2024-12-31', 'yyyy', 'en'); // Returns "2024" &#10003;</code></code></pre><h4>Development Mode Error Detection</h4><pre><code>// Angular 20 now throws errors for suspicious patterns in development
import { formatDate } from '@angular/common';

// &#10060; This will throw an error in development mode
try {
  formatDate(new Date(), 'YYYY/MM/dd', 'en');
} catch (error) {
  console.error(error.message);
  // "Suspicious use of week-based year "Y" in date pattern "YYYY/MM/dd". 
  //  Did you mean to use calendar year "y" instead?"
}

// &#9989; This works correctly
formatDate(new Date(), 'yyyy/MM/dd', 'en'); // No error</code></pre><h4>When Validation Triggers</h4><p>Angular validates patterns and throws errors for:</p><ol><li><p><strong>Week-based year without week indicator</strong>: Using <code>Y</code> without <code>w</code></p></li><li><p><strong>Mixed date components</strong>: Using day-of-year <code>D</code> with month <code>M</code></p></li></ol><pre><code><code>// &#10060; Will throw error - week-based year without week
formatDate(date, 'YYYY-MM-dd', 'en');

// &#9989; Valid - week-based year WITH week indicator
formatDate(date, `YYYY 'W'ww`, 'en'); // "2024 W52"

// &#9989; Valid - calendar year (most common use case)
formatDate(date, 'yyyy-MM-dd', 'en'); // "2024-12-31"</code></code></pre><h4>Practical Example</h4><pre><code>@Component({
  selector: 'app-date-display',
  template: `
    &lt;div class="date-info"&gt;
      &lt;!-- &#10060; Before: Potential bug with Y --&gt;
      &lt;!-- &lt;p&gt;Date: {{ currentDate | date:'YYYY-MM-dd' }}&lt;/p&gt; --&gt;
      
      &lt;!-- &#9989; After: Correct calendar year --&gt;
      &lt;p&gt;Date: {{ currentDate | date:'yyyy-MM-dd' }}&lt;/p&gt;
      
      &lt;!-- &#9989; Valid: Week-based year with week --&gt;
      &lt;p&gt;Week: {{ currentDate | date:`YYYY 'W'ww` }}&lt;/p&gt;
    &lt;/div&gt;
  `
})
export class DateDisplayComponent {
  currentDate = new Date();
  
  // &#10060; This method would throw in development
  // getFormattedDate() {
  //   return formatDate(this.currentDate, 'YYYY-MM-dd', 'en');
  // }
  
  // &#9989; Correct implementation
  getFormattedDate() {
    return formatDate(this.currentDate, 'yyyy-MM-dd', 'en');
  }
}</code></pre><p></p><h2>&#128293; @angular/compiler</h2><h3>1. @for Track Function Diagnostics (<a href="https://github.com/angular/angular/pull/60495">#60495</a>)</h3><p>Angular 20 introduces a new extended diagnostic that warns developers when track functions in <code>@for</code> loops are not properly invoked. This compile-time validation helps prevent performance issues when track functions are referenced but not called, causing unnecessary DOM recreation.</p><pre><code>@Component({
  template: `
    &lt;!-- &#10060; WRONG: Track function not invoked --&gt;
    @for (item of items; track trackByName) {
      &lt;div&gt;{{ item.name }}&lt;/div&gt;
    }
  `
})
export class ListComponent {
  items = [{ name: 'Alice' }, { name: 'Bob' }];
  
  trackByName(item: any) {
    return item.name; // This function is never called!
  }
}</code></pre><h4>Compile-Time Warning</h4><pre><code><code>// Angular 20 now shows a warning during compilation:
// Error: The track function in the @for block should be invoked: trackByName(/* arguments */)

@Component({
  template: `
    &lt;!-- &#10060; This triggers the diagnostic --&gt;
    @for (item of items; track trackByName) {
      &lt;div&gt;{{ item.name }}&lt;/div&gt;
    }
  `
})
export class DiagnosticExample {
  trackByName(item: any) {
    return item.name;
  }
}

// Error Code: 8115 - UNINVOKED_TRACK_FUNCTION
// Extended Diagnostic Name: uninvokedTrackFunction
// Category: Warning
// Message: "The track function in the @for block should be invoked: trackByName(/* arguments */)"</code></code></pre><p></p><h3>2. Void and Exponentiation Operators Support in Templates (<a href="https://github.com/angular/angular/pull/59894">#59894</a>)</h3><p>When a method returns <code>false</code> in an event handler, it can unintentionally prevent the default event behavior (similar to calling <code>preventDefault()</code>). The <code>void</code> operator ensures the expression always returns <code>undefined</code>, avoiding this issue.</p><h4>Examples:</h4><h5><strong>In Host Bindings:</strong></h5><pre><code><code>@Directive({
  selector: '[trackClicks]',
  host: { 
    '(mousedown)': 'void handleMousedown($event)',
    '(click)': 'void logClick($event)'
  }
})
export class ClickTrackerDirective {
  handleMousedown(event: MouseEvent) {
    console.log('Mouse down tracked');
    return false; // Won't prevent default behavior due to void
  }
  
  logClick(event: MouseEvent) {
    console.log('Click tracked');
    // Any return value is ignored
  }
}</code></code></pre><h5><strong>In Template Event Bindings:</strong></h5><pre><code><code>@Component({
  template: `
    &lt;button (click)="void saveData()"&gt;Save&lt;/button&gt;
    &lt;form (submit)="void handleSubmit($event)"&gt;
      &lt;!-- form content --&gt;
    &lt;/form&gt;
  `
})
export class MyComponent {
  saveData() {
    // Save logic here
    return false; // Won't affect event propagation
  }
  
  handleSubmit(event: Event) {
    // Handle form submission
    console.log('Form submitted');
  }
}</code></code></pre><p>Angular 20 adds support for the exponentiation operator (<code>**</code>) in template expressions, bringing mathematical operations in templates closer to standard JavaScript.</p><h4>Examples:</h4><h5><strong>Basic Mathematical Calculations:</strong></h5><pre><code>@Component({
  template: `
    &lt;div&gt;
      &lt;p&gt;2 to the power of 3: {{2 ** 3}}&lt;/p&gt;
      &lt;p&gt;Base squared: {{base ** 2}}&lt;/p&gt;
      &lt;p&gt;Scientific notation: {{10 ** -3}}&lt;/p&gt;
      &lt;p&gt;Complex calculation: {{(value + 1) ** exponent}}&lt;/p&gt;
    &lt;/div&gt;
  `
})
export class MathComponent {
  base = 5;
  value = 4;
  exponent = 3;
}</code></pre><p></p><h3>3. Tagged Template Literals Support (<a href="https://github.com/angular/angular/pull/59947">#59947</a>)</h3><p>Tagged template literals allow you to parse template literals with a function. The tag function receives the string parts and interpolated values separately, giving you complete control over how the final string is constructed.</p><pre><code>@Component({
  template: `
    &lt;div&gt;No interpolations: {{ tag\`hello world\` }}&lt;/div&gt;
    &lt;span&gt;With interpolations: {{ greet\`Hello \${name}, it's \${timeOfDay}!\` }}&lt;/span&gt;
    &lt;p&gt;With pipe: {{ format\`Welcome \${username}\` | uppercase }}&lt;/p&gt;
  `
})
export class MyComponent {
  name = 'Alice';
  timeOfDay = 'morning';
  username = 'developer';

  // Simple tag function
  tag = (strings: TemplateStringsArray, ...args: any[]) =&gt; {
    return strings.join('') + ' (processed)';
  };

  // Tag function with interpolation processing
  greet = (strings: TemplateStringsArray, name: string, time: string) =&gt; {
    return `${strings[0]}${name.toUpperCase()}${strings[1]}${time}${strings[2]}`;
  };

  // Formatting tag function
  format = (strings: TemplateStringsArray, ...args: string[]) =&gt; {
    return strings.reduce((result, string, i) =&gt; {
      return result + string + (args[i] ? `**${args[i]}**` : '');
    }, '');
  };
}</code></pre><p></p><h3>4. Support the in keyword in Binary expression (<a href="https://github.com/angular/angular/pull/58432">#58432</a>)</h3><p>The <code>in</code> operator returns <code>true</code> if a specified property exists in an object or its prototype chain. It's a fundamental JavaScript operator that's now available in Angular templates with full type safety.</p><pre><code>@Component({
  template: `
    &lt;div&gt;{{ 'name' in user ? 'Has name' : 'No name' }}&lt;/div&gt;
    &lt;div&gt;{{ 'email' in user ? user.email : 'No email provided' }}&lt;/div&gt;
    &lt;div&gt;{{ 'admin' in permissions ? 'Admin user' : 'Regular user' }}&lt;/div&gt;
  `
})
export class UserProfileComponent {
  user = {
    name: 'Alice',
    email: 'alice@example.com'
  };
  
  permissions = {
    read: true,
    write: true,
    admin: false
  };
}</code></pre><p></p><h2>&#128293; @angular/compiler-cli</h2><h3>1. Enhanced Template Expression Diagnostics: Unparenthesized Nullish Coalescing (<a href="https://github.com/angular/angular/pull/60279">#60279</a>)</h3><p>Angular 20 introduces a new extended diagnostic (<code>NG8114</code>) that helps developers write more robust template expressions by detecting potentially ambiguous operator precedence when mixing nullish coalescing (<code>??</code>) with logical operators (<code>&amp;&amp;</code> and <code>||</code>).</p><h4>What is the Unparenthesized Nullish Coalescing Diagnostic?</h4><p>This diagnostic identifies cases where the nullish coalescing operator (<code>??</code>) is used alongside logical AND (<code>&amp;&amp;</code>) or logical OR (<code>||</code>) operators without parentheses to clarify precedence. This pattern can lead to confusion and unexpected behavior, as the operator precedence may not be immediately obvious to developers.</p><h4>Why This Matters</h4><p>In JavaScript and TypeScript, mixing these operators without parentheses is considered an error because it creates ambiguous expressions. Angular templates have historically allowed this pattern, but it can lead to bugs and maintenance issues.</p><pre><code>@Component({
  template: `
    &lt;!-- Ambiguous: Is it (hasPermission() &amp;&amp; task()?.disabled) ?? true 
         or hasPermission() &amp;&amp; (task()?.disabled ?? true)? --&gt;
    &lt;button [disabled]="hasPermission() &amp;&amp; task()?.disabled ?? true"&gt;
      Run Task
    &lt;/button&gt;
    
    &lt;!-- Another ambiguous case --&gt;
    &lt;div&gt;{{ name || user?.name ?? 'Anonymous' }}&lt;/div&gt;
  `
})
export class ProblematicComponent {
  hasPermission = input(false);
  task = input&lt;Task | undefined&gt;(undefined);
  name = '';
  user = input&lt;User | null&gt;(null);
}</code></pre><h4>The Solution: Clear Parentheses</h4><p>Always use parentheses to explicitly define the intended order of operations:</p><pre><code>@Component({
  template: `
    &lt;!-- &#10060; Ambiguous precedence --&gt;
    &lt;div class="error" *ngIf="form.invalid &amp;&amp; field.touched ?? showAllErrors"&gt;
      Field is required
    &lt;/div&gt;
    
    &lt;!-- &#9989; Clear intent: Show error if form invalid AND (field touched OR show all) --&gt;
    &lt;div class="error" *ngIf="form.invalid &amp;&amp; (field.touched ?? showAllErrors)"&gt;
      Field is required
    &lt;/div&gt;
    
    &lt;!-- &#9989; Alternative: Show error if (form invalid AND field touched) OR show all --&gt;
    &lt;div class="error" *ngIf="(form.invalid &amp;&amp; field.touched) ?? showAllErrors"&gt;
      Field is required (alternative logic)
    &lt;/div&gt;
  `
})
export class FormValidationComponent {
  form = inject(FormBuilder).group({
    field: ['', Validators.required]
  });
  
  get field() { return this.form.get('field')!; }
  showAllErrors = signal(false);
}</code></pre><p></p><h3>2. Enhanced Template Diagnostics: Missing Structural Directive Detection (<a href="https://github.com/angular/angular/pull/59443">#59443</a>)</h3><p>Angular 20 introduces a new extended diagnostic (<code>NG8116</code>) that helps developers identify missing imports for custom structural directives in standalone components. This diagnostic prevents runtime errors and improves the developer experience when working with custom structural directives.</p><h4>What is the Missing Structural Directive Diagnostic?</h4><p>This diagnostic detects when a standalone component uses custom structural directives (like <code>*select</code>, <code>*featureFlag</code>, or <code>*permission</code>) in its template without importing the corresponding directive. This helps catch import oversights that would otherwise cause runtime failures.</p><pre><code>@Component({
  // &#9989; Proper imports for custom structural directives
  imports: [SelectDirective, FeatureFlagDirective],
  template: `
    &lt;div *select="let item from items"&gt;
      {{ item.name }}
    &lt;/div&gt;
    
    &lt;section *featureFlag="'newDashboard'"&gt;
      &lt;new-dashboard /&gt;
    &lt;/section&gt;
  `
})
export class MyComponent {
  items = [{ name: 'Item 1' }, { name: 'Item 2' }];
}</code></pre><p></p><h3>3. Enhanced Type Checking for Host Bindings (<a href="https://github.com/angular/angular/pull/60267">#60267</a>)</h3><p>Previously, Angular's type checking was limited to component templates. Now, host bindings in directives and components receive full type checking support, including:</p><ul><li><p><strong>Host object literals</strong> in <code>@Component</code> and <code>@Directive</code> decorators</p></li><li><p><code>@HostBinding</code><strong> decorator</strong> expressions</p></li><li><p><code>@HostListener</code><strong> decorator</strong> expressions</p></li><li><p><strong>IDE integration</strong> with hover information, autocomplete, and renaming support</p></li></ul><p>This feature is controlled by the <code>typeCheckHostBindings</code> compiler flag (<strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7370739947768811520?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7370739947768811520%2C7370768348692787200%29&amp;replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7370739947768811520%2C7371296523692883968%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287370768348692787200%2Curn%3Ali%3Aactivity%3A7370739947768811520%29&amp;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287371296523692883968%2Curn%3Ali%3Aactivity%3A7370739947768811520%29">this will be enabled by default in Angular 21</a></strong>):</p><pre><code>{
  "angularCompilerOptions": {
    "strictTemplates": true,
    "typeCheckHostBindings": true
  }
}</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_IzA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_IzA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 424w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 848w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 1272w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_IzA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif" width="919" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:919,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1885421,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_IzA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 424w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 848w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 1272w, https://substackcdn.com/image/fetch/$s_!_IzA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F057fe2af-e453-4c09-94d0-e52e9619f60e_919x424.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>&#128293; @angular/forms</h2><h3>1. Enhanced Form Control Management: <code>markAllAsDirty</code> Method (<a href="https://github.com/angular/angular/pull/58663">#58663</a>)</h3><p>The <code>markAllAsDirty</code> method recursively marks a form control and all of its child controls as dirty. This is particularly useful when you need to trigger validation display for an entire form or form section at once.</p><pre><code>import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';

@Component({
  template: `
    &lt;input [formControl]="emailControl" placeholder="Email"&gt;
    &lt;div *ngIf="emailControl.dirty &amp;&amp; emailControl.invalid" class="error"&gt;
      Email is required and must be valid
    &lt;/div&gt;
    &lt;button (click)="markDirty()"&gt;Mark as Dirty&lt;/button&gt;
    &lt;button (click)="reset()"&gt;Reset&lt;/button&gt;
    
    &lt;div class="status"&gt;
      &lt;p&gt;Dirty: {{ emailControl.dirty }}&lt;/p&gt;
      &lt;p&gt;Valid: {{ emailControl.valid }}&lt;/p&gt;
    &lt;/div&gt;
  `
})
export class FormControlExample {
  emailControl = new FormControl('', [
    Validators.required,
    Validators.email
  ]);
  
  markDirty(): void {
    this.emailControl.markAllAsDirty();
  }
  
  reset(): void {
    this.emailControl.reset();
  }
}</code></pre><p>Comparison with Existing Methods:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7IYB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7IYB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 424w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 848w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 1272w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7IYB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png" width="822" height="155" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:155,&quot;width&quot;:822,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36788,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7IYB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 424w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 848w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 1272w, https://substackcdn.com/image/fetch/$s_!7IYB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92672a5c-f82a-449e-ad63-0996ee7f6f96_822x155.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><h3>2. Enhanced Form Reset Controls: Silent Reset Option (<a href="https://github.com/angular/angular/pull/60354">#60354</a>)</h3><p>The <code>resetForm</code> method in <code>FormGroupDirective</code> now accepts an optional parameter that gets passed to the underlying <code>FormGroup.reset()</code> method, allowing you to control event emission during reset operations.</p><h4>The Problem</h4><p>Previously, resetting a form would always trigger change events, which could cause unwanted side effects:</p><pre><code>@Component({
  template: `
    &lt;form #formDir="ngForm" [formGroup]="userForm"&gt;
      &lt;input formControlName="name" placeholder="Name"&gt;
      &lt;input formControlName="email" placeholder="Email"&gt;
      &lt;button type="submit"&gt;Submit&lt;/button&gt;
      &lt;button type="button" (click)="resetForm(formDir)"&gt;Reset&lt;/button&gt;
    &lt;/form&gt;
    
    &lt;div class="debug"&gt;
      &lt;p&gt;Value changes count: {{ valueChangesCount }}&lt;/p&gt;
      &lt;p&gt;Form submitted: {{ formDir.submitted }}&lt;/p&gt;
    &lt;/div&gt;
  `
})
export class ProblematicComponent {
  userForm: FormGroup;
  valueChangesCount = 0;
  
  constructor(private fb: FormBuilder) {
    this.userForm = this.fb.group({
      name: [''],
      email: ['']
    });
    
    // This subscription would fire unnecessarily during reset
    this.userForm.valueChanges.subscribe(() =&gt; {
      this.valueChangesCount++;
      // Expensive operations triggered on every change
      this.performExpensiveCalculation();
    });
  }
  
  resetForm(formDir: FormGroupDirective): void {
    // &#10060; Old behavior: Always emits events
    formDir.resetForm();
    // This would increment valueChangesCount unnecessarily
  }
  
  private performExpensiveCalculation(): void {
    // Some expensive operation that shouldn't run during reset
    console.log('Expensive calculation triggered');
  }
}</code></pre><h4>The Solution</h4><p>Now you can reset forms without triggering change events:</p><pre><code>resetFormSilent(formDir: FormGroupDirective): void {
  // &#9989; New behavior: Reset without emitting events
  formDir.resetForm(undefined, { emitEvent: false });
  // valueChangesCount won't increment
}</code></pre><p></p><h2>&#128293; @angular/http</h2><h3>1. HTTP Client Keep-Alive Support for Fetch Requests (<a href="https://github.com/angular/angular/pull/60621/">#60621</a>)</h3><p>The <code>keepalive</code> option, when set to <code>true</code>, instructs the browser to keep the request alive even if the page that initiated it is unloaded. This is particularly useful for sending final analytics data, logging events, or performing cleanup operations when users navigate away from or close a page.</p><p>Keep-alive support requires using the Fetch API backend. Make sure to configure your application with <code>withFetch()</code>:</p><pre><code>import { provideHttpClient, withFetch } from '@angular/common/http';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withFetch()), // Required for keepalive support
    // other providers...
  ]
});</code></pre><h4>Simple GET Request with Keep-Alive:</h4><pre><code>import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-analytics',
  template: `
    &lt;button (click)="sendAnalytics()"&gt;Send Analytics&lt;/button&gt;
    &lt;button (click)="trackPageView()"&gt;Track Page View&lt;/button&gt;
  `
})
export class AnalyticsComponent {
  private http = inject(HttpClient);
  
  sendAnalytics(): void {
    // This request will persist even if the page is unloaded
    this.http.get('/api/analytics/session-end', { 
      keepalive: true 
    }).subscribe({
      next: (response) =&gt; console.log('Analytics sent:', response),
      error: (error) =&gt; console.error('Analytics failed:', error)
    });
  }
  
  trackPageView(): void {
    const pageData = {
      url: window.location.href,
      timestamp: Date.now(),
      userAgent: navigator.userAgent
    };
    
    this.http.post('/api/analytics/pageview', pageData, {
      keepalive: true,
      headers: { 'Content-Type': 'application/json' }
    }).subscribe();
  }
}</code></pre><p></p><h2>&#128293; @angular/platform-browser</h2><h3>1. Deprecate the platform-browser-dynamic package (<a href="https://github.com/angular/angular/pull/61043">#61043</a>)</h3><p>The <code>@angular/platform-browser-dynamic</code> package is now deprecated. All its functionality has been moved to <code>@angular/platform-browser</code>, providing a unified package for browser platform operations.</p><pre><code>// &#10060; Deprecated approach
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

// &#9989; New recommended approach
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

platformBrowser()
  .bootstrapModule(AppModule)
  .catch(err =&gt; console.error(err));</code></pre><p></p><h3>2. Deprecate the HammerJS integration (<a href="https://github.com/angular/angular/pull/60257">#60257</a>)</h3><h2>What's Being Deprecated</h2><p>The following HammerJS-related APIs and providers are now deprecated:</p><ul><li><p><code>HammerGestureConfig</code></p></li><li><p><code>HAMMER_GESTURE_CONFIG</code> token</p></li><li><p><code>HammerModule</code></p></li><li><p>Built-in HammerJS gesture directives</p></li><li><p>Automatic HammerJS gesture recognition</p></li></ul><h2>Why This Change?</h2><ol><li><p><strong>Native Browser Support</strong>: Modern browsers provide comprehensive touch and gesture APIs</p></li><li><p><strong>Bundle Size Reduction</strong>: Removing HammerJS dependency reduces application bundle size</p></li><li><p><strong>Performance</strong>: Native browser events are more performant than library-based solutions</p></li><li><p><strong>Maintenance</strong>: Reduces Angular's dependency on external libraries</p></li></ol><p></p><h2>&#128293; @angular/platform-server</h2><h3>1. Platform Server Testing Entry Point Deprecation (<a href="https://github.com/angular/angular/pull/60915">#60915</a>)</h3><p>The following APIs from <code>@angular/platform-server/testing</code> are now deprecated:</p><ul><li><p><code>platformServerTesting</code></p></li><li><p><code>ServerTestingModule</code></p></li><li><p>All related testing utilities for server-side rendering</p></li></ul><h2>Why This Change?</h2><ol><li><p><strong>Better Testing Practices</strong>: E2E tests provide more realistic SSR verification</p></li><li><p><strong>Real Environment Testing</strong>: E2E tests run in actual browser/server environments</p></li><li><p><strong>Simplified Maintenance</strong>: Reduces complexity in the platform-server package</p></li><li><p><strong>More Accurate Results</strong>: Tests actual SSR behavior rather than mocked environments</p></li></ol><pre><code>// &#10060; Deprecated approach
import { TestBed } from '@angular/core/testing';
import { platformServerTesting, ServerTestingModule } from '@angular/platform-server/testing';
import { AppComponent } from './app.component';

describe('AppComponent SSR', () =&gt; {
  beforeEach(() =&gt; {
    TestBed.initTestEnvironment(
      ServerTestingModule,
      platformServerTesting()
    );
    
    TestBed.configureTestingModule({
      declarations: [AppComponent]
    });
  });

  it('should render on server', () =&gt; {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    expect(fixture.nativeElement.textContent).toContain('Hello');
  });
});</code></pre><p>You can use Cypress, Playwright, Jest + Puppeteer and other configurations.</p><p></p><h2>&#128293; @angular/router</h2><h3>1. Add ability to directly abort a navigation (<a href="https://github.com/angular/angular/pull/60380">#60380</a>)</h3><p>The <code>Navigation</code> interface now includes an <code>abort()</code> method that allows you to cancel an ongoing navigation before it completes. This is particularly useful for scenarios where you need to cancel navigations based on user actions, external events, or application state changes.</p><pre><code>// Aborting current navigation

import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-navigation-control',
  template: `
    &lt;div class="navigation-controls"&gt;
      &lt;button (click)="startNavigation()"&gt;Start Navigation&lt;/button&gt;
      &lt;button (click)="abortNavigation()" [disabled]="!isNavigating"&gt;
        Abort Navigation
      &lt;/button&gt;
      &lt;p&gt;Status: {{ navigationStatus }}&lt;/p&gt;
    &lt;/div&gt;
  `
})
export class NavigationControlComponent {
  private router = inject(Router);
  
  isNavigating = false;
  navigationStatus = 'Ready';
  
  async startNavigation(): Promise&lt;void&gt; {
    this.isNavigating = true;
    this.navigationStatus = 'Navigating...';
    
    try {
      const result = await this.router.navigate(['/slow-loading-page']);
      this.navigationStatus = result ? 'Navigation completed' : 'Navigation failed';
    } catch (error) {
      this.navigationStatus = 'Navigation error';
    } finally {
      this.isNavigating = false;
    }
  }
  
  abortNavigation(): void {
    const currentNavigation = this.router.getCurrentNavigation();
    if (currentNavigation) {
      currentNavigation.abort();
      this.navigationStatus = 'Navigation aborted';
      this.isNavigating = false;
    }
  }
}</code></pre><p></p><h3>2. Asynchronous Router Redirects (<a href="https://github.com/angular/angular/pull/60863">#60863</a>)</h3><p>The <code>RedirectFunction</code> type now supports returning <code>MaybeAsync&lt;string | UrlTree&gt;</code>, which means redirect functions can return:</p><ul><li><p>Synchronous values: <code>string | UrlTree</code></p></li><li><p>Promises: <code>Promise&lt;string | UrlTree&gt;</code></p></li><li><p>Observables: <code>Observable&lt;string | UrlTree&gt;</code></p></li></ul><pre><code>// Synchronous Redirects (Existing)
const routes: Routes = [
  {
    path: 'old-page',
    redirectTo: '/new-page'  // Static redirect
  },
  {
    path: 'user/:id',
    redirectTo: (route) =&gt; `/profile/${route.params['id']}`  // Sync function
  }
];

// Asynchronous Redirects (New)
import { inject } from '@angular/core';
import { Routes } from '@angular/router';
import { UserService } from './user.service';

const routes: Routes = [
  {
    path: 'dashboard',
    redirectTo: async (route) =&gt; {
      const userService = inject(UserService);
      const user = await userService.getCurrentUser();
      
      if (user.isAdmin) {
        return '/admin/dashboard';
      } else {
        return '/user/dashboard';
      }
    }
  },
  {
    path: 'profile',
    redirectTo: (route) =&gt; {
      const userService = inject(UserService);
      
      // Return Observable
      return userService.getCurrentUser().pipe(
        map(user =&gt; user.isActive ? '/profile/active' : '/profile/inactive')
      );
    }
  }
];</code></pre><p></p><h3>3. Allow resolvers to read resolved data from ancestors (<a href="https://github.com/angular/angular/pull/59860">#59860</a>)</h3><p>Previously, resolvers could only access their own resolved data. Now, child resolvers can read resolved data from any ancestor route in the route tree, enabling better data composition and reducing redundant API calls.</p><pre><code>// Example: Accessing Parent Resolver Data

import { ActivatedRouteSnapshot } from '@angular/router';
import { inject } from '@angular/core';

const routes: Routes = [
  {
    path: 'company/:id',
    resolve: {
      company: (route: ActivatedRouteSnapshot) =&gt; {
        const dataService = inject(DataService);
        return dataService.getCompany(route.params['id']);
      }
    },
    children: [
      {
        path: 'departments',
        resolve: {
          // Child resolver can access parent's resolved data
          departments: (route: ActivatedRouteSnapshot) =&gt; {
            const dataService = inject(DataService);
            const company = route.data['company']; // Access parent's resolved company data
            return dataService.getDepartments(company.id);
          }
        },
        component: DepartmentsComponent,
        children: [
          {
            path: ':deptId/employees',
            resolve: {
              // Grandchild resolver can access both parent and grandparent data
              employees: (route: ActivatedRouteSnapshot) =&gt; {
                const dataService = inject(DataService);
                const company = route.data['company'];      // From grandparent
                const departments = route.data['departments']; // From parent
                const deptId = route.params['deptId'];
                
                return dataService.getEmployees(company.id, deptId);
              }
            },
            component: EmployeesComponent
          }
        ]
      }
    ]
  }
];</code></pre><p></p><h3>4. Support custom elements for RouterLink (<a href="https://github.com/angular/angular/pull/60290">#60290</a>)</h3><p>When <code>RouterLink</code> is used on a registered custom element, Angular now checks if the custom element's <code>observedAttributes</code> static property includes <code>'href'</code>. If it does, the element is treated as a navigational element similar to native <code>&lt;a&gt;</code> tags, with proper href updates and accessibility handling.</p><pre><code>import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-navigation',
  standalone: true,
  imports: [RouterModule],
  template: `
    &lt;nav class="custom-navigation"&gt;
      &lt;h2&gt;Custom Element Navigation&lt;/h2&gt;
      
      &lt;!-- Custom elements with RouterLink - href will be automatically managed --&gt;
      &lt;custom-link routerLink="/home" routerLinkActive="active"&gt;
        Home
      &lt;/custom-link&gt;
      
      &lt;custom-link routerLink="/products" routerLinkActive="active"&gt;
        Products
      &lt;/custom-link&gt;
      
      &lt;custom-link routerLink="/about" routerLinkActive="active"&gt;
        About
      &lt;/custom-link&gt;
      
      &lt;custom-link 
        routerLink="/contact" 
        routerLinkActive="active"
        [routerLinkActiveOptions]="{ exact: true }"&gt;
        Contact
      &lt;/custom-link&gt;
      
      &lt;!-- Disabled custom link --&gt;
      &lt;custom-link disabled&gt;
        Coming Soon
      &lt;/custom-link&gt;
      
      &lt;!-- Custom link with query parameters --&gt;
      &lt;custom-link 
        routerLink="/search" 
        [queryParams]="{ q: 'angular', category: 'docs' }"
        routerLinkActive="active"&gt;
        Search Angular Docs
      &lt;/custom-link&gt;
    &lt;/nav&gt;
  `,
})
export class CustomNavigationComponent {}</code></pre><p></p><h2>&#128293; @schematics/angular</h2><h3>1. Global Error Listeners for Better Error Handling (<a href="https://github.com/angular/angular-cli/commit/e03f2b89992cb1e34a57f9cd5beef77674c116b6">PR</a>)</h3><p>Previously, errors happening outside Angular's zone or in asynchronous operations might go unnoticed or crash your application silently. With this new provider, Angular automatically registers global error handlers to catch and manage these scenarios more gracefully.</p><p>The provider is now automatically included in new Angular applications generated through the CLI, ensuring better error handling.</p><pre><code>// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    // New global error listeners provider - catches unhandled errors
    provideBrowserGlobalErrorListeners(),
  ]
};

// app.ts
export class AppComponent {
  
  triggerAsyncError() {
    // This error would previously go unhandled
    setTimeout(() =&gt; {
      throw new Error('Async error caught by global listener!');
    }, 100);
  }
  
  triggerPromiseRejection() {
    // Unhandled promise rejection
    Promise.reject(new Error('Promise rejection caught by global listener!'));
  }
  
  triggerZoneError() {
    // Error outside Angular zone
    Zone.current.runOutsideAngular(() =&gt; {
      throw new Error('Zone error caught by global listener!');
    });
  }
}</code></pre><p></p><h3>2. Automatic TypeScript Module Resolution Migration (<a href="https://github.com/angular/angular-cli/commit/1e137ca848839402bc214fbccdc04243862d01d0">PR</a>)</h3><p>The CLI now automatically migrates existing projects to use TypeScript's <code>'bundler'</code> module resolution instead of the older <code>'node'</code> resolution. This change aligns with modern build tools and package managers, providing better support for ESM modules and contemporary JavaScript patterns.</p><p>The migration intelligently scans all TypeScript configuration files in your workspace and updates them appropriately, while respecting specific configurations like <code>module: 'preserve'</code>.</p><p>The migration is smart about when to apply changes:</p><ul><li><p>&#9989; Updates <code>moduleResolution: 'node'</code> to <code>moduleResolution: 'bundler'</code></p></li><li><p>&#10060; Skips files that already use <code>'bundler'</code> resolution</p></li><li><p>&#10060; Preserves <code>module: 'preserve'</code> configurations unchanged</p></li><li><p>&#128269; Scans all TypeScript configs in your workspace automatically</p></li></ul><pre><code>// BEFORE Migration - tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "node",  // &#10060; Old resolution strategy
  }
}

// AFTER Migration - tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "bundler",  // &#9989; Modern bundler resolution
  }
}</code></pre><p></p><h3>3. Server-Side Rendering API Modernization (<a href="https://github.com/angular/angular-cli/commit/26fd4ea73ad2a0148ae587d582134c68a0bf4b86">PR</a>)</h3><p>Two major changes streamline SSR setup:</p><ul><li><p><strong>Import Migration</strong>:</p><ul><li><p>&#9989; Moves <code>provideServerRendering</code> from <code>@angular/platform-server</code> to <code>@angular/ssr</code></p></li><li><p>&#9989; Preserves other platform-server imports</p></li><li><p>&#9989; Merges with existing <code>@angular/ssr</code> imports</p></li></ul></li><li><p><strong>API Consolidation</strong>:</p><ul><li><p>&#9989; Replaces <code>provideServerRouting(routes)</code> with <code>provideServerRendering(withRoutes(routes))</code></p></li><li><p>&#9989; Removes duplicate <code>provideServerRendering()</code> calls</p></li><li><p>&#9989; Preserves additional arguments and configurations</p></li></ul></li></ul><pre><code>// &#10060; BEFORE - Angular 19 SSR Configuration
// Old location
import { provideServerRendering } from '@angular/platform-server'; 
// Separate provider 
import { provideServerRouting } from '@angular/ssr';  

export const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(),
    provideServerRouting(routes)  // Two separate calls
  ]
};

// &#9989; AFTER - Angular 20 Unified SSR
// Single import
import { provideServerRendering, withRoutes } from '@angular/ssr';  

export const advancedServerConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(
      withRoutes(routes),
      withAppShell(AppShellComponent),  // App shell for instant loading
      withPrerendering(['/home', '/about'])  // Static prerendering
    )
  ]
};
</code></pre><p></p><h3>4. File Naming Style Guide Migration (<a href="https://github.com/angular/angular-cli/commit/fdc6291dda4903f418667d415b05367390cf829d">PR</a>)</h3><p>Angular 20 introduces a new file naming style guide but automatically preserves your existing project's naming conventions when upgrading. The migration ensures backward compatibility while new projects adopt the modern naming standards.</p><pre><code>// &#128202; FILE NAMING COMPARISON TABLE

| Schematic Type | Angular 19 (Old) | Angular 20 (New) | Command Example |
|---------------|------------------|------------------|-----------------|
| Service       | user.service.ts  | user-service.ts  | ng g service user |
| Guard         | auth.guard.ts    | auth-guard.ts    | ng g guard auth |
| Interceptor   | data.interceptor.ts | data-interceptor.ts | ng g interceptor data |
| Module        | shared.module.ts | shared-module.ts | ng g module shared |
| Pipe          | custom.pipe.ts   | custom-pipe.ts   | ng g pipe custom |
| Resolver      | api.resolver.ts  | api-resolver.ts  | ng g resolver api |

// &#128295; MIGRATION CONFIGURATION
// angular.json - Added automatically during ng update to preserve old naming
{
  "schematics": {
    // Keep .component.ts
    "@schematics/angular:component": { "type": "component" },  
    // Keep .directive.ts     
    "@schematics/angular:directive": { "type": "directive" }, 
    // Keep .service.ts    
    "@schematics/angular:service": { "type": "service" }, 
    // Keep .guard.ts        
    "@schematics/angular:guard": { "typeSeparator": "." },  
    // Keep .interceptor.ts      
    "@schematics/angular:interceptor": { "typeSeparator": "." },  
    // Keep .module.ts
    "@schematics/angular:module": { "typeSeparator": "." },  
    // Keep .pipe.ts     
    "@schematics/angular:pipe": { "typeSeparator": "." },         
    // Keep .resolver.ts
    "@schematics/angular:resolver": { "typeSeparator": "." }      
  }
}

// &#128194; PRACTICAL EXAMPLES

// &#128308; UPGRADED PROJECTS (with migration defaults):
ng generate service user-data       &#8594; user-data.service.ts
ng generate guard auth             &#8594; auth.guard.ts  
ng generate pipe currency-format  &#8594; currency-format.pipe.ts

// &#128994; NEW PROJECTS (Angular 20 defaults):
ng generate service user-data       &#8594; user-data-service.ts
ng generate guard auth             &#8594; auth-guard.ts
ng generate pipe currency-format  &#8594; currency-format-pipe.ts

// &#127919; COMPONENT STRUCTURE COMPARISON
ng generate component user-profile

// Upgraded projects:        // New projects:
user-profile/               user-profile/
&#9500;&#9472;&#9472; user-profile.component.ts    &#9500;&#9472;&#9472; user-profile-component.ts
&#9500;&#9472;&#9472; user-profile.component.html  &#9500;&#9472;&#9472; user-profile-component.html
&#9500;&#9472;&#9472; user-profile.component.css   &#9500;&#9472;&#9472; user-profile-component.css
&#9492;&#9472;&#9472; user-profile.component.spec.ts &#9492;&#9472;&#9472; user-profile-component.spec.ts

// &#128260; MIGRATION BEHAVIOR
// &#9989; Preserves existing settings if you already have custom configurations
// &#9989; Only adds defaults for missing configurations
// &#9989; Maintains your current project structure
// &#9989; Zero breaking changes - all existing files remain unchanged</code></pre><p></p><h3>5. Modern Build System with @angular/build (<a href="https://github.com/angular/angular-cli/commit/093c5a3152c4282d4afb51df40945283cc94d281">PR</a>)</h3><p>&#128230; <strong>New Build Package</strong>: <code>@angular/build</code> replaces <code>@angular-devkit/build-angular</code> for new projects<br>&#9889; <strong>Smaller Install Size</strong>: ~115 MB vs ~291 MB (60% reduction)<br>&#128640; <strong>Faster Builds</strong>: No Webpack dependencies for modern esbuild-based builds<br>&#128260; <strong>Backward Compatible</strong>: Existing projects can still use the old builder</p><p></p><h3>6. TypeScript Project References for Better IDE Support (<a href="https://github.com/angular/angular-cli/commit/d6f594fe0f8f21d9c0e2abedb5c8433a1aa5c157">PR</a>)</h3><p>&#128279; <strong>Project References</strong>: TypeScript configs now use composite projects and references<br>&#129504; <strong>Better IDE Support</strong>: IDEs can accurately discover types across different project areas<br>&#128193; <strong>Solution Style</strong>: Root <code>tsconfig.json</code> becomes a TypeScript solution file<br>&#9889; <strong>Zero Build Impact</strong>: Angular build process remains completely unaffected</p><h4>The Key Insight</h4><p><strong>TypeScript project references were designed for vanilla TypeScript</strong>, not framework projects. Here's why:</p><p>&#128269; <strong>Pure TypeScript</strong>: <code>tsc --build</code> works perfectly - it just compiles <code>.ts</code> to <code>.js</code> and <code>.d.ts</code></p><p>&#127917; <strong>Angular Reality</strong>: Needs template compilation, dependency injection metadata, AOT compilation, style processing, and bundling</p><h4>Why Nx Exists</h4><p>Nx realized this fundamental limitation years ago and built their own solution:</p><ul><li><p>&#127919; <strong>Dependency Graph</strong>: Manually tracks project relationships</p></li><li><p>&#9889; <strong>Build Orchestration</strong>: Runs builds in the correct order</p></li><li><p>&#127914; <strong>Framework Aware</strong>: Understands Angular's compilation needs</p></li><li><p>&#128190; <strong>Smart Caching</strong>: Avoids rebuilding unchanged projects</p></li></ul><h4>The Honest Truth</h4><p>Angular 20's "project references" are <strong>marketing over substance</strong>. They give you:</p><ul><li><p>&#9989; Better IDE experience</p></li><li><p>&#10060; Same build performance</p></li><li><p>&#10060; No incremental compilation</p></li><li><p>&#10060; No dependency-aware building</p></li></ul><p>For real Angular monorepo performance, you still need Nx. TypeScript project references alone just aren't enough for framework projects! &#127919;</p><p>Interesting PRs for Angular Project References:</p><ul><li><p><a href="https://github.com/angular/angular/issues/37276">TypeScript 3 Project References feature support #37276</a></p></li><li><p><a href="https://github.com/angular/angular/issues/59896">Support TS Project References #59896</a></p></li><li><p><a href="https://github.com/angular/angular/pull/59897#issuecomment-2653642300">feat(compiler-cli): support ts project references #59897</a></p></li></ul><p></p><h3>7. SSR Routing Simplification - No More Manual Configuration (<a href="https://github.com/angular/angular-cli/commit/18e13e2ceed931d29aa5582980c7d6d1f66c9787">PR</a>)</h3><p>Before Angular 20, developers had to understand the difference between "basic SSR" and "SSR with server routing."</p><p>Now it's simple: <strong>SSR means full SSR with everything enabled</strong>. One less decision, better defaults, happier developers! &#128640;</p><pre><code># Update these commands:
# &#10060; ng add @angular/ssr --server-routing
# &#9989; ng add @angular/ssr

# &#10060; ng generate app-shell --server-routing  
# &#9989; ng generate app-shell</code></pre><p></p><h3>8. TypeScript Module Preserve for Modern Bundling (<a href="https://github.com/angular/angular-cli/commit/03180fe0358662f8fd3255ad546994da3e3bda9c">PR</a>)</h3><p>&#128260; <strong>Module Preserve</strong>: New projects use <code>"module": "preserve"</code> instead of <code>"module": "ES2022"</code><br>&#9889; <strong>Auto-Enabled Options</strong>: Automatically sets <code>esModuleInterop</code>, <code>moduleResolution: "bundler"</code>, and <code>resolveJsonModule</code><br>&#128465;&#65039; <strong>Cleaner Config</strong>: Removes redundant explicit options from <code>tsconfig.json</code><br>&#128230; <strong>Better Bundler Compatibility</strong>: Matches modern bundler behavior exactly</p><pre><code>// &#10060; BEFORE - Angular 19 tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true,        // &#10060; Explicitly needed
    "moduleResolution": "bundler",  // &#10060; Explicitly needed
    "module": "ES2022"              // &#10060; Old module system
  }
}

// &#9989; AFTER - Angular 20 tsconfig.json
{
  "compilerOptions": {
    // &#9989; These are now automatically enabled by "preserve":
    // "esModuleInterop": true,
    // "moduleResolution": "bundler", 
    // "resolveJsonModule": true,
    "importHelpers": true,
    "target": "ES2022",
    "module": "preserve"            // &#9989; Modern bundler-aware module system
  }
}</code></pre><p></p><h2>&#128293; @angular/build</h2><h3>1. Experimental Vitest Unit Testing Support (<a href="https://github.com/angular/angular-cli/commit/d6ea6b09f182433f859a78d4a4d38a9db521e593">PR1</a>, <a href="https://github.com/angular/angular-cli/commit/12def3a2e907ca8e7d530cea1b39bba90e153144">PR2</a>)</h3><p>Angular 20 introduces experimental Vitest support as an alternative to Karma for unit testing, bringing modern testing capabilities with browser support and faster execution to Angular projects.</p><p>&#129514; <strong>Experimental Vitest Builder</strong>: New <code>@angular/build:unit-test</code> builder with Vitest support<br>&#127760; <strong>Browser Testing</strong>: Optional browser execution with Playwright or WebDriverIO<br>&#9889; <strong>Faster Tests</strong>: Modern test runner with better performance than Karma<br>&#128295; <strong>Build Target Integration</strong>: Leverages existing build configurations</p><pre><code>// &#10060; Not supported yet:
// - Watch mode  
// - Custom vitest configuration
// - Some Angular-specific testing features

// &#9989; Supported:
// - Basic unit testing
// - Code coverage  
// - Browser testing
// - Angular TestBed integration</code></pre><p>Here is a cool guide about <a href="https://cookbook.marmicode.io/angular/testing/why-vitest">implementing Vitest into NX monorepo</a> by <a href="https://www.linkedin.com/in/yjaaidi/">Younes Jaaidi</a>.</p><p></p><h3>2. Source Map Sources Content Control (<a href="https://github.com/angular/angular-cli/commit/c1de633007c423cfd9113cc781b5647e59306146">PR</a>)</h3><p>Source maps are files that map the compiled/minified code back to the original source files, making debugging easier. By default, source maps include the actual content of the original source files (known as "sourcesContent"). This makes the source maps self-contained but can significantly increase their size.</p><p>The Angular team has added the ability to exclude the original source content from generated source maps through a new <code>sourcesContent</code> option. This applies to both JavaScript and CSS source maps.</p><pre><code>// &#129517; WHAT ARE SOURCE MAPS?
// Think of them like a GPS for your code:
// 
// Your browser sees: `function a(b){return b+1}` (minified/ugly)
// Source map says: "This came from line 15 in user.service.ts: getUserAge(user)"
// DevTools shows: Your original, readable code!

// &#127919; THE NEW CHOICE: FULL GUIDE vs LIGHTWEIGHT GUIDE

// Option 1: Full Guide (sourcesContent: true) - DEFAULT
// &#9989; Includes your original source code IN the source map file
// &#9989; Works everywhere, even offline
// &#10060; Source map files are 3x larger
// &#10060; Your source code is exposed in production

// Option 2: Lightweight Guide (sourcesContent: false) - NEW!
// &#9989; Source map files are 60% smaller
// &#9989; Your source code stays private
// &#10060; Debugging requires access to original files

// &#128193; SIMPLE CONFIGURATION

// angular.json - Choose your approach
{
  "build": {
    "builder": "@angular/build:application",
    "options": {
      "sourceMap": {
        "scripts": true,
        "styles": true,
        "sourcesContent": false  // &#127919; NEW: Choose lightweight guide
      }
    },
    "configurations": {
      "development": {
        "sourceMap": {
          "sourcesContent": true   // &#128736;&#65039; FULL guide for debugging
        }
      },
      "production": {
        "sourceMap": {
          "sourcesContent": false  // &#128640; LIGHTWEIGHT guide for production
        }
      }
    }
  }
}</code></pre><p></p><h3>3. Smart Default Output Path - No More Configuration Needed (<a href="https://github.com/angular/angular-cli/commit/9b682e62519e761477e6266650239bf58026a9f4">PR</a>)</h3><ul><li><p>The <code>outputPath</code> field is no longer required in Angular application configurations</p></li><li><p>A default value <code>dist/&lt;project_name&gt;</code> is now used when <code>outputPath</code> is not specified</p></li><li><p>Before this change, you had to explicitly specify the <code>outputPath</code> in your Angular project configuration (<code>angular.json</code>). Now, if you don't specify it, the build will automatically use <code>dist/&lt;project_name&gt;</code> relative to your workspace root.</p></li></ul><p></p><h3>4. Custom Package Resolution Conditions (<a href="https://github.com/angular/angular-cli/commit/d067cedf05051e3a0f237d50306e1e4c881a0328">PR</a>)</h3><p>Angular 20 adds a new <code>conditions</code> option that gives you fine-grained control over how npm packages are resolved, allowing you to specify exactly which version of a package to use when it offers multiple build outputs.</p><pre><code>// Modern npm packages often ship multiple versions:
// package.json (example: lodash-es)
{
  "name": "lodash-es",
  "exports": {
    ".": {
      "node": "./node.js",        // For Node.js environments
      "browser": "./browser.js",  // For browsers
      "module": "./esm.js",       // Modern ES modules
      "import": "./esm.js",       // ES module imports
      "require": "./cjs.js",      // CommonJS requires
      "development": "./dev.js",  // Development builds
      "production": "./prod.js",  // Production builds (minified)
      "default": "./index.js"     // Fallback
    }
  }
}

// &#128295; ANGULAR 20 CONDITIONS CONFIGURATION

// angular.json - Control package resolution
{
  "build": {
    "builder": "@angular/build:application",
    "options": {
      "conditions": ["module", "browser", "production"]  // &#9989; NEW: Custom conditions
    },
    "configurations": {
      "development": {
        "conditions": ["module", "browser", "development"]  // &#9989; Dev-specific conditions
      },
      "production": {
        "conditions": ["module", "browser", "production"]   // &#9989; Prod-specific conditions
      }
    }
  }
}</code></pre><p></p><h3>5. Sass Package Importers (<a href="https://github.com/angular/angular-cli/commit/f4be831197010a17394264bc74b1eb385ba95028">PR</a>)</h3><p>Angular 20 adds support for Sass's new <code>pkg:</code> importers, making it easier to import Sass files from npm packages without messy path configurations or <code>~</code> prefixes.</p><p>Think of <code>pkg:</code> importers as a <strong>modern, clean way to import Sass from npm packages</strong>. Instead of dealing with complex paths and configurations, you can directly reference any npm package in your Sass files.</p><pre><code>// Old way (still works):
@import '~@angular/material/theming';

// New way (recommended):
@use 'pkg:@angular/material' as mat;</code></pre><p></p><h2>&#128293; @angular/ssr</h2><h3>1. Stabilize <code>AngularNodeAppEngine</code>, <code>AngularAppEngine</code>, and <code>provideServerRouting</code> APIs (<a href="https://github.com/angular/angular-cli/commit/cdfc50c29a2aa6f32d172b505a0ef09e563dfc59">PR</a>)</h3><p>These APIs enhance server-side rendering (SSR) capabilities in Angular applications, improving routing and server integration for better performance and reliability.</p><p></p><h2>Breaking Changes Summary &#9888;&#65039;</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!B8Zt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!B8Zt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 424w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 848w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 1272w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!B8Zt!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png" width="1200" height="621.5469613259669" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:750,&quot;width&quot;:1448,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:283111,&quot;alt&quot;:&quot;Breaking Changes Summary on Angular 20&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Breaking Changes Summary on Angular 20" title="Breaking Changes Summary on Angular 20" srcset="https://substackcdn.com/image/fetch/$s_!B8Zt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 424w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 848w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 1272w, https://substackcdn.com/image/fetch/$s_!B8Zt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fa962bb-95f8-41e6-812d-7dfb29d2ae40_1448x750.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Deprecations Summary &#9888;&#65039;</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jeJT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jeJT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 424w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 848w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 1272w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jeJT!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png" width="1200" height="260.4008293020041" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:314,&quot;width&quot;:1447,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:106935,&quot;alt&quot;:&quot;Angular 20 Deprecations Summary&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 20 Deprecations Summary" title="Angular 20 Deprecations Summary" srcset="https://substackcdn.com/image/fetch/$s_!jeJT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 424w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 848w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 1272w, https://substackcdn.com/image/fetch/$s_!jeJT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4bfd17c9-d83e-4c23-9358-8403e8e44b3f_1447x314.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p></p><h2>Style guide updates</h2><p>Angular 20 introduces a <strong>new file naming convention</strong> but automatically preserves your existing project's style when upgrading.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oXoa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oXoa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 424w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 848w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 1272w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oXoa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png" width="1259" height="689" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:689,&quot;width&quot;:1259,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:560672,&quot;alt&quot;:&quot;file Naming Style Guide Angular 20&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="file Naming Style Guide Angular 20" title="file Naming Style Guide Angular 20" srcset="https://substackcdn.com/image/fetch/$s_!oXoa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 424w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 848w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 1272w, https://substackcdn.com/image/fetch/$s_!oXoa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdafa8ec5-daf1-4071-9a75-bef7aa3681f2_1259x689.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><a href="https://github.com/angular/angular/discussions/58412">Check the RFC</a>.</p><p>If you want to check the modern Angular folder structure, you can read this <a href="https://www.angular.courses/blog/angular-folder-structure-guide">post</a> by <a href="https://www.linkedin.com/in/gerome-grignon/">Gerome Grignon</a>. Gerome also created a library called <a href="https://www.npmjs.com/package/ngx-boomer">ngx-boomer</a> to update your <code>angular.json</code> to keep the previous behaviour.</p><p></p><h2>Pioneering AI-First Development</h2><p>Angular 20 marks a pivotal moment where framework evolution meets artificial intelligence, establishing Angular as the premier choice for developers building in the age of GenAI. The Angular team has recognized that the future of web development isn't just about better frameworks&#8212;it's about frameworks that work seamlessly with AI tools.</p><h3>The AI Development Challenge</h3><p>Modern developers face a unique paradox: while AI tools can dramatically accelerate development, they often generate outdated code patterns. Picture this scenario&#8212;you ask an AI assistant to create an Angular component, and it returns code using NgModules and <code>*ngIf</code> directives, patterns that were cutting-edge five years ago but are now legacy approaches. This disconnect between AI knowledge and current best practices creates friction in what should be a smooth development experience.</p><h3>Angular's AI-First Strategy</h3><h4>1. The <code>llms.txt</code> Initiative: Teaching AI About Modern Angular</h4><p>Angular has introduced a groundbreaking <code>llms.txt</code> file&#8212;consider it a curriculum designed specifically for large language models. This isn't just documentation; it's a carefully curated learning path that helps AI systems understand:</p><ul><li><p><strong>Current syntax patterns</strong>: Control flow blocks (<code>@if</code>, <code>@for</code>, <code>@switch</code>) instead of structural directives</p></li><li><p><strong>Modern architecture</strong>: Standalone components over NgModule-based approaches</p></li><li><p><strong>Latest APIs</strong>: Signal-based reactivity and new lifecycle hooks</p></li><li><p><strong>Best practices</strong>: Contemporary file naming and organization standards</p></li></ul><h4>2. AI-Enhanced Development Guidelines</h4><p>The second pillar of Angular's AI strategy focuses on empowering developers who are building AI-powered applications. This goes beyond fixing AI-generated code&#8212;it's about creating applications that leverage AI as a core feature.</p><p><a href="https://angular.dev/ai?_sc=NTk5NDc4NyM2MzU2">A guide about AI in the Angular official docs</a>.</p><h5>Genkit Integration Patterns</h5><p><a href="https://github.com/angular/examples/tree/main/genkit-angular-starter-kit">Angular has pioneered integration patterns with Google's Genkit, creating templates for common AI use cases</a>.</p><div id="youtube2-mx7yZoIa2n4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;mx7yZoIa2n4&quot;,&quot;startTime&quot;:&quot;6709s&quot;,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/mx7yZoIa2n4?start=6709s&amp;rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Another initiative is to start with AI experiences using modern Angular, which <a href="https://hashbrown.dev/docs/angular/start/quick">Hashbrown</a> created.</p><p>Recently, <a href="https://www.linkedin.com/in/mgechev/">Minko Gechev</a> talked JSNation and showed a demo with an e-commerce and Gemini integration as AI using GenKit.</p><p>Here are the <a href="https://drive.google.com/file/d/1tXWuoVracPI5oAoNPb6xY_vOYPcN_WWx/view?pli=1">slides</a> and the <a href="https://github.com/mgechev/resourceful-suspense/tree/main">demo</a>.</p><p></p><h2>Angular Mascot</h2><p>Check the <a href="https://github.com/angular/angular/discussions/61733">RFC</a> to vote for the favourite one. For me, it is the number 1.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nLFL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nLFL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 424w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 848w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 1272w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nLFL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png" width="1334" height="532" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:532,&quot;width&quot;:1334,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:370264,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/164456738?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nLFL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 424w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 848w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 1272w, https://substackcdn.com/image/fetch/$s_!nLFL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1ceb00c-8564-4839-92e6-f21f3abb17e3_1334x532.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2><strong>Thanks for reading so far &#128591;</strong></h2><p>I&#8217;d like to have your feedback, so please leave a <strong>comment</strong>, <strong>clap</strong> or <strong>follow</strong>. <em>&#128079;</em></p><p>Spread the Angular love! &#128156;</p><p>If you liked it, <strong>share it</strong> among your community, tech bros and whoever you want! &#128640;&#128101;</p><p>Don't forget to follow me and stay updated: &#128241;</p><ul><li><p>&#128279; <strong><a href="https://www.linkedin.com/in/amos-lucian-isaila-34ab78146/">LinkedIn</a></strong></p></li><li><p>&#128221; <strong><a href="https://medium.com/@amosisaila">Medium</a></strong></p></li><li><p>&#127909; <strong><a href="https://www.youtube.com/@codigotipado">YouTube</a></strong></p></li><li><p>&#128038; <strong><a href="https://twitter.com/amosisaila">Twitter</a></strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item></channel></rss>