<?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>Mon, 15 Jun 2026 17:32:16 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[Angular 22: What's new]]></title><description><![CDATA[Angular 22: Signal Forms are stable. OnPush is the new default. The future is here.]]></description><link>https://www.codigotipado.com/p/angular-22-whats-new</link><guid isPermaLink="false">https://www.codigotipado.com/p/angular-22-whats-new</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Wed, 03 Jun 2026 22:01:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZXwQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg" 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_!ZXwQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg" width="1200" height="670.054945054945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:268520,&quot;alt&quot;:&quot;Angular 22&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.codigotipado.com/i/193433490?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22" title="Angular 22" srcset="https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ZXwQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0705989-0940-4c9e-9013-93236625e1f0_1920x1072.jpeg 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>&#127959;&#65039; Framework (<code>angular/angular</code>)</h2><h3>Core &amp; Reactivity</h3><h4>a) &#128293; Default change detection is now OnPush - <a href="https://github.com/angular/angular/commit/eae8f7e30b9f8bebdcdb535bd86260199c34274b">PR</a></h4><p><strong>Components without an explicit </strong><code>changeDetection</code><strong> property now default to </strong><code>OnPush</code> instead of the old <code>Default</code> (now renamed to <code>Eager</code>).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e9ca131f-2bf4-45df-8f81-49db0f92f885&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before Angular 22 &#8212; this was implicitly ChangeDetectionStrategy.Default
@Component({
  selector: &#8216;app-dashboard&#8217;,
  template: `&lt;h1&gt;{{ title }}&lt;/h1&gt;`,
})
export class DashboardComponent {
  title = &#8216;Dashboard&#8217;;
}

// Angular 22 &#8212; same code is now implicitly OnPush
// If you need the old behavior:
@Component({
  selector: &#8216;app-dashboard&#8217;,
  changeDetection: ChangeDetectionStrategy.Eager, // &#8592; new name for &#8220;Default&#8221;
  template: `&lt;h1&gt;{{ title }}&lt;/h1&gt;`,
})
export class DashboardComponent {
  title = &#8216;Dashboard&#8217;;
}</code></pre></div><h4>b) Signal-native debouncing with <code>debounced()</code> - <a href="https://github.com/angular/angular/commit/b918beda323eefef17bf1de03fde3d402a3d4af0">PR</a></h4><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;d30672c6-af68-4d3b-bc88-62bf38619b0e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">protected readonly searchInput = signal(&#8217;&#8216;);

// Debounced &#8212; settles after 500ms of no typing
protected readonly debouncedSearch = debounced(() =&gt; this.searchInput(), 500);</code></pre></div><p>The resource gives you <code>.value()</code>, <code>.status()</code>, <code>.isLoading()</code>, and <code>.error()</code> &#8212; loading states come for free. I wrote a full deep-dive on this: check out my <strong><a href="https://www.codigotipado.com/p/angular-22-debounced-signal">Debounced Signals in Angular 22</a></strong><a href="https://www.codigotipado.com/p/angular-22-debounced-signal"> article</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><h4>c) Nested leave animations scoped to component boundaries - <a href="https://github.com/angular/angular/commit/df659b8d0cf64eeed418c60bc16cae5630086401">PR</a></h4><p>Leave animations are no longer limited to just the element being removed. They now propagate to nested elements within the same component boundary. If you have a parent element with <code>:leave</code> and child elements with their own animations, the children will now animate out properly before the parent is removed.</p><p>This is a <strong>breaking change</strong> if you relied on the old behavior where nested leave animations were silently ignored.</p><h4>d) Special return statuses for resource params - <a href="https://github.com/angular/angular/commit/2206efa55fc1de160333d62680f8893c47525335">PR</a></h4><p>Resources now support special return statuses from their <code>params</code> function. Instead of always returning params (which triggers a load), you can now return <code>undefined</code> to put the resource in an <code>idle</code> state &#8212; meaning it won&#8217;t fetch until valid params are available.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;db8cb4d4-2156-46e8-a642-409e277cf4bf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">type UserSearchParams = { query: string };

@Component({
  selector: &#8216;app-user-search&#8217;,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    &lt;input (input)=&#8221;searchQuery.set($any($event.target).value)&#8221; placeholder=&#8221;Search users...&#8221; /&gt;

    @switch (users.status()) {
      @case (&#8217;idle&#8217;) { &lt;p&gt;Type something to search&lt;/p&gt; }
      @case (&#8217;loading&#8217;) { &lt;p&gt;Searching...&lt;/p&gt; }
      @case (&#8217;resolved&#8217;) {
        @for (user of users.value(); track user.id) {
          &lt;p&gt;{{ user.name }}&lt;/p&gt;
        }
      }
      @case (&#8217;error&#8217;) { &lt;p&gt;Something went wrong&lt;/p&gt; }
    }
  `,
})
export class UserSearchComponent {
  protected readonly searchQuery = signal(&#8217;&#8216;);

  protected readonly users = httpResource&lt;User[]&gt;(() =&gt; {
    const query = this.searchQuery();

    // Return undefined &#8594; resource stays idle, no request fired
    if (query.length &lt; 2) return undefined;

    return `/api/users?q=${encodeURIComponent(query)}`;
  });
}</code></pre></div><p>The <code>idle</code> status is new &#8212; before this, a resource would always be in <code>loading</code>, <code>resolved</code>, or <code>error</code>. Now you can distinguish between &#8220;hasn&#8217;t started yet&#8221; and &#8220;currently fetching.&#8221;</p><p><a href="https://www.codigotipado.com/p/angular-19-resource-api-for-async">Learn more about Angular resources here</a>.</p><h4>e) <code>TestBed.getLastFixture()</code> - <a href="https://github.com/angular/angular/commit/dc3131c639542ad6a463bff3da5ca84c6f8ecb6f">PR</a></h4><p>A new testing utility that returns the last created <code>ComponentFixture</code>. Originally introduced as <code>TestBed.getFixture()</code> in next.0, it was renamed to <code>TestBed.getLastFixture()</code> in next.4 for clarity.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;29daf4c0-2438-41fa-babc-f1e695be1c7b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">it(&#8217;should render the component&#8217;, () =&gt; {
  TestBed.configureTestingModule({
    imports: [MyComponent],
  });

  const fixture = TestBed.createComponent(MyComponent);
  
  // Later in the test, if you need the fixture again:
  const sameFixture = TestBed.getLastFixture();
  expect(sameFixture).toBe(fixture);
});</code></pre></div><h4>f) Customizable @defer idle behavior - <a href="https://github.com/angular/angular/commit/a5981b83a60577d9068d2429bcbed969edca581b">PR</a> &amp; <a href="https://github.com/angular/angular/commit/98eb24cea0498382cc7cf7d7b85cd9ead5ad99ad">PR</a></h4><p>Two related changes that give you more control over <code>@defer</code> blocks with idle triggers:</p><ol><li><p><code>IdleRequestOptions</code><strong> support</strong> &#8212; you can now customize the idle service behavior</p></li><li><p><strong>Optional timeout for idle triggers</strong> &#8212; set a maximum wait time for idle-triggered defer blocks</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;36ae273a-e3c2-470e-a2ee-31e8f33e7c92&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// You can now configure idle behavior with timeout
@defer (on idle; timeout 5000) {
  &lt;app-heavy-widget /&gt;
}</code></pre></div><p>This is useful when you want a defer block to load during idle time but don&#8217;t want to wait forever if the browser stays busy.</p><h4>g) Exhaustive type checking for <code>@switch</code> - <a href="https://github.com/angular/angular/commit/8bc31a515ff6e8edda6ea5786a47ae5a788acd36">PR</a></h4><p>The <code>@switch</code> control flow now supports exhaustive type checking with other expressions. The compiler can verify that all possible values of a union type are handled:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;f648b381-4d9a-4a99-b374-58bcc7ca31c1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">type Status = &#8216;active&#8217; | &#8216;inactive&#8217; | &#8216;pending&#8217;;

@Component({
  template: `
    @switch (status()) {
      @case (&#8217;active&#8217;) { &lt;span&gt;Active&lt;/span&gt; }
      @case (&#8217;inactive&#8217;) { &lt;span&gt;Inactive&lt;/span&gt; }
      @case (&#8217;pending&#8217;) { &lt;span&gt;Pending&lt;/span&gt; }
    }
  `,
})
export class StatusComponent {
  status = input.required&lt;Status&gt;();
}</code></pre></div><h4>h) &#9888;&#65039; <code>createNgModuleRef</code> removed - <a href="https://github.com/angular/angular/commit/36936872c962b2073c8f44080684701068866691">PR</a></h4><p>The deprecated <code>createNgModuleRef</code> function has been removed. Use <code>createNgModule</code> instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;9784791e-05a0-43e4-bc25-bbdfd43f61b0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// No more createNgModuleRef
const moduleRef = createNgModule(MyModule, injector);</code></pre></div><h4>i) &#9888;&#65039; <code>ChangeDetectorRef.checkNoChanges</code> removed - <a href="https://github.com/angular/angular/commit/69fb1614eff6e40bb7dcca81f275ac32b9cbd28a">PR</a></h4><p><code>ChangeDetectorRef.checkNoChanges</code> has been removed from the public API. In tests, use <code>fixture.detectChanges()</code> instead.</p><h4>j) &#9888;&#65039; AnimationCallbackEvent.animationComplete signature changed - <a href="https://github.com/angular/angular/commit/b9b5c279b444ab2684fe911982930dc7c31ed43c">PR</a></h4><p>The <code>animationComplete</code> callback signature on <code>AnimationCallbackEvent</code> has been enhanced. If you&#8217;re using this API directly, check the new signature.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;5f212263-34af-4b44-b9b0-b60e5189f742&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// strictly typed as VoidFunction
type AnimationCallbackEvent = {
  target: Element;
  animationComplete: VoidFunction;
};</code></pre></div><h4>k) Migration schematic: <code>ChangeDetectionStrategy.Eager</code> - <a href="https://github.com/angular/angular/commit/cb4cb77053a817fe800af6395783720761e29ada">PR</a></h4><p>Since the default changed to OnPush, Angular provides a migration schematic that automatically adds <code>ChangeDetectionStrategy.Eager</code> to components that previously relied on the implicit <code>Default</code> strategy. Run it during <code>ng update</code>.</p><h4>l) De-duplicate host directives - <a href="https://github.com/angular/angular/commit/9c55fcb3e65ffcde32d7ac438ea40a69ffc2b3b6">PR</a></h4><p>If the same directive appears multiple times in a host directive chain (through inheritance or composition), it&#8217;s only applied once. Before this, duplicates could cause unexpected double-initialization or conflicting behavior.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;9533058a-6d09-4b04-b464-34f06f142db9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Directive({ host: { class: &#8216;tooltip&#8217; } })
export class TooltipDirective {}

// Both BaseCard and FancyCard declare TooltipDirective as a host directive
@Component({
  selector: &#8216;app-base-card&#8217;,
  hostDirectives: [TooltipDirective],
  template: `&lt;ng-content /&gt;`,
})
export class BaseCardComponent {}

@Component({
  selector: &#8216;app-fancy-card&#8217;,
  hostDirectives: [TooltipDirective], // duplicate &#8212; now de-duplicated automatically
  template: `&lt;ng-content /&gt;`,
})
export class FancyCardComponent extends BaseCardComponent {}</code></pre></div><h4>m) &#9888;&#65039; Drop support for TypeScript 5.9 - <a href="https://github.com/angular/angular/commit/8fe025f5149d7eb460e784a5a17bb467f85b9080">PR</a></h4><p>Angular 22 now requires <strong>TypeScript 6.0 or later</strong>. TypeScript 5.9 and older are no longer supported. Make sure to update your TypeScript dependency when upgrading.</p><h4>n) &#9888;&#65039; <code>ComponentFactoryResolver</code> &amp; <code>ComponentFactory</code> removed - <a href="https://github.com/angular/angular/commit/9d76ac82290e047f1481fb38bd95233e951a77de">PR</a></h4><p><code>ComponentFactoryResolver</code> and <code>ComponentFactory</code> have been completely removed from the API surface. These were deprecated long ago in favor of passing component classes directly.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;30ae526f-a93d-4985-b3db-10ee7a9c6b92&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#10060; &#8212; using the factory pattern
const factory = this._resolver.resolveComponentFactory(MyComponent);
const componentRef = viewContainerRef.createComponent(factory);

// After &#9989; &#8212; pass the component class directly
const componentRef = viewContainerRef.createComponent(MyComponent);

// Or use the standalone function:
const componentRef = createComponent(MyComponent, { environmentInjector });</code></pre></div><p>If you&#8217;re still using <code>ComponentFactoryResolver</code> anywhere &#8212; in dynamic component loading, lazy modules, or testing utilities &#8212; this is the migration you need to make.</p><h4>o) Bootstrap via <code>ApplicationRef</code> with config - <a href="https://github.com/angular/angular/commit/a0aa8304cd78a58a990c3b648e41f6888b50b1b3">PR</a></h4><p><code>ApplicationRef.bootstrap()</code> now accepts a configuration object as its second argument. The type has been tightened &#8212; it no longer accepts <code>any</code>, so you need to pass a proper non-nullable element.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;0d5b8dd0-f857-4543-9d86-96cbfddefbfe&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#8212; second arg was loosely typed as `any`
appRef.bootstrap(AppComponent, document.getElementById(&#8217;app&#8217;));

// Angular 22 &#8212; stricter typing, element must be non-nullable
const rootEl = document.getElementById(&#8217;app&#8217;);
if (rootEl) {
  appRef.bootstrap(AppComponent, rootEl);
}</code></pre></div><p>This is a <strong>breaking change</strong> if you were passing a potentially nullable element without checking first.</p><h4>p) Synchronous Values for Stream Resources - <a href="https://github.com/angular/angular/pull/67382">PR</a></h4><p>Stream resources (<code>resource()</code> with a <code>stream</code> loader) previously required the loader to return a <code>PromiseLike&lt;Signal&lt;ResourceStreamItem&lt;T&gt;&gt;&gt;</code>. That async requirement meant the resource always started in a <code>loading</code> state &#8212; even when the data was available immediately.</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://www.codigotipado.com/p/angular-22-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-22-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-22-whats-new?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div><p>This was a real problem for SSR scenarios. When you&#8217;re trying to cache resource values in <code>TransferState</code>, the resource needs to set its value synchronously. Otherwise, it enters <code>loading</code> state on the client, which destroys the server-hydrated DOM before the value arrives.</p><p>The <code>ResourceStreamingLoader</code> type now accepts three return types:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;fe979ee6-7f9f-492b-b6f5-068070974d63&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// New type signature
type ResourceStreamingLoader&lt;T, R&gt; = (
  param: ResourceLoaderParams&lt;R&gt;
) =&gt; Signal&lt;ResourceStreamItem&lt;T&gt;&gt;       // &#8592; NEW: synchronous
   | PromiseLike&lt;Signal&lt;ResourceStreamItem&lt;T&gt;&gt;&gt;  // existing async
   | undefined;                                   // &#8592; NEW: skip loading</code></pre></div><p>What this enables:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;de3f5ecc-328f-4f27-b05c-0e7c1a9b4199&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Component({
  template: `
    @if (weather.value(); as data) {
      &lt;p&gt;{{ data.temperature }}&#176;C in {{ data.city }}&lt;/p&gt;
    }
  `
})
export class WeatherComponent {
  private _city = input.required&lt;string&gt;();
  private _cache = inject(TransferStateCache);

  weather = resource({
    params: this._city,
    stream: ({ params: city }) =&gt; {
      // Check cache first &#8212; returns synchronously if cached
      const cached = this._cache.get&lt;WeatherData&gt;(`weather-${city}`);
      if (cached) {
        // Resource is immediately 'resolved', no loading flicker
        return signal({ value: cached });
      }

      // Fall back to async fetch
      return this._fetchWeather(city);
    },
  });

  private async _fetchWeather(
    city: string
  ): Promise&lt;Signal&lt;ResourceStreamItem&lt;WeatherData&gt;&gt;&gt; {
    const response = await fetch(`/api/weather?city=${city}`);
    const data: WeatherData = await response.json();
    return signal({ value: data });
  }
}</code></pre></div><p>The key win: <code>rxResource</code> with synchronous observables (like <code>of(value)</code>) now resolves immediately after a tick instead of requiring <code>await appRef.whenStable()</code>. This makes SSR hydration seamless &#8212; the client picks up the cached value without any loading state flash.</p><h4>q) Support Bootstrapping Under Shadow Roots - <a href="https://github.com/angular/angular/pull/67947">PR</a></h4><p>Angular applications can now be bootstrapped inside a Shadow DOM root. Previously, <code>bootstrapApplication</code> assumed it was working with the document&#8217;s root &#8212; which broke when you tried to embed an Angular app inside a web component&#8217;s shadow root.</p><p>This is a big deal for micro-frontend architectures and web component integration scenarios:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;85610072-bfc2-4706-913f-90c1af19fe60&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Bootstrap Angular inside a shadow root
const shadowRoot = hostElement.attachShadow({ mode: 'open' });

const appDiv = document.createElement('div');
appDiv.id = 'angular-app';
shadowRoot.appendChild(appDiv);

bootstrapApplication(AppComponent, {
  ...appConfig,
  // Angular now correctly scopes to the shadow root
  // instead of assuming document-level access
});</code></pre></div><p>This means Angular can coexist with other frameworks on the same page without style or DOM conflicts, each living in its own shadow boundary. If you&#8217;re building micro-frontends or embedding Angular widgets in non-Angular pages, this removes a significant barrier.</p><h4>r) The New <code>@Service</code> Decorator - <a href="https://github.com/angular/angular/pull/68195">PR</a></h4><p>This is a big one. Angular now has a dedicated <code>@Service</code> decorator that&#8217;s purpose-built for the most common use case: defining a singleton service available across your entire app.</p><p><code>@Injectable</code> has been around since Angular 2, and it carries a lot of historical baggage. Most developers just want to create a service that&#8217;s <code>providedIn: 'root'</code>, uses <code>inject()</code> for dependencies, and doesn&#8217;t need the complex provider configurations (<code>useClass</code>, <code>useValue</code>, <code>useFactory</code>, etc.). Yet every time, you have to write <code>@Injectable({ providedIn: 'root' })</code> &#8212; boilerplate that adds noise without adding value.</p><p><code>@Service</code> fixes this by making the common case the default. Here&#8217;s what&#8217;s different:</p><ol><li><p><code>providedIn: 'root'</code><strong> by default.</strong> No configuration needed for the 90% case. If you want to provide the service yourself, set <code>autoProvided: false</code>.</p></li><li><p><strong>No constructor-based injection.</strong> Only the <code>inject()</code> function is supported. This aligns with where Angular has been heading for a while now.</p></li><li><p><strong>Simplified factory support.</strong> Instead of the complex <code>useClass</code>/<code>useValue</code>/<code>useExisting</code>/<code>useFactory</code> signature from <code>@Injectable</code>, <code>@Service</code> supports a single <code>factory</code> function.</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;2ad070d6-259c-4b4e-b242-3cebbd28c285&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before: @Injectable with boilerplate &#10060;
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class PostService {
  private readonly _httpClient = inject(HttpClient);

  getUserPosts(userId: string) {
    return this._httpClient.get&lt;Post[]&gt;(`/api/posts/${userId}`);
  }
}</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;eb01c1e0-ffdf-4d40-bdf6-4f3fe125a99c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// After: @Service &#8212; clean and intentional &#9989;
import { Service, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Service()
export class PostService {
  private readonly _httpClient = inject(HttpClient);

  getUserPosts(userId: string) {
    return this._httpClient.get&lt;Post[]&gt;(`/api/posts/${userId}`);
  }
}</code></pre></div><p>The difference looks small in a single file, but across a codebase with hundreds of services, the intent becomes much clearer. <code>@Service()</code> says &#8220;this is a singleton service&#8221; without any ceremony.</p><p>When you don&#8217;t want automatic root-level providing &#8212; say, for a service scoped to a specific component or route &#8212; you opt out explicitly:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;4328ae5d-b9e6-4d78-9af1-4c75abe2d4c0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Service({ autoProvided: false })
export class PanelStateService {
  private readonly _isOpen = signal(false);

  readonly isOpen = this._isOpen.asReadonly();

  toggle(): void {
    this._isOpen.update((open) =&gt; !open);
  }
}</code></pre></div><p>And if you need a custom factory (the equivalent of <code>useFactory</code> in <code>@Injectable</code>):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;726fa905-6f7d-4e1c-bf85-01db23546fb6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Service({
  factory: () =&gt; {
    const config = inject(AppConfig);
    const http = inject(HttpClient);
    return new ApiService(config.apiBaseUrl, http);
  },
})
export class ApiService {
  constructor(
    private readonly _baseUrl: string,
    private readonly _http: HttpClient
  ) {}

  get&lt;T&gt;(path: string) {
    return this._http.get&lt;T&gt;(`${this._baseUrl}${path}`);
  }
}</code></pre></div><p>One thing worth noting: <code>@Service</code> enforces <code>inject()</code>-only dependency resolution. If you try to use constructor parameters for DI, the compiler will throw a <code>SERVICE_CONSTRUCTOR_DI</code> error. This is intentional &#8212; the Angular team is pushing the ecosystem toward <code>inject()</code> as the standard pattern, and <code>@Service</code> draws that line clearly.</p><p><code>@Injectable</code> isn&#8217;t going anywhere. It&#8217;s still fully supported for cases that need its flexibility. But for new services, <code>@Service</code> is the cleaner, more opinionated choice.</p><h4>s) Testability Now Uses PendingTasks for Stability - <a href="https://github.com/angular/angular/pull/68187">PR</a></h4><p>Angular&#8217;s <code>Testability</code> service &#8212; the one that powers <code>whenStable()</code> for tools like Protractor and E2E testing frameworks &#8212; has been updated to use <code>PendingTasks</code> as its stability indicator. This is a significant step toward making the testing infrastructure work seamlessly in zoneless applications.</p><p>Here&#8217;s the backstory. Angular&#8217;s stability concept has historically been tied to Zone.js. The <code>Testability.isStable()</code> method checked whether the NgZone was stable and had no pending macrotasks. That worked fine when Zone.js was the only game in town, but it completely broke for zoneless apps &#8212; there was no Zone to check.</p><p>Meanwhile, Angular has been building <code>PendingTasks</code> as a zone-agnostic way to track async work. The <code>Router</code> and <code>HttpClient</code> already contribute to <code>PendingTasks</code>. This commit connects the dots: <code>Testability</code> now checks <code>PendingTasks</code> in addition to Zone stability.</p><p>The behavior depends on your setup:</p><ul><li><p><strong>Zoneless apps</strong> (no Zone.js present): <code>PendingTasks</code> is used automatically. <code>whenStable()</code> resolves when there are no pending tasks &#8212; no configuration needed.</p></li><li><p><strong>Zone.js apps</strong>: The old Zone-based stability check still works by default. You can opt into <code>PendingTasks</code>-based stability through <code>provideProtractorTestingSupport()</code>.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;a636baff-0915-4dd9-b732-d6ae795481e2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Zoneless app &#8212; PendingTasks stability works automatically &#9989;
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    provideExperimentalZonelessChangeDetection(),
    // whenStable() now works correctly without Zone.js
  ],
};</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;362429b4-8c09-4df5-85f5-82bc2e3a1e5d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Zone.js app &#8212; opt into PendingTasks stability explicitly
import { provideProtractorTestingSupport } from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    provideProtractorTestingSupport({
      usePendingTasksForStability: true,
    }),
  ],
};</code></pre></div><p>The <code>isStable()</code> method now evaluates three conditions instead of two:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;dec77770-2e3a-4415-9e6e-f230dd56b2f3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Internal logic (simplified)
isStable(): boolean {
  return (
    this._isZoneStable &amp;&amp;
    !this._ngZone.hasPendingMacrotasks &amp;&amp;
    (!this._usePendingTasks || !this.pendingTasksInternal.hasPendingTasks)
  );
}</code></pre></div><p>What this means for you in practice: if you&#8217;re migrating to zoneless and your E2E tests use <code>whenStable()</code> (directly or through a testing framework), they&#8217;ll now work correctly. The <code>Router</code> navigation, <code>HttpClient</code> requests, and any custom <code>PendingTasks</code> usage will all be tracked as part of application stability.</p><h4>t) <code>injectAsync</code> &#8212; lazy-load and inject services on demand - <a href="https://github.com/angular/angular/pull/68248">PR</a></h4><p>Here&#8217;s the thing about lazy loading in Angular: we&#8217;ve had lazy routes for ages, but lazy-loading <em>services</em> has always been awkward. You&#8217;d typically need to grab an <code>EnvironmentInjector</code>, dynamically import the service class, then manually call <code>injector.get()</code>. It worked, but it wasn&#8217;t ergonomic.</p><p><code>injectAsync</code> is a new helper function exported from <code>@angular/core</code> that streamlines this pattern. It returns a function that, when called, resolves to an instance of the lazily-loaded service &#8212; fully wired through DI.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;8294d697-7f58-4c30-96e2-478e0be34ec5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { Component, injectAsync } from '@angular/core';

@Component({
  selector: 'app-dashboard',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    &lt;button (click)="loadChart()"&gt;Show Chart&lt;/button&gt;
  `,
})
export class DashboardComponent {
  // Returns a function: () =&gt; Promise&lt;ChartService&gt;
  private _chartService = injectAsync(
    () =&gt; import('./chart.service').then((m) =&gt; m.ChartService),
  );

  async loadChart(): Promise&lt;void&gt; {
    const chartService = await this._chartService();
    chartService.render();
  }
}</code></pre></div><h4>u) SSR resource caching - <a href="https://github.com/angular/angular/commit/5a7c1e62dc2a4fa199b85150eca66914c107a6f4">PR</a></h4><p>Angular&#8217;s <code>resource()</code> and <code>rxResource()</code> now support a new <code>id</code> option that enables automatic caching through <code>TransferState</code>. This is the missing piece for SSR + signals: data fetched on the server gets serialized into the HTML, and when the client hydrates, the resource resolves <strong>synchronously</strong> from the cache instead of making a redundant network request.</p><p>Here&#8217;s the problem this solves. Without caching, your SSR flow looks like:</p><ol><li><p>Server renders &#8594; resource fetches data &#8594; HTML sent to client</p></li><li><p>Client hydrates &#8594; resource fetches the <strong>same data again</strong> &#8594; flash of loading state</p></li></ol><p>With the <code>id</code> option, step 2 becomes: client hydrates &#8594; resource reads from <code>TransferState</code> &#8594; <strong>instant resolution, no network call, no loading flicker</strong>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;9ad0adb1-6583-423d-9d06-b0d9dac0ff14&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { resource } from '@angular/core';
import { inject } from '@angular/core';

@Component({
  selector: 'app-product-detail',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @if (product.status() === 'resolved') {
      &lt;h1&gt;{{ product.value().name }}&lt;/h1&gt;
      &lt;p&gt;{{ product.value().description }}&lt;/p&gt;
    }
  `,
})
export class ProductDetailComponent {
  private _productId = input.required&lt;string&gt;();
  private _http = inject(HttpClient);

  product = resource({
    params: () =&gt; ({ id: this._productId() }),
    loader: ({ params }) =&gt;
      firstValueFrom(this._http.get&lt;Product&gt;(`/api/products/${params.id}`)),
    id: 'product-detail', // &#8592; enables TransferState caching
  });
}</code></pre></div><p>The <code>id</code> is a <code>string</code> added to <code>BaseResourceOptions</code>. It serves as the <code>TransferState</code> key. When the resource resolves on the server, the value is written to <code>TransferState</code> under that key. On the client, if a matching key exists in <code>TransferState</code>, the resource skips the loader entirely and resolves synchronously with the cached value.</p><p>This also works with <code>rxResource()</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;04b1230a-db16-4ec6-8059-fdd133563bee&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { rxResource } from '@angular/core/rxjs-interop';

export class DashboardComponent {
  private _http = inject(HttpClient);

  metrics = rxResource({
    stream: () =&gt; this._http.get&lt;DashboardMetrics&gt;('/api/metrics'),
    id: 'dashboard-metrics',
  });
}</code></pre></div><p>This is different from <code>HttpClient</code>&#8216;s <code>transferCache</code> option (which caches HTTP responses at the interceptor level). The <code>id</code> option works at the <strong>resource level</strong>, meaning it caches the final resolved value regardless of how you fetched it &#8212; HTTP, WebSocket, IndexedDB, whatever your loader does.</p><p>One thing to note: you need <code>provideClientHydration()</code> in your app config for <code>TransferState</code> to work. If you&#8217;re already using SSR with hydration, you likely have this set up.</p><p></p><h3>Templates &amp; Compiler</h3><h4>a) Support comments in HTML elements - <a href="https://github.com/angular/angular/commit/e850643b1b8dca8cfdc12705be51441197cd987a">PR</a></h4><p>Angular&#8217;s template parser now supports HTML comments inside elements.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;603866da-09c2-46c0-a014-97bdc3836140&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- This now works as expected everywhere in templates --&gt;
&lt;p&gt;Subscribe to codigotipado!&lt;/p&gt;</code></pre></div><h4>b) &#9888;&#65039; Compile-time diagnostic for duplicate selectors (NG8023) - <a href="https://github.com/angular/angular/commit/ca67828ee247bdff46736661e51f43f2ca736a24">PR</a></h4><p>Angular now throws a <strong>compile-time error</strong> when multiple components or directives share the same selector within the same scope. Before this, the behavior was undefined &#8212; sometimes one would win, sometimes you&#8217;d get subtle bugs that were painful to track down.</p><p>The new diagnostic <code>NG8023</code> catches this at build time:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;2ca7021c-bfd8-413c-930f-a62ca68e309f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// This now throws NG8023 at compile time
@Component({
  selector: &#8216;app-card&#8217;,
  template: `&lt;p&gt;Card A&lt;/p&gt;`,
})
export class CardAComponent {}

@Component({
  selector: &#8216;app-card&#8217;, // Duplicate selector!
  template: `&lt;p&gt;Card B&lt;/p&gt;`,
})
export class CardBComponent {}</code></pre></div><p><code>error NG8023: Multiple components match node with selector "app-card".</code></p><p>If you have duplicate selectors in your codebase, you&#8217;ll need to rename one of them. This is a breaking change, but honestly it&#8217;s catching a bug you already had.</p><h4>c) Warning for <code>@defer</code> prefetch without main trigger - <a href="https://github.com/angular/angular/commit/7f9450219f5c30d1ce0a90061864e8c844c8807c">PR</a></h4><p>The compiler now emits a <strong>warning</strong> when you use <code>prefetch</code> on a <code>@defer</code> block without specifying a main trigger. This catches a common mistake &#8212; if there&#8217;s no trigger to actually load the deferred content, the prefetch is pointless.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;3e4e4344-d6b6-45e1-bce1-4305b103087b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- &#9888;&#65039; Warning: prefetch without a main trigger does nothing --&gt;
@defer (prefetch on idle) {
  &lt;app-heavy-widget /&gt;
}

&lt;!-- &#9989; Correct: has both a trigger and a prefetch strategy --&gt;
@defer (on viewport; prefetch on idle) {
  &lt;app-heavy-widget /&gt;
}</code></pre></div><p>This is a compile-time warning, not an error &#8212; your app will still build. But it&#8217;s a strong hint that something&#8217;s off in your defer configuration.</p><h4>d) &#9888;&#65039; Safe Navigation Now Correctly Narrows Nullables - <a href="https://github.com/angular/angular/pull/67959">PR</a></h4><p>This one's been a long time coming. Since 2020, Angular's safe navigation operator (<code>?.</code>) in templates didn't actually narrow types the way TypeScript does in regular code. That meant you couldn't rely on <code>?.</code> checks to eliminate <code>null | undefined</code> from subsequent expressions &#8212; the type system just ignored the narrowing.</p><p><strong>BREAKING CHANGE</strong>: This will trigger <code>nullishCoalescingNotNullable</code> and <code>optionalChainNotNullable</code> diagnostics on existing projects. If you're seeing a flood of new warnings after updating, you can temporarily disable those two diagnostics in your <code>tsconfig.json</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;492147a9-25dd-4206-83f0-cfc771fd2dea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">// tsconfig.json
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "nullishCoalescingNotNullable": "suppress",
        "optionalChainNotNullable": "suppress"
      }
    }
  }
}</code></pre></div><p>Or, if you want to disable narrowing entirely (not recommended long-term):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;31ad1e7d-0cee-4df4-b4a1-a5ede9401a3b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "angularCompilerOptions": {
    "strictSafeNavigationTypes": false
  }
}</code></pre></div><p>Here&#8217;s what this actually means in practice:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;c4d21f9b-26d8-480c-9449-d95a0ac1c0e9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before: Angular didn't narrow after ?. &#8212; this compiled fine even though it's redundant
@Component({
  template: `
    @if (user?.name) {
      &lt;!-- user.name was still typed as string | undefined here --&gt;
      {{ user?.name ?? 'fallback' }}  &lt;!-- no warning, even though ?? is unnecessary --&gt;
    }
  `
})

// After &#9989;: Angular correctly narrows &#8212; you'll get a warning about the unnecessary ??
@Component({
  template: `
    @if (user?.name) {
      &lt;!-- user.name is now correctly narrowed to string --&gt;
      {{ user.name }}  &lt;!-- clean, no need for ?. or ?? --&gt;
    }
  `
})</code></pre></div><p>The <code>ng update</code> migration (covered below in Migrations) will automatically disable these diagnostics so you can update without being blocked, then fix them at your own pace.</p><h4>e) <strong>&#9888;&#65039;</strong> Optional chaining now returns <code>undefined</code> instead of <code>null</code> - <a href="https://github.com/angular/angular/pull/68084">PR</a></h4><p>This is a big one. Angular&#8217;s template compiler has historically treated the safe navigation operator (<code>?.</code>) differently from standard JavaScript/TypeScript. In vanilla JS, <code>foo?.bar</code> returns <code>undefined</code> when <code>foo</code> is nullish. But Angular templates used to return <code>null</code>. That inconsistency has been a source of subtle bugs for years &#8212; particularly when comparing values in templates or passing them to functions that distinguish between <code>null</code> and <code>undefined</code>.</p><p>Angular expressions with optional chaining now correctly return <code>undefined</code>, aligning template behavior with the JavaScript specification.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;1b494c6a-ca7f-4c5f-bf9e-33df7ca74769&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- Given: user: { address?: { city?: string } } = {} --&gt;

&lt;!-- Before: this evaluated to null &#10060; --&gt;
&lt;!-- After: this evaluates to undefined &#9989; (matching JS behavior) --&gt;
{{ user?.address?.city }}

&lt;!-- This matters when you do strict comparisons --&gt;
@if (user?.address?.city === undefined) {
  &lt;p&gt;No city provided&lt;/p&gt;
}</code></pre></div><p><strong>Migration path:</strong> If you have code that explicitly checks for <code>null</code> from optional chaining results, you&#8217;ll need to update those checks. The Angular team provides two escape hatches:</p><ol><li><p><code>$null()</code><strong> magic function</strong> &#8212; wrap any expression to preserve the old behavior:</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;2af54c28-cbfb-48bc-a3ac-ba960a8f8d39&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- Opt-out: preserves legacy null-returning behavior --&gt;
{{ $null(user?.address?.city) }}</code></pre></div><ol start="2"><li><p><code>legacyOptionalChaining</code><strong> compiler option</strong> &#8212; a project-wide flag in <code>tsconfig.json</code>:</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;f5c573f6-9ce5-4f8d-b4c7-39e224d5bc9c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "angularCompilerOptions": {
    "legacyOptionalChaining": true
  }
}</code></pre></div><p>The <code>legacyOptionalChaining</code> flag is meant as a temporary migration aid. It won&#8217;t stick around forever &#8212; plan to remove it once you&#8217;ve audited your templates.</p><p>What&#8217;s interesting is how the linker handles backward compatibility: libraries compiled with Angular &lt; 22 automatically get the legacy behavior applied, so you don&#8217;t need to worry about third-party packages breaking.</p><h4>f) Node.js 26.0.0 support - <a href="https://github.com/angular/angular/commit/b8d3f36ed962bd4f5abd6bf6e55078b56ce9fffa">PR</a></h4><h4>g) External TCBs with copied content in specific mode - <a href="https://github.com/angular/angular/commit/2eae497a04a6a9b34397181dcd64dbd103f76c47">PR</a></h4><p>Enables better type-checking accuracy when working with templates in external files.</p><p></p><h3>Forms</h3><p>Signal Forms got a lot of love in this release. <a href="https://www.codigotipado.com/p/mastering-angular-21-signal-forms">Learn more about Angular Signal Forms here</a>.</p><h4>a) <code>debounce()</code> with &#8216;blur&#8217; option - <a href="https://github.com/angular/angular/commit/c767d678cff65a89f380b4512590fa732db072c8">PR</a></h4><p>The <a href="https://www.codigotipado.com/p/mastering-the-new-debounce-api-in">debounce() rule in Signal Forms</a> now supports a <code>'blur'</code> mode. Instead of debouncing by time, validation only fires when the field loses focus:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;d27c7545-2de5-45f8-aee9-85a3b3093055&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { form, debounce, validateAsync } from &#8216;@angular/forms/signals&#8217;;

const myForm = form(data, (path) =&gt; {
  // Validation fires only when the user leaves the field
  debounce(path.username, &#8216;blur&#8217;);

  validateAsync(path.username, {
    params: (ctx) =&gt; ({ username: ctx.value() }),
    factory: (params) =&gt; httpResource(() =&gt; `/api/check-username?u=${params.username}`),
    onSuccess: (result) =&gt; (result.taken ? { usernameTaken: true } : null),
  });
});</code></pre></div><p>This is perfect for fields where partial values are meaningless &#8212; IBANs, coupon codes, usernames. No point validating &#8220;Lond&#8221; against an API when the user clearly isn&#8217;t done typing.</p><h4>b) <code>reloadValidation()</code> for Signal Forms - <a href="https://github.com/angular/angular/commit/74f76d8075d03b1271aef37b974c9e15f9c7d3af">PR</a></h4><p>You can now manually trigger async validation on a signal form field with <code>reloadValidation()</code>. This is useful when external state changes and you need to re-validate without the user modifying the field &#8212; think of a coupon code field that should re-validate when the cart contents change.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;b6c6f724-b1cc-4d6b-be42-df35be7ac73c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { form, validateAsync, reloadValidation } from &#8216;@angular/forms/signals&#8217;;

@Component({
  selector: &#8216;app-checkout&#8217;,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    &lt;input [formField]=&#8221;couponField&#8221; placeholder=&#8221;Coupon code&#8221; /&gt;
    @if (couponField.errors()?.invalidCoupon) {
      &lt;span&gt;This coupon is no longer valid for your cart&lt;/span&gt;
    }
    &lt;button (click)=&#8221;onCartChanged()&#8221;&gt;Recalculate&lt;/button&gt;
  `,
})
export class CheckoutComponent {
  private readonly _cartService = inject(CartService);

  protected readonly data = signal({ coupon: &#8216;&#8217; });

  protected readonly checkoutForm = form(this.data, (path) =&gt; {
    validateAsync(path.coupon, {
      params: (ctx) =&gt; ({ code: ctx.value(), cartTotal: this._cartService.total() }),
      factory: (params) =&gt;
        httpResource(() =&gt; `/api/validate-coupon?code=${params.code}&amp;total=${params.cartTotal}`),
      onSuccess: (result) =&gt; (result.valid ? null : { invalidCoupon: true }),
    });
  });

  protected get couponField() {
    return this.checkoutForm.controls.coupon;
  }

  onCartChanged(): void {
    // Cart changed &#8212; re-validate the coupon without the user touching the field
    reloadValidation(this.checkoutForm.controls.coupon);
  }
}</code></pre></div><h4>c) Debounce option for <code>validateAsync</code> and <code>validateHttp</code> - <a href="https://github.com/angular/angular/commit/24e52d450d201e3da90bb64f84358f9eccd7877d">PR</a></h4><p>Async validators now accept a debounce configuration directly, so you don&#8217;t always need a separate <code>debounce()</code> rule:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ecf05a3f-c3dc-46e1-8b2e-e21cd46fb01c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// validateAsync with built-in debounce
validateAsync(path.city, {
  debounce: 500,
  params: (ctx) =&gt; ({ city: ctx.value() }),
  factory: (params) =&gt; httpResource(() =&gt; `/api/validate-city?name=${params.city}`),
  onSuccess: (result) =&gt; (result.valid ? null : { invalidCity: true }),
});

// validateHttp with built-in debounce &#8212; same pattern
validateHttp(path.email, {
  debounce: 300,
  request: (ctx) =&gt; `/api/check-email?email=${encodeURIComponent(ctx.value())}`,
  onSuccess: (response) =&gt; (response.available ? null : { emailTaken: true }),
});</code></pre></div><p>Before this, you had to pair every async validator with a separate <code>debounce()</code> call. Now it&#8217;s a single declaration. The <code>debounce</code> option accepts a number (milliseconds) or <code>'blur'</code> &#8212; same as the standalone <code>debounce()</code> rule.</p><h4>d) <code>FieldState.getError()</code> - <a href="https://github.com/angular/angular/commit/709f5a390ca0de04f8066012a5cb36999f2fd4a6">PR</a></h4><p>A new convenience method to retrieve a specific error from a field&#8217;s validation state:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ef0b36c9-6ea6-471f-88fb-f19ccc750f7d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">const emailError = fieldState.getError(&#8217;email&#8217;);
// Returns the error value or undefined</code></pre></div><h4>e) Support binding <code>number | null</code> to <code>&lt;input type="text"&gt;</code> - <a href="https://github.com/angular/angular/commit/41b1410cb8a333a2ce6569483cd10866effc154d">PR</a></h4><p>Signal Forms now properly handle binding <code>number | null</code> values to text inputs. Before this, you&#8217;d get type errors or unexpected behavior when a numeric field could be null.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;2f3077ef-c577-48e0-aedd-62561d8c3f45&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">type ProductForm = {
  name: string;
  price: number | null;  // nullable numeric field
  discount: number | null;
};

const data = signal&lt;ProductForm&gt;({
  name: &#8216;Widget&#8217;,
  price: 29.99,
  discount: null, // no discount yet
});

const productForm = form(data, (path) =&gt; {
  validate(path.name, required);
});</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;03ec02c1-13ce-4f26-8f5a-39f820645228&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- This now works without type errors --&gt;
&lt;input type=&#8221;text&#8221; [formField]=&#8221;productForm.controls.price&#8221; /&gt;
&lt;input type=&#8221;text&#8221; [formField]=&#8221;productForm.controls.discount&#8221; /&gt;</code></pre></div><p>When the value is <code>null</code>, the input displays empty. When the user types a number, it&#8217;s parsed back to <code>number</code>. Clean round-trip for nullable numeric fields.</p><h4>f) <code>ngNoCva</code> opt-out for ControlValueAccessors - <a href="https://github.com/angular/angular/commit/3983080236e348ecc17ab4e65a6a5cc0a16aa315">PR</a></h4><p>A new <code>ngNoCva</code> attribute lets you opt out of ControlValueAccessor binding on specific elements. This is useful when you have a custom form control but don&#8217;t want Angular&#8217;s CVA machinery to interfere with certain child elements.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;html&quot;,&quot;nodeId&quot;:&quot;052f94b3-1402-4fa4-8690-dfb5e681225b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-html">&lt;!-- This input won&#8217;t get a ControlValueAccessor attached --&gt;
&lt;input ngNoCva type=&#8221;text&#8221; /&gt;</code></pre></div><h4>g) Template &amp; reactive support for FVC - <a href="https://github.com/angular/angular/commit/c4ce3f345fdb14595f0991dff488c4043a0fc71c">PR</a></h4><p>Form Validation Controls (FVC) now work with both template-driven and reactive forms approaches, expanding the interop surface between Signal Forms and the existing form systems.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;0cbd4f84-bc0f-40bb-8bcc-0904f77440d1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Template-driven: FVC works with ngModel
@Component({
  selector: &#8216;app-profile&#8217;,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FormsModule, SignalFormsModule],
  template: `
    &lt;input [(ngModel)]=&#8221;name&#8221; [formValidation]=&#8221;nameValidation&#8221; /&gt;
    @if (nameValidation.errors()?.required) {
      &lt;span&gt;Name is required&lt;/span&gt;
    }
  `,
})
export class ProfileComponent {
  protected name = &#8216;&#8217;;
  protected readonly nameValidation = fieldValidation(/* ... */);
}

// Reactive: FVC works with FormControl
@Component({
  selector: &#8216;app-settings&#8217;,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ReactiveFormsModule, SignalFormsModule],
  template: `
    &lt;input [formControl]=&#8221;emailControl&#8221; [formValidation]=&#8221;emailValidation&#8221; /&gt;
  `,
})
export class SettingsComponent {
  protected readonly emailControl = new FormControl(&#8217;&#8216;);
  protected readonly emailValidation = fieldValidation(/* ... */);
}</code></pre></div><h4>h) &#9889; Lazily instantiate signal form fields - <a href="https://github.com/angular/angular/commit/98c5afdb02192f99c886fc3fda13ec6f39018f23">PR</a></h4><p>A performance improvement that defers the instantiation of signal form fields until they&#8217;re actually needed. For large forms with many fields, this reduces the initial creation cost.</p><h4>i) Shim Legacy NG_VALIDATORS into parseErrors for CVA Mode - <a href="https://github.com/angular/angular/pull/67943">PR</a></h4><p>Signal Forms CVA (ControlValueAccessor) interop mode now properly bridges legacy <code>NG_VALIDATORS</code> into the new <code>parseErrors</code> system. If you have existing custom validators registered through the legacy <code>NG_VALIDATORS</code> token, they&#8217;ll now work correctly when used alongside Signal Forms CVA mode.</p><p>This is about backward compatibility. As the Angular team builds out Signal Forms, they need to ensure that existing form infrastructure doesn&#8217;t break. This shim means you can incrementally adopt Signal Forms without rewriting all your custom validators at once:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;5b485a07-4283-47a4-91d4-e8806dc781c6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Your existing validator directive still works with Signal Forms CVA mode
@Directive({
  selector: '[appCustomValidator]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: CustomValidatorDirective,
      multi: true,
    },
  ],
})
export class CustomValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors | null {
    return control.value?.length &gt; 10 ? { tooLong: true } : null;
  }
}

// These legacy validators now correctly surface in Signal Forms' parseErrors
// No migration needed &#8212; the shim handles the bridging automatically</code></pre></div><h4>j) &#127881; Signal forms APIs graduate to public API - <a href="https://github.com/angular/angular/commit/7745365910771d97c91e9b640c2c26a99bfa5a6d">PR</a></h4><p>This is the big one. Signal-based forms &#8212; which have been in developer preview &#8212; are now part of Angular's stable public API. This means the APIs are subject to Angular's semantic versioning guarantees and won't have breaking changes outside major versions.</p><p><a href="https://www.codigotipado.com/p/mastering-angular-21-signal-forms">Read full guide about Angular Signal Forms here</a>.</p><h4>k) &#9889; Performance: shallow array equality for reactivity - <a href="https://github.com/angular/angular/commit/e0536091f5f6c2033e377998eea3bf65b14f5ac6">PR</a></h4><p>Signal forms now use shallow array equality checks when determining whether reactive values have changed. Previously, any mutation to an array (even if the contents were identical) would trigger downstream recomputation. Now, if the array elements are the same by reference, the signal won&#8217;t notify dependents.</p><p>This reduces unnecessary change detection cycles in forms with array-based controls (like dynamic field lists or multi-select values).</p><h4>l) &#9889; Performance: shortcut deepSignal writes if value is unchanged - <a href="https://github.com/angular/angular/commit/9b9769479b295bf34bae9a938ee758a256bd4b32">PR</a></h4><p>When writing to a deep signal (nested form state), the framework now short-circuits if the new value is identical to the current one. No notification, no recomputation, no template re-render. This is a targeted optimization for forms where programmatic resets or patches might write the same value back.</p><h4>m) <strong>&#9888;&#65039;</strong> <code>min</code> and <code>max</code> validation rules no longer accept strings - <a href="https://github.com/angular/angular/pull/68001">PR</a></h4><p>The <code>min</code> and <code>max</code> validation rules in signal forms no longer accept string values. Bound values must be <code>number | null</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;2db099c2-7522-4c07-a9ae-d55ee04ada03&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#8212; strings were silently accepted &#10060;
this._fb.field(0, { validators: [min('5')] });

// After &#8212; must be number or null &#9989;
this._fb.field(0, { validators: [min(5)] });</code></pre></div><p>If you were passing string values (perhaps from template bindings), you&#8217;ll need to parse them to numbers first.</p><p></p><h3>HTTP</h3><h4>a) &#9888;&#65039; FetchBackend is now the default HTTP backend - <a href="https://github.com/angular/angular/commit/5c432fb8bb69343ef2633811c37c0c6c0fd65126">PR</a></h4><p><code>provideHttpClient()</code> now uses <code>FetchBackend</code> by default instead of <code>HttpXhrBackend</code>. The Fetch API is more modern, supports streaming, and aligns with where the web platform is heading.</p><p><strong>What this means:</strong></p><ul><li><p>Upload progress reports are <strong>no longer available</strong> by default (Fetch API limitation)</p></li><li><p>If you need upload progress, use <code>withXhr()</code>:</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;da83b233-40f0-4f15-82ee-7df0acb66854&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before (Angular 21) &#8212; XHR was the default
provideHttpClient()

// Angular 22 &#8212; Fetch is the default. Same call, different backend.
provideHttpClient()

// If you need XHR (for upload progress):
provideHttpClient(withXhr())</code></pre></div><p>There&#8217;s a migration schematic that adds <code>withXhr()</code> where needed. <code>withFetch()</code> is now <strong>deprecated</strong> since Fetch is the default &#8212; you can safely remove it.</p><h4>b) <code>reportUploadProgress</code> &amp; <code>reportDownloadProgress</code> replace <code>reportProgress</code> - <a href="https://github.com/angular/angular/commit/7c8c3347efc1be2b5967b9481e3a2a3a23c24977">commit 7c8c334</a></h4><p>The monolithic <code>reportProgress</code> option on HTTP requests is now deprecated. In its place, two granular options give you explicit control over which direction of progress you want to track:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e5502376-ca23-4a37-aff7-ac237efea495&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#8212; reported both upload AND download progress &#10060; (deprecated)
this._http.post('/upload', formData, {
  reportProgress: true,
  observe: 'events',
});

// After &#8212; explicit about what you're tracking &#9989;
this._http.post('/upload', formData, {
  reportUploadProgress: true,
  observe: 'events',
});

// Or for downloads:
this._http.get('/large-file', {
  reportDownloadProgress: true,
  observe: 'events',
});

// Or both:
this._http.post('/upload-with-response', formData, {
  reportUploadProgress: true,
  reportDownloadProgress: true,
  observe: 'events',
});</code></pre></div><p>This split makes intent clearer and avoids the overhead of tracking progress in a direction you don&#8217;t care about. Most file upload UIs only need upload progress; most download UIs only need download progress. Now you can be explicit.</p><p></p><h3>Router</h3><h4>a) <code>withComponentInputBinding</code> options - <a href="https://github.com/angular/angular/commit/89c9a4de308a087ce95246ee259f32c8a927e39e">PR</a></h4><p><code>withComponentInputBinding()</code> now accepts an optional config to control which router sources bind to your component inputs:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;a198a85c-17e6-4adc-a21f-508dab32fc69&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Disable query param binding &#8212; only path params and route data bind
provideRouter(routes, withComponentInputBinding({ queryParams: false }));</code></pre></div><p>Before this, it was all or nothing. Now you can prevent query parameters from overriding your component inputs &#8212; which is a real hygiene improvement since query params are user-controlled. I wrote a full article about this one: <strong><a href="https://www.codigotipado.com/p/you-can-now-control-what-binds-to">Selective Component Input Binding in Angular 22</a></strong>.</p><h4>b) <code>browserUrl</code> input support for router links - <a href="https://github.com/angular/angular/commit/3683902234acf74c7047337bda4db937e93f93d7">PR</a></h4><p>Router links now support a <code>browserUrl</code> input that lets you display a different URL in the browser&#8217;s address bar than the actual route being navigated to. This was already possible with <code>router.navigateByUrl()</code> via <code>NavigationBehaviorOptions</code>, but now it works declaratively in templates.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;b4dad057-ef9a-44db-ab34-0a3827415a2c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Component({
  selector: &#8216;app-product-list&#8217;,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [RouterLink],
  template: `
    @for (product of products(); track product.id) {
      &lt;!-- Navigates to /products/42/details internally,
           but the browser shows /products/cool-widget --&gt;
      &lt;a
        [routerLink]=&#8221;[&#8217;/products&#8217;, product.id, &#8216;details&#8217;]&#8221;
        [browserUrl]=&#8221;&#8217;/products/&#8217; + product.slug&#8221;
      &gt;
        {{ product.name }}
      &lt;/a&gt;
    }
  `,
})
export class ProductListComponent {
  products = input.required&lt;Product[]&gt;();
}</code></pre></div><p>The <code>browserUrl</code> input accepts a <code>string</code> or <code>UrlTree</code>. This is useful for SEO-friendly URLs, vanity URLs, or when your internal route structure doesn&#8217;t match the URL you want users to see and share. It works on both <code>&lt;a&gt;</code> elements and non-anchor elements with <code>[routerLink]</code>.</p><h4>c) &#9888;&#65039; <code>provideRoutes()</code> removed - <a href="https://github.com/angular/angular/commit/bdb6ae9dbc080cd6ce4f5058c65f6b2bd853beda">PR bdb6ae9</a></h4><p>The deprecated <code>provideRoutes()</code> function has been removed. Use <code>provideRouter()</code> or the <code>ROUTES</code> multi token instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;b31d3718-137b-4c92-9445-c264ea69414a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#10060;
provideRoutes(myRoutes)

// After &#9989;
provideRouter(myRoutes)

// Or if you need the multi-token approach:
{ provide: ROUTES, useValue: myRoutes, multi: true }</code></pre></div><h4>d) <code>unmatchedInputBehavior</code> option for <code>componentInputBinding</code> - <a href="https://github.com/angular/angular/commit/c84642ac16bf3588c071bbdcc684daa8d4e494b3">PR</a></h4><p>When you enable <code>withComponentInputBinding()</code> in the router, route parameters and query params are automatically bound to component inputs. But what happens when a route parameter doesn&#8217;t match any input on the component? Previously, it was silently ignored.</p><p>The new <code>unmatchedInputBehavior</code> option lets you control this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e457d668-4e14-493d-94d2-e00401979b43&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { provideRouter, withComponentInputBinding } from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withComponentInputBinding({ unmatchedInputBehavior: 'warn' }),
    ),
  ],
};</code></pre></div><p>This is useful for catching typos in route configurations or detecting when route params change names but components haven&#8217;t been updated. In development, setting this to <code>'warn'</code> or <code>'error'</code> helps catch mismatches early.</p><p></p><h3>Rendering &amp; Platform</h3><h4>a) &#9888;&#65039; Hammer.js integration removed - <a href="https://github.com/angular/angular/commit/f99e7ed20f0b1a26fd275fcf5befd589bb4e5d31">PR</a></h4><p>The built-in Hammer.js integration has been completely removed. If you&#8217;re using gesture events like <code>(swipe)</code>, <code>(pinch)</code>, <code>(rotate)</code>, etc., you&#8217;ll need to implement your own gesture handling or use a third-party library directly.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e990bd1f-77cd-4b05-860f-749ccb3ea695&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#10060; &#8212; Angular handled Hammer.js integration
import { HammerModule } from &#8216;@angular/platform-browser&#8217;;

// After &#9989; &#8212; Use Hammer.js directly or switch to Pointer Events
// Install hammerjs yourself and set it up manually,
// or migrate to native pointer events</code></pre></div><p>For most apps, the <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events">Pointer Events API</a></strong> is the modern replacement.</p><h4>b) Incremental Hydration Is Now the Default - <a href="https://github.com/angular/angular/pull/68092">PR</a></h4><p>Incremental hydration is no longer opt-in &#8212; it&#8217;s the default behavior. Previously, you had to explicitly enable it. Now, when your application uses SSR with hydration, Angular will incrementally hydrate components as they become needed rather than hydrating the entire page at once.</p><p>What does this mean in practice? Your SSR pages will become interactive faster because Angular only hydrates the components that the user is actually interacting with. A component at the bottom of a long page won&#8217;t be hydrated until the user scrolls to it (or it&#8217;s triggered by some other interaction).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;21ff0350-3ff7-4828-bfa2-e9be4567555e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before: you had to explicitly opt in
// provideClientHydration(withIncrementalHydration())

// After &#9989;: incremental hydration is on by default
// Just use provideClientHydration() &#8212; incremental behavior is included
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideClientHydration(),  // incremental hydration is now default
  ],
};</code></pre></div><p>You can still use <code>@defer</code> blocks to control hydration triggers:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;c15b5f32-77aa-4568-8032-9d3d23c8ef50&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Component({
  template: `
    &lt;header&gt;
      &lt;app-nav /&gt;
    &lt;/header&gt;

    &lt;main&gt;
      &lt;app-hero-section /&gt;

      @defer (on viewport) {
        &lt;app-comments-section /&gt;  &lt;!-- Only hydrates when scrolled into view --&gt;
      }

      @defer (on interaction) {
        &lt;app-feedback-form /&gt;  &lt;!-- Only hydrates when user interacts --&gt;
      }
    &lt;/main&gt;
  `
})
export class PageComponent {}</code></pre></div><p>This is a meaningful performance win for content-heavy pages. The initial Time to Interactive (TTI) drops because Angular isn&#8217;t spending time hydrating components the user hasn&#8217;t reached yet.</p><p></p><h3>Developer Tooling</h3><h4>a) Angular DI Graph In-Page AI Tool - <a href="https://github.com/angular/angular/pull/68030">PR</a></h4><p>Angular now ships a built-in AI tool that exposes the dependency injection graph at runtime. This is part of the broader push to make Angular applications introspectable by AI assistants and developer tools.</p><p>The DI graph tool lets AI-powered debugging assistants (think: Copilot, Gemini, or custom MCP tools) query the injector hierarchy, understand which services are provided where, and trace dependency chains &#8212; all from within the running application.</p><h4>b) Register AI Runtime Debugging Tools - <a href="https://github.com/angular/angular/pull/67985">PR</a></h4><p>Closely related to the DI graph tool above, this commit establishes the infrastructure for registering AI runtime debugging tools in Angular. It provides the foundation that allows Angular to expose runtime information (component trees, signal states, DI graphs) to external AI tools.</p><p>Think of it as the plumbing that makes the DI graph tool (and future AI tools) possible. The framework now has a standardized way to register and expose debugging capabilities that AI assistants can consume.</p><h4>c) Enhanced Profiling with Documentation URLs - <a href="https://github.com/angular/angular/pull/67942">PR</a></h4><p>Angular&#8217;s profiling output now includes links to relevant documentation. When you&#8217;re profiling your application and see a particular operation taking time, the profiler will point you directly to the docs that explain what that operation does and how to optimize it.</p><p>It&#8217;s a small but thoughtful improvement. Instead of seeing a cryptic profiling label and having to search the docs yourself, you get a direct link. This is especially helpful for developers who are newer to Angular&#8217;s internals.</p><h4>d) <code>provideWebMcpTools</code> &#8212; MCP integration for Angular apps - <a href="https://github.com/angular/angular/pull/68139">PR</a></h4><p>Angular is going all-in on AI tooling. <code>provideWebMcpTools</code> is a new provider function that registers <a href="https://anthropic.com/news/model-context-protocol">WebMCP</a> tools directly through Angular&#8217;s dependency injection system. MCP (Model Context Protocol) is the open standard that lets AI assistants &#8212; Claude, Copilot, Cursor, etc. &#8212; interact with your running application: inspecting component state, triggering actions, querying data.</p><p>Here&#8217;s what makes this interesting: the tools are <strong>tied to the lifecycle of the </strong><code>Injector</code> they&#8217;re provided to. They register automatically when the environment initializes and unregister when the injector is destroyed. And the <code>execute</code> function runs in the injection context of that injector &#8212; meaning you can <code>inject()</code> any dependency inside it.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;08838c96-6d56-46aa-bd9b-2a4b47c4dbe0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { provideWebMcpTools } from '@angular/core';
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideWebMcpTools([
      {
        name: 'getUserCart',
        description: 'Returns the current user cart items with quantities and prices',
        inputSchema: { type: 'object', properties: {} },
        execute: async () =&gt; {
          const cartService = inject(CartService);
          const items = cartService.items();

          return {
            content: [{
              type: 'text',
              text: JSON.stringify(items),
            }],
          };
        },
      },
    ]),
  ],
};</code></pre></div><p>The <code>execute</code> function receives two arguments: the parsed <code>args</code> (typed from your <code>inputSchema</code>) and a <code>WebMcpClient</code> object containing an <code>AbortSignal</code> for cancellation support.</p><p>What&#8217;s particularly powerful is combining this with <strong>route-level providers</strong> and <code>withExperimentalAutoCleanupInjectors</code>. Tools register when the user navigates to a route and automatically unregister when navigating away:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;32c7fc3a-c45f-426e-8894-5ed8f1f860be&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { provideRouter, withExperimentalAutoCleanupInjectors } from '@angular/router';
import { provideWebMcpTools } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      [
        {
          path: 'admin',
          component: AdminDashboardComponent,
          providers: [
            provideWebMcpTools([
              {
                name: 'getSystemMetrics',
                description: 'Returns current system health metrics (only available on admin page)',
                inputSchema: {
                  type: 'object',
                  properties: {
                    timeRange: { type: 'string', enum: ['1h', '24h', '7d'] },
                  },
                },
                execute: async (args) =&gt; {
                  const metrics = inject(MetricsService);
                  const data = await metrics.fetch(args.timeRange);

                  return { content: [{ type: 'text', text: JSON.stringify(data) }] };
                },
              },
            ]),
          ],
        },
      ],
      withExperimentalAutoCleanupInjectors(),
    ),
  ],
};</code></pre></div><p>When the user navigates to <code>/admin</code>, the <code>getSystemMetrics</code> tool becomes available to AI assistants. Navigate away, and it&#8217;s gone. No manual cleanup needed.</p><p>Under the hood, <code>provideWebMcpTools</code> is a thin wrapper &#8212; it calls <code>makeEnvironmentProviders</code> with a <code>provideEnvironmentInitializer</code> that iterates over your tool descriptors and calls <code>declareWebMcpTool</code> for each one.</p><h4>e) <code>declareWebMcpTool</code> &#8212; the low-level imperative API - <a href="https://github.com/angular/angular/pull/68139">PR</a></h4><p>While <code>provideWebMcpTools</code> is the ergonomic, declarative approach, <code>declareWebMcpTool</code> is the lower-level primitive it&#8217;s built on. This function immediately registers a single WebMCP tool and ties its lifecycle to the current (or provided) <code>Injector</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ea065504-9526-424c-9f41-e16426f11836&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { declareWebMcpTool } from '@angular/core';
import type { WebMcpToolDescriptor } from '@angular/core';

@Component({
  selector: 'app-chat',
  template: `&lt;div class="chat-container"&gt;...&lt;/div&gt;`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChatComponent {
  private _chatService = inject(ChatService);

  constructor() {
    // Registers the tool in the current injection context.
    // Automatically unregistered when this component's injector is destroyed.
    declareWebMcpTool({
      name: 'sendMessage',
      description: 'Sends a message in the active chat conversation',
      inputSchema: {
        type: 'object',
        properties: {
          message: { type: 'string' },
          channel: { type: 'string' },
        },
      },
      execute: async (args) =&gt; {
        await this._chatService.send(args.channel, args.message);
        return { content: [{ type: 'text', text: `Message sent to #${args.channel}` }] };
      },
    });
  }
}</code></pre></div><p>The function signature:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;70cd432b-f34e-4d27-8788-883c8ac6849f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">declare function declareWebMcpTool&lt;const InputSchema extends JsonSchemaForInference&gt;(
  tool: WebMcpToolDescriptor&lt;InputSchema&gt;,
  injector?: Injector,
): void;</code></pre></div><p>The optional <code>injector</code> parameter lets you register tools outside an injection context &#8212; useful for dynamic scenarios where you create tools programmatically.</p><p><strong>Key types exported:</strong></p><ul><li><p><code>WebMcpToolDescriptor&lt;InputSchema&gt;</code> &#8212; the tool definition (name, description, inputSchema, execute)</p></li><li><p><code>WebMcpToolExecute&lt;InputSchema&gt;</code> &#8212; the execute function type: <code>(args: InferArgsFromInputSchema&lt;InputSchema&gt;, client: WebMcpClient) =&gt; unknown</code></p></li><li><p><code>WebMcpClient</code> &#8212; contains <code>signal: AbortSignal</code> for cancellation</p></li></ul><p><strong>When to use which:</strong></p><ul><li><p><code>provideWebMcpTools</code> &#8594; declarative, multiple tools at once, works in <code>providers</code> arrays</p></li><li><p><code>declareWebMcpTool</code> &#8594; imperative, single tool, useful inside constructors or <code>afterNextRender</code></p></li></ul><p>Both are marked <code>@experimental</code>. The JSON Schema types are intentionally not exported from <code>@angular/core</code> &#8212; if you need them for complex schemas, add <code>@mcp-b/webmcp-types</code> as a direct dependency.</p><p></p><h3>Language Tooling</h3><h4>a) Template inlay hints - <a href="https://github.com/angular/angular/commit/5a6d88626b604db937287a501cb723c088412a7e">PR</a></h4><p>The Angular Language Service now supports <strong>inlay hints</strong> in templates. These are the subtle inline annotations your editor shows for parameter names, types, and other contextual information. If you use VS Code with the Angular extension, you&#8217;ll start seeing type hints directly in your templates.</p><h4>b) Document Symbols for Angular templates - <a href="https://github.com/angular/angular/commit/cfd0f9950c08324e1c56f16d98a2e3081feeda58">PR</a></h4><p>The language service now provides Document Symbols for Angular templates. This means the <strong>Outline view</strong> in VS Code (and other editors) will show the structure of your template &#8212; components, directives, control flow blocks &#8212; making navigation in large templates much easier.</p><h4>c) Idle timeout support in defer blocks - <a href="https://github.com/angular/angular/commit/c6f98c723cdd2c209092927855f8cbaf63ecce30">PR</a></h4><p>The language service now understands the new idle timeout syntax in <code>@defer</code> blocks, providing proper autocompletion and diagnostics.</p><p></p><h3>Legacy / Upgrade</h3><h4>a) &#9888;&#65039; <code>getAngularLib</code>/<code>setAngularLib</code> removed - <a href="https://github.com/angular/angular/commit/01a179577b5a250f5801f6d9a04378aea73c4251">PR</a></h4><p>The deprecated <code>getAngularLib</code> and <code>setAngularLib</code> functions have been removed. Use <code>getAngularJSGlobal</code> and <code>setAngularJSGlobal</code> instead:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;095c7263-6c61-48c0-93c4-362fe0075ddb&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#10060;
import { getAngularLib, setAngularLib } from &#8216;@angular/upgrade/static&#8217;;

// After &#9989;
import { getAngularJSGlobal, setAngularJSGlobal } from &#8216;@angular/upgrade/static&#8217;;</code></pre></div><p></p><h2>&#128736;&#65039; CLI &amp; Build Tooling (<code>angular/angular-cli</code>)</h2><h3>@angular/cli</h3><h4>a) &#9889; Faster <code>ng add</code> with cached resolution - <a href="https://github.com/angular/angular-cli/commit/ad0fd5f41fc6ee5d920fb3c725f09f17d86d2ab4">PR</a> &amp; <a href="https://github.com/angular/angular-cli/commit/a39a33128fb56e9c65ea89e06c4f127252d3b220">PR</a></h4><p>Two performance improvements to <code>ng add</code>: redundant package version resolution is now avoided, and the root manifest is cached with restricted package exports resolved upfront. If you&#8217;ve ever noticed <code>ng add</code> being slow on large monorepos, this should help.</p><h4>b) MCP devserver custom port support - <a href="https://github.com/angular/angular-cli/commit/87d99e98b178c8f7d5e944a346faf70c51bdfcef">PR</a></h4><p>The MCP (Model Context Protocol) devserver start tool now supports a custom port. This is relevant if you&#8217;re using Angular&#8217;s AI tooling integration.</p><h4>c) Chunk Optimization Enabled by Default with Smart Heuristics - <a href="https://github.com/angular/angular-cli/commit/cd2ad3c1958d9adadc214045b5e59c0c8f8b4499">PR</a></h4><p>The advanced chunk optimization pass &#8212; which uses Rollup (or optionally Rolldown) to re-bundle lazy chunks after esbuild &#8212; is now enabled by default. Previously, you had to explicitly opt in via the <code>NG_BUILD_OPTIMIZE_CHUNKS</code> environment variable. That&#8217;s no longer the case.</p><p>Here&#8217;s the thing: not every project benefits from this. A small app with one or two lazy routes won&#8217;t see meaningful gains, and the extra build step just adds overhead. So the Angular team introduced a heuristic: the optimization only kicks in when your build produces <strong>3 or more lazy chunks</strong>. This threshold hits the sweet spot where chunk merging and deduplication actually pay off.</p><p>The heuristic counts <code>.js</code> files in the build output that aren&#8217;t part of the initial bundle. If that count meets the threshold, the optimization runs automatically. For most medium-to-large apps with multiple lazy-loaded routes, this means smaller total bundle sizes and fewer network requests &#8212; without you having to configure anything.</p><p>You can still control the behavior through the <code>NG_BUILD_OPTIMIZE_CHUNKS</code> environment variable:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;46b7541e-9a0f-458c-8f0e-a04b25cb8b87&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># Force optimization on regardless of chunk count
NG_BUILD_OPTIMIZE_CHUNKS=true ng build

# Disable optimization entirely
NG_BUILD_OPTIMIZE_CHUNKS=false ng build

# Set a custom threshold (e.g., only optimize when 5+ lazy chunks exist)
NG_BUILD_OPTIMIZE_CHUNKS=5 ng build</code></pre></div><p>What happens under the hood: after esbuild generates the initial chunks, Angular feeds them through Rollup (or Rolldown if you&#8217;ve opted into it via <code>NG_BUILD_CHUNKS_ROLLDOWN</code>). Rollup analyzes cross-chunk dependencies and merges small chunks that share code, reducing the total number of files the browser needs to fetch for lazy routes.</p><p>For a typical app with 10+ lazy routes, this can reduce the number of JS files by 30-50%, which translates directly to fewer HTTP requests and faster route transitions. The build time increase is minimal &#8212; usually under a second for the optimization pass itself.</p><h4>d) Quiet Option for Suppressing Build Noise in Unit Tests - <a href="https://github.com/angular/angular-cli/commit/0781f74986e773d143b9bdf5d3f0747de0df6b0a">PR</a></h4><p>A new <code>quiet</code> option has been added to <code>@angular/build</code> that suppresses build output noise during unit test runs. When running Vitest through the Angular builder, the build system now supports silencing the verbose compilation output that clutters test results.</p><p>This is a developer experience improvement. When you&#8217;re running tests, you want to see test results &#8212; not pages of build logs scrolling by. The <code>quiet</code> option lets the test runner focus the terminal output on what matters: which tests passed, which failed, and why.</p><h4>e) Stabilized Jasmine-to-Vitest Migration Schematic - <a href="https://github.com/angular/angular-cli/commit/de630c2fcee22622942478c98117d8f473d8c894">PR</a></h4><p>The <code>refactor-jasmine-vitest</code> schematic has been marked as stable. This schematic automates the migration from Jasmine test syntax to Vitest, which is now Angular&#8217;s recommended testing framework.</p><p>If you&#8217;ve been holding off on migrating your test suite because the schematic was experimental &#8212; that barrier is gone. You can now run it with confidence:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;9e24ddda-1657-44a1-aabf-b273bc3e8a86&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">ng generate @schematics/angular:refactor-jasmine-vitest</code></pre></div><p>The schematic handles the mechanical parts of the migration: updating imports, converting Jasmine-specific APIs to their Vitest equivalents, and adjusting configuration files. It won&#8217;t rewrite your test logic, but it takes care of the boilerplate changes that make manual migration tedious.</p><p>This stabilization signals that the Angular team considers the Vitest migration path production-ready. If you&#8217;re still on Karma + Jasmine, this is a good time to start planning the switch.</p><p></p><h3>@angular/build</h3><h4>a) Runtime Zone.js detection in Vitest - <a href="https://github.com/angular/angular-cli/commit/414320d02a090b7b36720051569cba7563bf9ac6">PR</a></h4><p>The Vitest unit test runner now supports runtime Zone.js detection. If your project still uses Zone.js, the test runner will detect it automatically &#8212; no manual configuration needed.</p><h4>b) &#9888;&#65039; <code>process.env.PORT</code> takes priority in dev server - <a href="https://github.com/angular/angular-cli/commit/fe720cab64bbc8bcc2db583188e32ad938e63a23">PR</a></h4><p>The <code>ng serve</code> dev server now gives <strong>highest priority</strong> to the <code>PORT</code> environment variable. This overrides both <code>angular.json</code> config and the <code>--port</code> CLI flag &#8212; including the default 4200.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;8c7931ad-f325-49ec-adbd-99c3d95b1abe&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript"># This now takes priority over everything
PORT=3000 ng serve
# Even if angular.json says port: 4200 and you pass --port 8080</code></pre></div><p>This is a <strong>breaking change</strong> if you rely on <code>--port</code> or <code>angular.json</code> to override an environment variable. The priority order is now: <code>PORT</code> env var &gt; <code>--port</code> flag &gt; <code>angular.json</code>.</p><h4>c) <code>experimentalPlatform</code> renamed to <code>platform</code> - <a href="https://github.com/angular/angular-cli/commit/af2c7e9444fba81d3b1fd2d37dc4412f8305b5ed">PR</a></h4><p>The <code>experimentalPlatform</code> option in the application builder has been renamed to <code>platform</code>. If you were using this option in your <code>angular.json</code>, update the key name.</p><h4>d) &#9888;&#65039; <code>istanbul-lib-instrument</code> is now an optional peer dependency - <a href="https://github.com/angular/angular-cli/commit/3007f46b7e077227b17d8bb1090edd2f8ff19ae3">PR</a></h4><p>If you&#8217;re using Karma with code coverage, you&#8217;ll need to ensure <code>istanbul-lib-instrument</code> is installed as a direct dependency. The <code>ng update</code> schematic handles this automatically.</p><h4>e) Subresource Integrity (SRI) validation for dynamically loaded modules - <a href="https://github.com/angular/angular-cli/commit/58c7c7a9d80fc6af5cf8b82a6d87f1d3cf3808c6">PR</a></h4><p>This is a security improvement. When you enable <code>subresourceIntegrity: true</code> in your build configuration, Angular already generates integrity hashes for scripts in <code>index.html</code>. But lazy-loaded chunks &#8212; the ones fetched at runtime via dynamic <code>import()</code> &#8212; weren&#8217;t validated.</p><p>Now they are. The build system generates integrity metadata for all lazy chunks, and the runtime validates them before execution. If a CDN or proxy tampers with a lazy-loaded module, the browser will reject it.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;7dd0abed-e7dd-4e76-b49c-b018d6af00e4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "architect": {
    "build": {
      "builder": "@angular/build:application",
      "options": {
        "subresourceIntegrity": true
      }
    }
  }
}</code></pre></div><p>What happens under the hood: the build emits a manifest mapping chunk filenames to their SHA-384 hashes. When Angular&#8217;s router triggers a lazy import, the runtime fetches the chunk and verifies its integrity against the manifest before evaluating it.</p><p>This closes a gap that existed since Angular adopted code splitting. Previously, <code>subresourceIntegrity</code> only protected the initial bundle &#8212; the scripts directly referenced in <code>index.html</code> with <code>integrity</code> attributes. Lazy chunks were unprotected. Now the entire application is covered.</p><p>For apps served behind CDNs or third-party proxies, this is a meaningful security hardening. It ensures that even if an intermediary modifies a JavaScript chunk in transit, the application will refuse to execute it rather than running potentially malicious code.</p><p></p><h3>@angular/ssr</h3><h4>a) &#9888;&#65039; SSR: No more CSR fallback for invalid hosts - <a href="https://github.com/angular/angular-cli/commit/27cd355619aad140dfc221c6bd161f4a981e0f3b">PR</a></h4><p>The Angular SSR server no longer falls back to Client-Side Rendering when a request fails host validation. Requests with unrecognized <code>Host</code> headers now return a <strong>400 Bad Request</strong>. Make sure all valid hosts are configured in the <code>allowedHosts</code> option.</p><p></p><h3>@schematics/angular</h3><h4>a) Strict templates default in generated workspaces - <a href="https://github.com/angular/angular-cli/commit/f98cc82eb0f46986e61b4f94b57dcd36e4eaf215">PR</a></h4><p>New workspaces generated with <code>ng new</code> now rely on the strict template default. This aligns with the migration that adds <code>strictTemplates</code> to existing projects during <code>ng update</code>.</p><h4>b) Karma-to-Vitest migration schematic - <a href="https://github.com/angular/angular-cli/commit/be60a63b7b8fdea26bfd3329ce54d321334db2d2">PR</a></h4><p>A new migration schematic that helps you move from Karma to Vitest. Run it during <code>ng update</code> to automatically update your test configuration.</p><h4>c) Migration to add <code>istanbul-lib-instrument - </code><a href="https://github.com/angular/angular-cli/commit/43505066e2350ca875051bb0f9991da8003c9247">PR</a></h4><p></p><h3>Removed packages</h3><h4>a) &#9888;&#65039; <code>@angular-devkit/architect-cli</code> removed - <a href="https://github.com/angular/angular-cli/commit/1f21e89d99c191642627df6842402644a1bf26ee">PR</a></h4><p>The <code>@angular-devkit/architect-cli</code> package is no longer available. The <code>architect</code> CLI tool has been moved to the <code>@angular-devkit/architect</code> package.</p><h4>b) &#9888;&#65039; Experimental Jest and Web Test Runner builders removed - <a href="https://github.com/angular/angular-cli/commit/b4885b851226709ae4146070122806e14a3d5eb9">PR</a></h4><p>The experimental <code>@angular-devkit/build-angular:jest</code> and <code>@angular-devkit/build-angular:web-test-runner</code> builders have been removed. Vitest is now the recommended test runner going forward.</p><p></p><h2>&#127912; Components &amp; CDK (<code>angular/components</code>)</h2><p></p><h2>&#128230; Migrations &amp; Schematics (cross-cutting)</h2><h4>a) Add <code>strictTemplates</code> to tsconfig during <code>ng update</code> &#8212; <code>angular/angular</code> - <a href="https://github.com/angular/angular/commit/682aaf943fea3d99f9f834b0bad4d165b4b28071">PR</a></h4><p>When you run <code>ng update</code>, Angular now automatically adds <code>strictTemplates: true</code> to your <code>tsconfig.json</code> if it&#8217;s not already there. This has been the recommended setting for a while, and now it&#8217;s enforced during migration.</p><h4>b) Migration schematic for <code>provideHttpClient</code> with XHR &#8212; <code>angular/angular </code>- <a href="https://github.com/angular/angular/commit/3bc095d508653982a48b337afd51bfedbbde1f87">PR</a></h4><p>Since <code>FetchBackend</code> is now the default, a migration schematic is provided to add <code>withXhr()</code> to your <code>provideHttpClient()</code> calls if your app relies on XHR-specific features (like upload progress).</p><h4>c) Migration for <code>ChangeDetectionStrategy.Eager</code> &#8212; <code>angular/angular</code></h4><p>Automatically adds <code>ChangeDetectionStrategy.Eager</code> to components that need it after the default changed to OnPush.</p><h4>d) Migration for <code>CanMatchFn</code> snapshot parameter &#8212; <code>angular/angular </code>- <a href="https://github.com/angular/angular/pull/67452">PR</a></h4><p>The <code>currentSnapshot</code> parameter in <code>CanMatchFn</code> and the <code>canMatch</code> method of the <code>CanMatch</code> interface is now <strong>required</strong>. While the Router already passed this at runtime, existing class implementations must now include the third argument to satisfy the interface. A migration schematic handles this automatically.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;1b16ef73-eefb-4d3a-8ade-1b083f258fea&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#10060; &#8212; third parameter was optional
canMatch(route: Route, segments: UrlSegment[]): boolean {
  return true;
}

// After &#9989; &#8212; currentSnapshot is now required
canMatch(route: Route, segments: UrlSegment[], currentSnapshot: ActivatedRouteSnapshot): boolean {
  return true;
}
</code></pre></div><h4>e) Karma-to-Vitest migration &#8212; <code>angular/angular-cli</code></h4><h4>f) Migration to add <code>istanbul-lib-instrument</code> &#8212; <code>angular/angular-cli</code></h4><h4>g) Auto-Disable nullishCoalescingNotNullable &amp; optionalChainNotNullable on ng update - <a href="https://github.com/angular/angular/pull/68080">PR</a></h4><p>When you run <code>ng update</code> to upgrade to this version, the migration will automatically add suppression rules for the <code>nullishCoalescingNotNullable</code> and <code>optionalChainNotNullable</code> diagnostics to your <code>tsconfig.json</code>.</p><p>This is the companion migration for the breaking compiler change described above. The idea is simple: don&#8217;t block your update with hundreds of new warnings. The migration suppresses them so you can update cleanly, then you can address the warnings incrementally.</p><p>After updating, you&#8217;ll find this in your <code>tsconfig.json</code>:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;2b6fc2c3-e288-4359-b824-d6155666507b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "nullishCoalescingNotNullable": "suppress",
        "optionalChainNotNullable": "suppress"
      }
    }
  }
}</code></pre></div><p>Once you&#8217;ve cleaned up the unnecessary <code>?.</code> and <code>??</code> operators in your templates, remove these suppressions to benefit from the stricter type checking going forward.</p><h4>h) Auto-Add <code>strictTemplates</code> to tsconfig During ng update - <a href="https://github.com/angular/angular/commit/682aaf943fea3d99f9f834b0bad4d165b4b28071">PR</a></h4><p>When you run <code>ng update</code> to upgrade to Angular 22, a new migration will automatically add <code>strictTemplates: false</code> to your <code>tsconfig.json</code> under <code>angularCompilerOptions</code> &#8212; but only if it&#8217;s not already set.</p><p>Why <code>false</code>? Because Angular 22 is making <code>strictTemplates: true</code> the default compiler behavior. Projects that haven&#8217;t explicitly opted into strict templates would suddenly get a wave of new type-checking errors in their templates. The migration preserves the previous implicit behavior by making it explicit, so your update doesn&#8217;t break anything.</p><p>The migration is smart about it:</p><ul><li><p>If <code>strictTemplates</code> is already set (to <code>true</code> or <code>false</code>), it leaves it alone</p></li><li><p>If <code>compilerOptions</code> is empty or missing, it skips the file (not a real tsconfig)</p></li><li><p>If <code>angularCompilerOptions</code> doesn&#8217;t exist, it creates the section with <code>strictTemplates: false</code></p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;307678af-4bb4-4093-86a7-8739aee40f14&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">// What the migration adds to your tsconfig.json
{
  "compilerOptions": {
    "target": "es2022"
    // ... your existing options
  },
  "angularCompilerOptions": {
    "strictTemplates": false  // &#8592; added by migration
  }
}</code></pre></div><p>After updating, you can (and should) work toward enabling <code>strictTemplates: true</code> at your own pace. Strict template type checking catches real bugs &#8212; wrong property names, type mismatches in bindings, missing inputs &#8212; that would otherwise only surface at runtime. But the migration ensures you&#8217;re not forced into it during the update itself.</p><h4>i) Model + Output conflict migration - <a href="https://github.com/angular/angular/pull/67349">PR</a></h4><p>When Angular introduced <code>model()</code> signals, it implicitly created a <code>&lt;name&gt;Change</code> output for two-way binding. But some codebases already had explicit <code>output()</code> declarations with that same name &#8212; creating a conflict where the component had duplicate outputs.</p><p>This migration automatically detects the pattern and converts it to <code>input()</code> + <code>linkedSignal()</code>, which preserves the reactive behavior without the naming collision.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;21fabacf-b3d8-40e5-878f-125cc0f25307&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before &#8212; broken duplicate outputs &#10060;
@Component({
  selector: 'app-slider',
  template: '',
})
export class SliderComponent {
  foo = model(0);              // implicitly creates fooChange output
  fooChange = output&lt;number&gt;(); // explicit output &#8212; CONFLICT!
}

// After migration &#9989;
@Component({
  selector: 'app-slider',
  template: '',
})
export class SliderComponent {
  fooInput = input(0, { alias: 'foo' });
  foo = linkedSignal(this.fooInput);
  fooChange = output&lt;number&gt;();
}</code></pre></div><p>The migration handles several cases:</p><ul><li><p>Preserves access modifiers (<code>public</code>, <code>protected</code>, <code>readonly</code>)</p></li><li><p>Handles generic types (<code>model&lt;string&gt;('initial')</code> &#8594; <code>input&lt;string&gt;('initial', {alias: 'bar'})</code>)</p></li><li><p>Adds <code>input</code> and <code>linkedSignal</code> to the import statement automatically</p></li><li><p>Only triggers when there&#8217;s an actual naming conflict &#8212; if your <code>model()</code> doesn&#8217;t have a conflicting explicit output, nothing changes</p></li></ul><p>This runs automatically during <code>ng update</code> to Angular 22.</p><h4>j) Migrate <code>fakeAsync</code> to Vitest fake timers - <a href="http://PR">PR</a></h4><p>The schematic now automatically converts <code>fakeAsync</code>/<code>tick</code>/<code>flush</code> patterns to Vitest&#8217;s native fake timer APIs. Angular&#8217;s <code>fakeAsync</code> was a Zone.js-powered utility &#8212; in a zoneless Vitest world, you use <code>vi.useFakeTimers()</code> and <code>vi.advanceTimersByTime()</code> instead.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;ae194f15-1b66-4033-9a98-52b5723dcdda&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">// Before (Jasmine + Zone.js) &#10060;
it('should debounce input', fakeAsync(() =&gt; {
  component.onSearch('angular');
  tick(300);
  expect(component.results().length).toBeGreaterThan(0);
}));

// After (Vitest) &#9989;
it('should debounce input', () =&gt; {
  vi.useFakeTimers();
  component.onSearch('angular');
  vi.advanceTimersByTime(300);
  expect(component.results().length).toBeGreaterThan(0);
  vi.useRealTimers();
});</code></pre></div><h4>k) Migrate <code>fakeAsync</code>&#8216;s <code>flush</code> behavior in <code>beforeEach</code> - <a href="https://github.com/angular/angular-cli/commit/d2aa9ede55a3e16b61ce6ae60dba6c8ea8954358">PR</a></h4><p>When <code>fakeAsync</code> with <code>flush()</code> is used inside <code>beforeEach</code> blocks, the schematic now correctly transforms it. This is a common pattern where setup code needs to wait for async initialization to complete before each test.</p><h4>l) Set up fake timers in <code>beforeEach</code> instead of <code>beforeAll</code> - <a href="https://github.com/angular/angular-cli/commit/c9f4081533f6f114846b88a152a9d5dc7363d680">PR</a></h4><p>The schematic ensures fake timers are set up in <code>beforeEach</code> (per-test isolation) rather than <code>beforeAll</code> (shared across tests). This prevents timer state from leaking between tests &#8212; a common source of flaky test suites.</p><h4>m) Update TSConfig globals during migration - <a href="https://github.com/angular/angular-cli/commit/8d0805dd1750cb16af620811dc01b40e46ad030e">PR</a></h4><p>When migrating from Karma to Vitest, the schematic now updates your <code>tsconfig.spec.json</code> to include Vitest&#8217;s global type definitions. This ensures TypeScript recognizes <code>vi</code>, <code>describe</code>, <code>it</code>, <code>expect</code> and other Vitest globals without manual configuration.</p><h4>n) Conditionally install istanbul coverage provider - <a href="https://github.com/angular/angular-cli/commit/b2f7a038b4a321e4e1b0b340cd09425f948c77ad">PR</a></h4><p>During the Karma-to-Vitest migration, the schematic now checks if your project uses code coverage and conditionally installs <code>@vitest/coverage-istanbul</code>. This ensures your existing coverage pipeline (Codecov, SonarQube, etc.) continues working without manual intervention.</p><p></p><h2><strong>&#9888;&#65039; Breaking Changes Summary</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HS3g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HS3g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 424w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 848w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 1272w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HS3g!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png" width="1200" height="3166.1107404936624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:3955,&quot;width&quot;:1499,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:988932,&quot;alt&quot;:&quot;Angular 22 Breaking Changes 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/193433490?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d527c03-1c65-4ec0-85bb-7f25b41d9fb8_1499x4236.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22 Breaking Changes Summary" title="Angular 22 Breaking Changes Summary" srcset="https://substackcdn.com/image/fetch/$s_!HS3g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 424w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 848w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.png 1272w, https://substackcdn.com/image/fetch/$s_!HS3g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fce944252-42d4-4494-b37b-1f63e5840a2b_1499x3955.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>&#9888;&#65039; Deprecations Summary</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tJAm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tJAm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 424w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 848w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 1272w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tJAm!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png" width="1200" height="657.9075425790754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:676,&quot;width&quot;:1233,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:105424,&quot;alt&quot;:&quot;Angular 22 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/193433490?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22 Deprecations summary" title="Angular 22 Deprecations summary" srcset="https://substackcdn.com/image/fetch/$s_!tJAm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 424w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 848w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.png 1272w, https://substackcdn.com/image/fetch/$s_!tJAm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8658bd40-686d-4693-b2aa-7052cc2f2f48_1233x676.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><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>. &#128079;</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>LinkedIn</strong></p></li><li><p>&#128221; <strong>Medium</strong></p></li><li><p>&#127909; <strong>YouTube</strong></p></li><li><p>&#128038; <strong>Twitter</strong></p></li></ul><p>Thanks for being part of this Angular journey! &#128075;&#128513;</p>]]></content:encoded></item><item><title><![CDATA[injectAsync: Angular's Native Solution for Lazy-Loading Services]]></title><description><![CDATA[Angular 22 ships injectAsync &#8212; a first-party helper that lets you lazily load services through the DI system.]]></description><link>https://www.codigotipado.com/p/injectasync-angulars-native-solution</link><guid isPermaLink="false">https://www.codigotipado.com/p/injectasync-angulars-native-solution</guid><dc:creator><![CDATA[Amos Isaila]]></dc:creator><pubDate>Wed, 06 May 2026 05:38:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!T9k1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_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_!T9k1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T9k1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T9k1!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/af61d742-fc91-4eb2-92d4-d0019c623769_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;:489803,&quot;alt&quot;:&quot;Angular 22: injectAsync:  Lazy-Loading Services&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/196620785?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="Angular 22: injectAsync:  Lazy-Loading Services" title="Angular 22: injectAsync:  Lazy-Loading Services" srcset="https://substackcdn.com/image/fetch/$s_!T9k1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!T9k1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf61d742-fc91-4eb2-92d4-d0019c623769_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 used <code>ngxtension/inject-lazy</code>, this will feel familiar. But the API is different, and the trade-offs matter.</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</h2><p>Bundle size. You have a <code>ChartService</code> that pulls in D3, a <code>PdfService</code> that imports pdfjs, or an <code>AnalyticsService</code> that loads a heavy SDK. These services aren&#8217;t needed on initial render &#8212; maybe they&#8217;re triggered by a button click, a route guard, or a conditional feature flag.</p><p>With <code>providedIn: 'root'</code>, the service class and its dependencies end up in the main bundle regardless of whether the user ever triggers the code path that uses them.</p><p>Lazy routes solve this for route-level code splitting. But what about services used <em>within</em> a component that&#8217;s already loaded?</p><h2>Before <code>injectAsync</code>: The Manual Approach</h2><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;2f285e19-ebd0-44d6-8f35-105f3869ad03&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">@Component({
  selector: 'app-report',
  template: `&lt;button (click)="generate()"&gt;Generate PDF&lt;/button&gt;`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReportComponent {
  private _injector = inject(EnvironmentInjector);

  async generate(): Promise&lt;void&gt; {
    const { PdfService } = await import('./pdf.service');
    const pdfService = this._injector.get(PdfService);
    pdfService.createReport();
  }
}</code></pre></div>
      <p>
          <a href="https://www.codigotipado.com/p/injectasync-angulars-native-solution">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><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[Angular 22: Signal Forms Stable API]]></title><description><![CDATA[Angular 21 introduces one of the most significant improvements to form handling since the framework's inception: Signal-Based Forms (Introduced as experimental in Angular v21).]]></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></channel></rss>