<?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"><channel><title><![CDATA[Codes With Rajat]]></title><description><![CDATA[Code With Rajat simplifies modern web development with practical tutorials, real-world insights, and best practices in Angular, frontend, and scalable architect]]></description><link>https://hashnode.rajatmalik.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1756358366530/09601be7-d7f3-40e6-8219-e8ec29b6516a.webp</url><title>Codes With Rajat</title><link>https://hashnode.rajatmalik.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 10:23:12 GMT</lastBuildDate><atom:link href="https://hashnode.rajatmalik.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[10 Game-Changing Decorator Patterns Every JavaScript Developer Should Know]]></title><description><![CDATA[Ever looked at @Component, @Injectable, or @Input in Angular and wondered — "Can I make my own decorator magic?"
If yes, you're about to unlock one of the most elegant and underused features in TypeSc]]></description><link>https://hashnode.rajatmalik.dev/10-game-changing-decorator-patterns-every-javascript-developer-should-know</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/10-game-changing-decorator-patterns-every-javascript-developer-should-know</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 24 Mar 2026 13:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/68afdd25985df6aa5807fb7b/76a738b8-33d6-4e35-87e0-9fb89e6ef388.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Ever looked at @Component, @Injectable, or @Input in Angular and wondered — "Can I make my own decorator magic?"</p>
<p>If yes, you're about to unlock one of the most elegant and underused features in TypeScript: <strong>decorators</strong>.</p>
</blockquote>
<p>This article dives deep into <strong>10 practical and powerful decorator patterns</strong>, not just theory—but <strong>hands-on, demo-style code</strong> you can apply right now in your Angular or vanilla TypeScript projects. We’ll cover class decorators, method decorators, property decorators, and accessor decorators with clarity and purpose.</p>
<p>At the end, you’ll know how to:</p>
<ul>
<li>Create and apply custom decorators</li>
<li>Use decorators to inject behavior, log automatically, validate data, and memoize functions</li>
<li>Write cleaner, DRY-er, and scalable Angular code</li>
<li>Think like an advanced frontend developer</li>
</ul>
<p>Let’s get started.</p>
<hr />
<h2>1. <strong>Logging Methods Automatically with a Method Decorator</strong></h2>
<pre><code class="language-jsx">function LogMethod(
  target: Object,
  propertyName: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyName} with`, args);
    const result = original.apply(this, args);
    console.log(`Returned from ${propertyName} with`, result);
    return result;
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class MathService {
  @LogMethod
  add(a: number, b: number): number {
    return a + b;
  }
}

new MathService().add(2, 3); // Logs input and output
</code></pre>
<hr />
<h2>2. <strong>Property Validation Decorator</strong></h2>
<pre><code class="language-jsx">function Required(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = () =&gt; value;
  const setter = (newVal: any) =&gt; {
    if (!newVal) {
      throw new Error(`${propertyKey} is required.`);
    }
    value = newVal;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
  });
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class User {
  @Required
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

new User(""); // Throws error
</code></pre>
<hr />
<h2>3. <strong>Creating a Custom Angular Service Decorator</strong></h2>
<pre><code class="language-jsx">export function SingletonService() {
  return function (target: any) {
    Reflect.defineMetadata('singleton', true, target);
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">@SingletonService()
@Injectable({ providedIn: 'root' })
export class MyService {}
</code></pre>
<hr />
<h2>4. <strong>Memoize Expensive Computations</strong></h2>
<pre><code class="language-jsx">function Memoize(_: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  const cache = new Map();

  descriptor.value = function (...args: any[]) {
    const hash = JSON.stringify(args);
    if (cache.has(hash)) {
      return cache.get(hash);
    }
    const result = original.apply(this, args);
    cache.set(hash, result);
    return result;
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class MathEngine {
  @Memoize
  fib(n: number): number {
    if (n &lt;= 1) return n;
    return this.fib(n - 1) + this.fib(n - 2);
  }
}
</code></pre>
<hr />
<h2>5. <strong>Role-Based Access Control</strong></h2>
<pre><code class="language-jsx">function Role(role: string) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const userRole = 'admin'; // Mock example
      if (userRole !== role) {
        throw new Error(`Unauthorized access to ${key}`);
      }
      return original.apply(this, args);
    };
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class AdminPanel {
  @Role('admin')
  deleteUser(userId: string) {
    console.log(`User ${userId} deleted`);
  }
}
</code></pre>
<hr />
<h2>6. <strong>Debounce Function Calls</strong></h2>
<pre><code class="language-jsx">function Debounce(ms: number) {
  return function (_: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    let timeout: any;

    descriptor.value = function (...args: any[]) {
      clearTimeout(timeout);
      timeout = setTimeout(() =&gt; original.apply(this, args), ms);
    };
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class SearchBar {
  @Debounce(300)
  onInputChange(value: string) {
    console.log('Search for', value);
  }
}
</code></pre>
<hr />
<h2>7. <strong>Readonly Properties with a Property Decorator</strong></h2>
<pre><code class="language-jsx">function Readonly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false,
  });
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class Settings {
  @Readonly
  appName = 'MyApp';
}
</code></pre>
<hr />
<h2>8. <strong>Auto-Bind Method to Context</strong></h2>
<pre><code class="language-jsx">function Autobind(_: any, _2: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  return {
    configurable: true,
    get() {
      return original.bind(this);
    },
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class ButtonHandler {
  @Autobind
  onClick() {
    console.log('Clicked by', this);
  }
}
</code></pre>
<hr />
<h2>9. <strong>Custom @Component Wrapper in Angular</strong></h2>
<pre><code class="language-jsx">export function EnhancedComponent(metadata: Component) {
  return function (target: any) {
    Component(metadata)(target);
    // Add your enhancements here
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">@EnhancedComponent({
  selector: 'app-enhanced',
  template: `&lt;p&gt;Enhanced Component&lt;/p&gt;`,
})
export class EnhancedComponentDemo {}
</code></pre>
<hr />
<h2>10. <strong>Custom Date Formatter Decorator</strong></h2>
<pre><code class="language-jsx">function FormatDate(_: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.get;

  descriptor.get = function () {
    const result = original!.apply(this);
    return new Date(result).toLocaleDateString();
  };
}
</code></pre>
<h3>Usage:</h3>
<pre><code class="language-jsx">class Invoice {
  private rawDate = '2025-05-24T12:00:00Z';

  @FormatDate
  get createdAt() {
    return this.rawDate;
  }
}
</code></pre>
<hr />
<h2>Final Thoughts</h2>
<p>Decorators aren’t just for Angular—they’re a <strong>powerful metaprogramming tool</strong> available to all TypeScript developers. Whether you're streamlining UI logic in Angular, reducing boilerplate, or creating testable, reusable utility patterns, decorators can take your code from good to great.</p>
<hr />
<h3>🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3>🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3>🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li>💼 <a href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</li>
<li>🎥 <a href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</li>
<li>🐦 <a href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</li>
<li>👥 <a href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</li>
<li>🌟 <a href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</li>
<li>🌐 <a href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</li>
<li>📚 <a href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</li>
<li>💬 <a href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</li>
<li>✉️ <a href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</li>
<li>🧩 <a href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</li>
<li>✍️ <a href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions  </li>
<li>✍️ <a href="https://www.reddit.com/user/codewithrajat/submitted/"><strong>Reddit</strong></a> — Developer blog posts &amp; tech discussions</li>
</ul>
<hr />
<h3>🎉 If you found this article valuable:</h3>
<ul>
<li>Leave a <strong>👏 Clap</strong></li>
<li>Drop a <strong>💬 Comment</strong></li>
<li>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠🚀</p>
<p><a href="https://forms.gle/mdfXEVh3AxYwAsKw5"><strong>✨ Share Your Thoughts To 📣 Set Your Notification Preference</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[withInMemoryScrolling in Angular: Modern Scroll Restoration and Anchor Scrolling Explained]]></title><description><![CDATA[Single-page applications (SPAs) fundamentally changed web navigation by eliminating full page reloads. However, this architectural shift introduced a UX regression: browsers lost their native ability to restore scroll positions when users navigate ba...]]></description><link>https://hashnode.rajatmalik.dev/withinmemoryscrolling-in-angular-modern-scroll-restoration-and-anchor-scrolling-explained</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/withinmemoryscrolling-in-angular-modern-scroll-restoration-and-anchor-scrolling-explained</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 14 Jan 2026 13:30:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768136383738/c568402e-2cf0-4df0-994d-1e29add4e5c5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Single-page applications (SPAs) fundamentally changed web navigation by eliminating full page reloads. However, this architectural shift introduced a UX regression: browsers lost their native ability to restore scroll positions when users navigate backward or forward through history. In traditional multi-page applications, the browser automatically remembers where you scrolled on a page and returns you to that exact position when you hit the back button. SPAs broke this behavior.</p>
<p>Angular’s <code>withInMemoryScrolling</code> feature addresses this gap by giving the router control over scroll behavior during navigation. It provides declarative configuration for two critical scroll-related features: scroll position restoration across route changes and fragment-based anchor scrolling.</p>
<p>Developers should care about this feature when building applications where users frequently navigate between routes and expect intuitive scroll behavior — particularly in content-heavy applications, e-commerce platforms with list-detail patterns, dashboards with tabbed interfaces, or any application where preserving navigation context improves usability.</p>
<h3 id="heading-what-is-withinmemoryscrolling">What is withInMemoryScrolling</h3>
<p><code>withInMemoryScrolling</code> is a router feature function introduced in Angular’s standalone API era that enables the router to manage scroll behavior during navigation events. The “in-memory” designation refers to how Angular stores scroll positions: it maintains a map of scroll positions in memory, keyed by navigation state, rather than relying on browser-native mechanisms.</p>
<p>At the router level, this works through integration with Angular’s navigation lifecycle. When a navigation begins, the router captures the current scroll position. When a navigation completes, it determines whether to restore a previous position, scroll to top, scroll to an anchor, or leave the scroll position unchanged. This decision is made based on the configuration options and the navigation context (forward navigation, back navigation, route reload, etc.).</p>
<p>The critical difference between browser default scrolling and Angular router-controlled scrolling lies in control and consistency. Browser scroll restoration works at the document level and can be unreliable in SPAs where the DOM is dynamically replaced. Angular’s approach operates within the application’s routing context, providing deterministic behavior that accounts for lazy loading, route transitions, and application-specific navigation patterns.</p>
<h3 id="heading-configuration-in-modern-angular">Configuration in Modern Angular</h3>
<p>In modern Angular applications using standalone components and the <code>provideRouter</code> API, <code>withInMemoryScrolling</code> is configured as a router feature alongside other router capabilities.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { ApplicationConfig } <span class="hljs-keyword">from</span> ‘<span class="hljs-meta">@angular</span>/core’;
<span class="hljs-keyword">import</span> { provideRouter, withInMemoryScrolling } <span class="hljs-keyword">from</span> ‘<span class="hljs-meta">@angular</span>/router’;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> ‘./app.routes’;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withInMemoryScrolling({
        scrollPositionRestoration: ‘enabled’,
        anchorScrolling: ‘enabled’
      })
    )
  ]
};
</code></pre>
<p>In the standalone bootstrap approach:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { bootstrapApplication } <span class="hljs-keyword">from</span> ‘<span class="hljs-meta">@angular</span>/platform-browser’;
<span class="hljs-keyword">import</span> { provideRouter, withInMemoryScrolling } <span class="hljs-keyword">from</span> ‘<span class="hljs-meta">@angular</span>/router’;
<span class="hljs-keyword">import</span> { AppComponent } <span class="hljs-keyword">from</span> ‘./app/app.component’;
<span class="hljs-keyword">import</span> { routes } <span class="hljs-keyword">from</span> ‘./app/app.routes’;

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(
      routes,
      withInMemoryScrolling({
        scrollPositionRestoration: ‘enabled’,
        anchorScrolling: ‘enabled’
      })
    )
  ]
});
</code></pre>
<p>The <code>withInMemoryScrolling</code> function accepts a configuration object with two primary options:</p>
<p><strong>scrollPositionRestoration</strong>: Controls how scroll positions are managed during navigation. Accepts <code>'disabled'</code>, <code>'enabled'</code>, or <code>'top'</code>.</p>
<p><strong>anchorScrolling</strong>: Enables or disables automatic scrolling to fragment identifiers in URLs. Accepts <code>'enabled'</code> or <code>'disabled'</code>.</p>
<h3 id="heading-scroll-restoration-strategies">Scroll Restoration Strategies</h3>
<h3 id="heading-disabled">‘disabled’</h3>
<p>This strategy completely disables Angular’s scroll management. The scroll position remains wherever it was when navigation occurs. This is the default behavior if <code>withInMemoryScrolling</code> is not configured.</p>
<p>Use cases for <code>'disabled'</code>:</p>
<ul>
<li><p>Applications implementing custom scroll behavior</p>
</li>
<li><p>Infinite scroll implementations that manage their own position</p>
</li>
<li><p>Single-route applications where scroll restoration isn’t relevant</p>
</li>
<li><p>Performance-critical applications minimizing router overhead</p>
</li>
</ul>
<p>UX implications: Users navigating back will stay at the top of the previous page rather than returning to their prior position. This can be disorienting in list-detail patterns.</p>
<pre><code class="lang-typescript">withInMemoryScrolling({
  scrollPositionRestoration: ‘disabled’
})
</code></pre>
<h3 id="heading-enabled">‘enabled’</h3>
<p>This strategy restores scroll positions when navigating backward or forward through browser history, but scrolls to the top on new forward navigations. This mimics traditional browser behavior in a SPA context.</p>
<p>Use cases for <code>'enabled'</code>:</p>
<ul>
<li><p>E-commerce platforms with product listings</p>
</li>
<li><p>Content management systems</p>
</li>
<li><p>Any list-to-detail navigation pattern</p>
</li>
<li><p>Applications where users browse and return frequently</p>
</li>
</ul>
<p>UX implications: Provides intuitive navigation experience consistent with traditional websites. Users expect to return to their scroll position when hitting back, which this strategy delivers.</p>
<pre><code class="lang-typescript">withInMemoryScrolling({
  scrollPositionRestoration: ‘enabled’
})
</code></pre>
<h3 id="heading-top">‘top’</h3>
<p>This strategy always scrolls to the top of the page on every navigation, regardless of direction.</p>
<h3 id="heading-read-the-complete-article-on-medium">Read the Complete Article on Medium</h3>
<p>This post is a <strong>condensed overview</strong> of Angular’s <code>withInMemoryScrolling</code> feature.</p>
<p>The <strong>full, in-depth article on Medium</strong> includes:</p>
<ul>
<li><p>Complete explanation of scroll restoration in SPAs</p>
</li>
<li><p>Angular Router lifecycle and internal behavior</p>
</li>
<li><p>Real-world use cases (list → detail → back, dashboards, content pages)</p>
</li>
<li><p>Edge cases (lazy loading, SSR, hydration, virtual scroll conflicts)</p>
</li>
<li><p>Performance and accessibility considerations</p>
</li>
<li><p>Step-by-step demo walkthrough</p>
</li>
<li><p>Sample code and configuration examples</p>
</li>
</ul>
<p><strong>Read the full article, explore the demo, and access the complete code here:</strong><br /><a target="_blank" href="https://medium.com/javascript-in-plain-english/withinmemoryscrolling-in-angular-modern-scroll-restoration-and-anchor-scrolling-explained-c1974da4bf6c">Read Full Article</a></p>
<p>**🎯 Your Turn, Devs!</p>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I’d love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://www.reddit.com/user/codewithrajat/submitted/"><strong>Reddit</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Why Does Your Angular Component State Reset When You Move It? A Deep Dive into CDK Portals]]></title><description><![CDATA[Have you ever moved a component from one part of your Angular layout to another, only to watch all its state disappear? The timer resets, the form clears, the toggle switches back—everything vanishes. Frustrating, right?
This isn't a bug. It's how An...]]></description><link>https://hashnode.rajatmalik.dev/why-does-your-angular-component-state-reset-when-you-move-it-a-deep-dive-into-cdk-portals</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/why-does-your-angular-component-state-reset-when-you-move-it-a-deep-dive-into-cdk-portals</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[TechBlogs]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 07 Jan 2026 13:30:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767503223922/d425a8bd-07dc-407d-8f05-ad2c9642d203.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever moved a component from one part of your Angular layout to another, only to watch all its state disappear? The timer resets, the form clears, the toggle switches back—everything vanishes. Frustrating, right?</p>
<p>This isn't a bug. It's how Angular handles component rendering. But here's the thing: most developers don't realize there are multiple ways to move components in Angular, and they behave very differently. Some recreate your component from scratch. Others actually move the live instance, preserving all state.</p>
<p>In this article, we'll explore three approaches to moving components across your layout: <code>ngTemplateOutlet</code>, CDK Template Portal, and CDK DOM Portal. By the end, you'll understand exactly when Angular recreates your views and how to preserve component state when moving elements across your application.</p>
<p>Let's dive into a real-world scenario and see what's actually happening under the hood.</p>
<hr />
<h3 id="heading-the-problem-moving-components-kills-state"><strong>The Problem: Moving Components Kills State</strong></h3>
<p>Imagine you're building an admin dashboard with a promotional banner. This banner has interactive elements: a heart button users can click and a live timer counting up. Initially, the banner displays in the sidebar, but users can click a button to move it to the main content area.</p>
<p>Here's what happens with a naive implementation:</p>
<pre><code class="lang-tsx">import { Component, signal } from '@angular/core';
import { PromoBannerComponent } from './promo-banner.component';

@Component({
  selector: 'app-root',
  imports: [PromoBannerComponent],
   styles: [
    `
  .bottom {
    display: flex;
  justify-content: center
  align-items: center;
  height: 100vh; 
  }
`,
  ],
  template: `
    &lt;div class="dashboard"&gt;
      &lt;header&gt;
        &lt;button (click)="toggleRegion()"&gt;Toggle Banner Position&lt;/button&gt;
      &lt;/header&gt;

      &lt;div class="layout"&gt;
        @if (dockRight()) {
          &lt;main&gt;
            &lt;app-promo-banner /&gt;
          &lt;/main&gt;
        }

         &lt;aside class="bottom"&gt;
          @if (!dockRight()) {
            &lt;app-promo-banner /&gt;
          }
        &lt;/aside&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `
})
export class AppComponent {
  dockRight = signal(false);

  toggleRegion() {
    this.dockRight.update(value =&gt; !value);
  }
}
</code></pre>
<p>When you click the toggle button, the banner moves—but the heart button resets and the timer starts over from zero. Why?</p>
<p>Because you're not actually moving the component. You're destroying it in one location and creating a brand new instance in another. The conditional rendering removes the component from the DOM entirely, then Angular initializes a fresh instance elsewhere.</p>
<p>Think of it like moving houses. You're not picking up your house and relocating it—you're demolishing it, then building an identical new house somewhere else. Everything inside gets lost in the process.</p>
<p>Can we do better? Let's explore three different approaches.</p>
<hr />
<h3 id="heading-approach-1-using-ngtemplateoutlet"><strong>Approach 1: Using ngTemplateOutlet</strong></h3>
<p>The first instinct might be to use Angular's <code>ngTemplateOutlet</code> directive. This lets you define a template once and stamp it out in multiple locations. Sounds promising, right?</p>
<p>Here's how you'd implement it:</p>
<pre><code class="lang-tsx">import { Component, signal } from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { PromoBannerComponent } from './promo-banner.component';

@Component({
  selector: 'app-root',
  imports: [NgTemplateOutlet, PromoBannerComponent],
  styles: [
    `
  .bottom {
    display: flex;
  justify-content: center
  align-items: center;
  height: 100vh; 
  }
`,
  ],
  template: `
    &lt;div class="dashboard"&gt;
      &lt;header&gt;
        &lt;button (click)="toggleRegion()"&gt;Toggle Banner Position&lt;/button&gt;
      &lt;/header&gt;

      &lt;ng-template #promoBanner&gt;
        &lt;app-promo-banner /&gt;
      &lt;/ng-template&gt;

      &lt;div class="layout"&gt;
        @if (dockRight()) {
          &lt;main&gt;
            &lt;ng-template [ngTemplateOutlet]="promoBanner" /&gt;
          &lt;/main&gt;
        }

         &lt;aside class="bottom"&gt;
          @if (!dockRight()) {
            &lt;ng-template [ngTemplateOutlet]="promoBanner" /&gt;
           }
          &lt;/aside&gt;        
      &lt;/div&gt;
    &lt;/div&gt;
  `
})
export class AppComponent {
  dockRight = signal(false);

  toggleRegion() {
    this.dockRight.update(value =&gt; !value);
  }
}
</code></pre>
<p>You define the banner component once inside an <code>ng-template</code> with a template reference variable. Then you use <code>ngTemplateOutlet</code> to render it in either location based on the condition.</p>
<p>This feels like it should work. You're reusing the same template definition, so surely the state should persist?</p>
<p>Unfortunately, no. The state still resets when you toggle the position. Click the heart, move the banner, and watch it reset.</p>
<p>Here's why: <code>ngTemplateOutlet</code> creates a new embedded view each time it attaches. You're reusing the template definition, but Angular still creates a fresh component instance every time. New view equals new component instance equals new state.</p>
<p>It's like having a blueprint for a house. Sure, you're using the same blueprint in both locations, but you're still building a new house from scratch each time.</p>
<hr />
<h3 id="heading-approach-2-cdk-template-portal"><strong>Approach 2: CDK Template Portal</strong></h3>
<p>Angular CDK provides a Portal module with more sophisticated ways to move content. Let's try the Template Portal approach.</p>
<p>First, you need to install Angular CDK if you haven't already:</p>
<pre><code class="lang-bash">npm install @angular/cdk
</code></pre>
<p>Now let's refactor our component to use a Template Portal:</p>
<p><strong>The complete, code-heavy deep dive (with diagrams, explanations, and tests) is published.</strong></p>
<p><a target="_blank" href="https://medium.com/javascript-in-plain-english/why-does-your-angular-component-state-reset-when-you-move-it-a-deep-dive-into-cdk-portals-28d99c9b963f"><strong>Read the full article</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Migrating from Jasmine/Karma to Vitest in Angular 21: A Step-by-Step Guide Developer's Complete Guide]]></title><description><![CDATA[Introduction
Are you still waiting minutes for your Angular test suite to run? What if I told you that you could cut that time by 50% or more?
With Angular 21, the framework has officially moved away from Jasmine and Karma in favor of Vitest as the d...]]></description><link>https://hashnode.rajatmalik.dev/migrating-from-jasminekarma-to-vitest-in-angular-21-a-step-by-step-guide-developers-complete-guide</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/migrating-from-jasminekarma-to-vitest-in-angular-21-a-step-by-step-guide-developers-complete-guide</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 23 Dec 2025 13:30:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1766214292575/45d20061-cdcf-4362-bb39-31076fb50ca0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Are you still waiting minutes for your Angular test suite to run? What if I told you that you could cut that time by 50% or more?</p>
<p>With Angular 21, the framework has officially moved away from Jasmine and Karma in favor of Vitest as the default testing framework. If you’re working on an existing Angular project, you might be wondering: “How do I migrate without breaking everything?”</p>
<p>In this guide, you’ll learn:</p>
<ul>
<li><p>How to manually configure Vitest in your existing Angular project</p>
</li>
<li><p>Step-by-step migration from Jasmine/Karma to Vitest</p>
</li>
<li><p>Common pitfalls and how to avoid them</p>
</li>
<li><p>Advanced configuration tips for real browser testing</p>
</li>
<li><p>How to write modern unit tests with the latest Angular syntax</p>
</li>
</ul>
<p>By the end of this article, you’ll have a fully functional Vitest setup that runs faster, provides better developer experience, and supports modern testing patterns.</p>
<p>Let’s dive in.</p>
<hr />
<h2 id="heading-why-vitest-understanding-the-shift">Why Vitest? Understanding the Shift</h2>
<p>Angular 21 marks a significant shift in the testing landscape. The Angular team has deprecated Karma and Jasmine in favor of Vitest for several compelling reasons:</p>
<p><strong>Speed:</strong> Vitest is built on Vite, offering near-instant test startup and hot module replacement. Your tests run in milliseconds, not seconds.</p>
<p><strong>Modern API:</strong> While maintaining compatibility with Jasmine-style syntax, Vitest offers a more modern, flexible API that aligns with current JavaScript testing practices.</p>
<p><strong>Browser and Node Support:</strong> Unlike Karma, Vitest can run tests in both Node environments (with jsdom) and real browsers using Playwright.</p>
<p><strong>Better Developer Experience:</strong> Built-in watch mode, clear error messages, and seamless TypeScript support make debugging a breeze.</p>
<p>Have you ever felt frustrated waiting for Karma to spin up? That frustration ends here.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we start, make sure you have:</p>
<ul>
<li><p>An existing Angular project (any version, but we’ll focus on 21+)</p>
</li>
<li><p>Node.js 18+ installed</p>
</li>
<li><p>Basic familiarity with Angular testing concepts</p>
</li>
</ul>
<p>Don’t worry if you’re new to Vitest—we’ll cover everything you need to know.</p>
<hr />
<h2 id="heading-step-1-installing-vitest-and-dependencies">Step 1: Installing Vitest and Dependencies</h2>
<p>First, let’s install Vitest and the necessary dependencies:</p>
<pre><code class="lang-bash">npm install -D vitest @vitest/browser jsdom
</code></pre>
<p>Here’s what each package does:</p>
<ul>
<li><p><code>vitest</code>: The core testing framework</p>
</li>
<li><p><code>@vitest/browser</code>: Enables browser-based testing with Playwright</p>
</li>
<li><p><code>jsdom</code>: Simulates a browser environment in Node.js</p>
</li>
</ul>
<p>If you plan to use real browser testing (recommended for component tests), also install Playwright:</p>
<pre><code class="lang-bash">npm install -D @vitest/browser playwright
</code></pre>
<p>Quick tip: Check your package.json to ensure these are added under <code>devDependencies</code>.</p>
<hr />
<h2 id="heading-step-2-configuring-angularjson">Step 2: Configuring angular.json</h2>
<p>This is where the magic happens. Open your <code>angular.json</code> file and locate your project’s test configuration. We need to replace the Karma builder with Angular’s new Vitest builder.</p>
<p><strong>Before (Karma configuration):</strong></p>
<pre><code class="lang-json"><span class="hljs-string">"test"</span>: {
  <span class="hljs-attr">"builder"</span>: <span class="hljs-string">"@angular-devkit/build-angular:karma"</span>,
  <span class="hljs-attr">"options"</span>: {
    <span class="hljs-attr">"karmaConfig"</span>: <span class="hljs-string">"karma.conf.js"</span>,
    <span class="hljs-attr">"polyfills"</span>: [<span class="hljs-string">"zone.js"</span>, <span class="hljs-string">"zone.js/testing"</span>],
    <span class="hljs-attr">"tsConfig"</span>: <span class="hljs-string">"tsconfig.spec.json"</span>,
    <span class="hljs-attr">"assets"</span>: [<span class="hljs-string">"src/favicon.ico"</span>, <span class="hljs-string">"src/assets"</span>]
  }
}
</code></pre>
<p><strong>After (Vitest configuration):</strong></p>
<pre><code class="lang-json"><span class="hljs-string">"test"</span>: {
  <span class="hljs-attr">"builder"</span>: <span class="hljs-string">"@angular/build:unit-test"</span>,
  <span class="hljs-attr">"options"</span>: {
    <span class="hljs-attr">"polyfills"</span>: [<span class="hljs-string">"zone.js"</span>, <span class="hljs-string">"zone.js/testing"</span>],
    <span class="hljs-attr">"tsConfig"</span>: <span class="hljs-string">"tsconfig.spec.json"</span>,
    <span class="hljs-attr">"assets"</span>: [<span class="hljs-string">"src/favicon.ico"</span>, <span class="hljs-string">"src/assets"</span>]
  }
}
</code></pre>
<p>Notice these changes:</p>
<ol>
<li><p>Builder changed from <code>@angular-devkit/build-angular:karma</code> to <code>@angular/build:unit-test</code></p>
</li>
<li><p>Removed the <code>karmaConfig</code> option</p>
</li>
<li><p>Kept essential options like polyfills, tsConfig, and assets</p>
</li>
</ol>
<p>Save the file and let’s move forward.</p>
<hr />
<h2 id="heading-step-3-removing-karma">Step 3: Removing Karma</h2>
<p>Time to clean up. Let’s uninstall all Karma-related packages:</p>
<pre><code class="lang-bash">npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter @types/jasmine jasmine-core
</code></pre>
<p>Next, delete these files from your project root if they exist:</p>
<ul>
<li><p><code>karma.conf.js</code></p>
</li>
<li><p><code>src/test.ts</code> (if you have it)</p>
</li>
</ul>
<p>Pro tip: Use your version control to double-check what you’re removing. You can always revert if needed.</p>
<hr />
<h2 id="heading-step-4-running-your-first-test-with-vitest">Step 4: Running Your First Test with Vitest</h2>
<p>Here’s the beautiful part: your existing Jasmine tests will mostly work without changes. Let’s see it in action.</p>
<p>Run your tests:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span>
</code></pre>
<p>Vitest will start, and you’ll see output similar to:</p>
<pre><code class="lang-typescript">RUN  v2<span class="hljs-number">.1</span><span class="hljs-number">.8</span>

✓ src/app/app.component.spec.ts (<span class="hljs-number">4</span> tests) <span class="hljs-number">234</span>ms
✓ src/app/services/data.service.spec.ts (<span class="hljs-number">3</span> tests) <span class="hljs-number">156</span>ms

Test Files  <span class="hljs-number">2</span> passed (<span class="hljs-number">2</span>)
     Tests  <span class="hljs-number">7</span> passed (<span class="hljs-number">7</span>)
</code></pre>
<p>Notice how much faster it is compared to Karma?</p>
<p><strong>API Compatibility:</strong></p>
<p>Vitest maintains compatibility with most Jasmine functions:</p>
<ul>
<li><p><code>describe()</code> - Test suites</p>
</li>
<li><p><code>it()</code> - Individual tests</p>
</li>
<li><p><code>beforeEach()</code> - Setup before each test</p>
</li>
<li><p><code>beforeAll()</code> - Setup before all tests</p>
</li>
<li><p><code>afterEach()</code> - Cleanup after each test</p>
</li>
<li><p><code>expect()</code> - Assertions</p>
</li>
</ul>
<p><strong>Key Differences:</strong></p>
<p>Instead of Jasmine’s focused tests:</p>
<pre><code class="lang-tsx">// Old Jasmine way
fdescribe('Focused suite', () =&gt; {});
fit('Focused test', () =&gt; {});
</code></pre>
<p>Use Vitest’s syntax:</p>
<pre><code class="lang-tsx">// New Vitest way
describe.only('Focused suite', () =&gt; {});
it.only('Focused test', () =&gt; {});
</code></pre>
<hr />
<h2 id="heading-step-5-fixing-typescript-typings">Step 5: Fixing TypeScript Typings</h2>
<p>You might notice TypeScript errors about <code>describe</code>, <code>it</code>, or <code>expect</code> not being defined. This happens because Vitest doesn’t pollute the global namespace by default (which is actually a good thing).</p>
<p>Update your <code>tsconfig.spec.json</code>:</p>
<p><strong>Before:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./out-tsc/spec"</span>,
    <span class="hljs-attr">"types"</span>: [<span class="hljs-string">"jasmine"</span>]
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src/**/*.spec.ts"</span>, <span class="hljs-string">"src/**/*.d.ts"</span>]
}
</code></pre>
<p><strong>After:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"extends"</span>: <span class="hljs-string">"./tsconfig.json"</span>,
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./out-tsc/spec"</span>,
    <span class="hljs-attr">"types"</span>: [<span class="hljs-string">"vitest/globals"</span>]
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src/**/*.spec.ts"</span>, <span class="hljs-string">"src/**/*.d.ts"</span>]
}
</code></pre>
<p>Changed <code>"types": ["jasmine"]</code> to <code>"types": ["vitest/globals"]</code>.</p>
<p><strong>Better Approach - Explicit Imports:</strong></p>
<p>Instead of relying on global types, import Vitest functions explicitly in your test files:</p>
<pre><code class="lang-tsx">import { describe, it, expect, beforeEach } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () =&gt; {
  beforeEach(async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [AppComponent]
    }).compileComponents();
  });

  it('should create the app', () =&gt; {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });
});
</code></pre>
<p>This approach is cleaner, more explicit, and enables better tree-shaking.</p>
<p><strong>Automated Migration:</strong></p>
<p>Angular provides an experimental schematic to add imports automatically:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --migrate --add-imports
</code></pre>
<p>This will scan your test files and add the necessary imports. However, review the changes carefully.</p>
<hr />
<h2 id="heading-step-6-writing-modern-angular-tests-with-vitest">Step 6: Writing Modern Angular Tests with Vitest</h2>
<p>Let’s write a complete example using Angular 21’s latest syntax with standalone components and signal-based inputs.</p>
<p><strong>Component to Test:</strong></p>
<pre><code class="lang-tsx">// user-profile.component.ts
import { Component, input, computed } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  template: `
    &lt;div class="profile"&gt;
      &lt;h2&gt;{{ displayName() }}&lt;/h2&gt;
      @if (isAdmin()) {
        &lt;span class="badge"&gt;Admin&lt;/span&gt;
      }
      &lt;p&gt;Email: {{ email() }}&lt;/p&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .profile { padding: 20px; }
    .badge { color: red; font-weight: bold; }
  `]
})
export class UserProfileComponent {
  firstName = input.required&lt;string&gt;();
  lastName = input.required&lt;string&gt;();
  email = input.required&lt;string&gt;();
  role = input&lt;string&gt;('user');

  displayName = computed(() =&gt;
    `${this.firstName()} ${this.lastName()}`
  );

  isAdmin = computed(() =&gt;
    this.role() === 'admin'
  );
}
</code></pre>
<p><strong>Unit Test:</strong></p>
<pre><code class="lang-tsx">// user-profile.component.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserProfileComponent } from './user-profile.component';

describe('UserProfileComponent', () =&gt; {
  let component: UserProfileComponent;
  let fixture: ComponentFixture&lt;UserProfileComponent&gt;;

  beforeEach(async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [UserProfileComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(UserProfileComponent);
    component = fixture.componentInstance;
  });

  it('should create the component', () =&gt; {
    expect(component).toBeTruthy();
  });

  it('should display full name correctly', () =&gt; {
    fixture.componentRef.setInput('firstName', 'John');
    fixture.componentRef.setInput('lastName', 'Doe');
    fixture.componentRef.setInput('email', 'john@example.com');
    fixture.detectChanges();

    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h2').textContent).toBe('John Doe');
  });

  it('should show admin badge when role is admin', () =&gt; {
    fixture.componentRef.setInput('firstName', 'Jane');
    fixture.componentRef.setInput('lastName', 'Smith');
    fixture.componentRef.setInput('email', 'jane@example.com');
    fixture.componentRef.setInput('role', 'admin');
    fixture.detectChanges();

    const compiled = fixture.nativeElement;
    const badge = compiled.querySelector('.badge');
    expect(badge).toBeTruthy();
    expect(badge.textContent).toBe('Admin');
  });

  it('should not show admin badge for regular users', () =&gt; {
    fixture.componentRef.setInput('firstName', 'Bob');
    fixture.componentRef.setInput('lastName', 'Wilson');
    fixture.componentRef.setInput('email', 'bob@example.com');
    fixture.componentRef.setInput('role', 'user');
    fixture.detectChanges();

    const compiled = fixture.nativeElement;
    const badge = compiled.querySelector('.badge');
    expect(badge).toBeNull();
  });

  it('should compute display name from first and last name', () =&gt; {
    fixture.componentRef.setInput('firstName', 'Alice');
    fixture.componentRef.setInput('lastName', 'Johnson');
    fixture.componentRef.setInput('email', 'alice@example.com');

    expect(component.displayName()).toBe('Alice Johnson');
  });
});
</code></pre>
<p><strong>Key Points:</strong></p>
<ul>
<li><p>Using <code>imports: [UserProfileComponent]</code> instead of declarations (standalone architecture)</p>
</li>
<li><p>Using <code>fixture.componentRef.setInput()</code> to set signal-based inputs</p>
</li>
<li><p>Testing computed signals directly</p>
</li>
<li><p>Testing template control flow with <code>@if</code></p>
</li>
</ul>
<hr />
<h2 id="heading-step-7-testing-services-with-vitest">Step 7: Testing Services with Vitest</h2>
<p>Let’s test a service that uses signals and modern Angular features:</p>
<p><strong>Service:</strong></p>
<pre><code class="lang-tsx">// user.service.ts
import { Injectable, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';

export interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private users = signal&lt;User[]&gt;([]);

  allUsers = this.users.asReadonly();
  userCount = computed(() =&gt; this.users().length);

  constructor(private http: HttpClient) {}

  loadUsers() {
    return this.http.get&lt;User[]&gt;('/api/users').subscribe(
      users =&gt; this.users.set(users)
    );
  }

  addUser(user: User) {
    this.users.update(current =&gt; [...current, user]);
  }

  removeUser(id: number) {
    this.users.update(current =&gt;
      current.filter(u =&gt; u.id !== id)
    );
  }
}
</code></pre>
<p><strong>Unit Test:</strong></p>
<pre><code class="lang-tsx">// user.service.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService, User } from './user.service';

describe('UserService', () =&gt; {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  it('should be created', () =&gt; {
    expect(service).toBeTruthy();
  });

  it('should load users from API', () =&gt; {
    const mockUsers: User[] = [
      { id: 1, name: 'John', email: 'john@test.com' },
      { id: 2, name: 'Jane', email: 'jane@test.com' }
    ];

    service.loadUsers();

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);

    expect(service.allUsers()).toEqual(mockUsers);
    expect(service.userCount()).toBe(2);
  });

  it('should add a new user', () =&gt; {
    const newUser: User = {
      id: 3,
      name: 'Bob',
      email: 'bob@test.com'
    };

    service.addUser(newUser);

    expect(service.allUsers()).toContain(newUser);
    expect(service.userCount()).toBe(1);
  });

  it('should remove a user by id', () =&gt; {
    const users: User[] = [
      { id: 1, name: 'John', email: 'john@test.com' },
      { id: 2, name: 'Jane', email: 'jane@test.com' }
    ];

    users.forEach(user =&gt; service.addUser(user));
    expect(service.userCount()).toBe(2);

    service.removeUser(1);

    expect(service.userCount()).toBe(1);
    expect(service.allUsers().find(u =&gt; u.id === 1)).toBeUndefined();
    expect(service.allUsers().find(u =&gt; u.id === 2)).toBeTruthy();
  });

  it('should compute user count correctly', () =&gt; {
    expect(service.userCount()).toBe(0);

    service.addUser({ id: 1, name: 'Test', email: 'test@test.com' });
    expect(service.userCount()).toBe(1);

    service.addUser({ id: 2, name: 'Test2', email: 'test2@test.com' });
    expect(service.userCount()).toBe(2);
  });
});
</code></pre>
<p>Notice how we’re testing signal-based state management and computed signals directly.</p>
<hr />
<h2 id="heading-step-8-advanced-configuration-with-vitestconfigts">Step 8: Advanced Configuration with vitest.config.ts</h2>
<p>For more control over your testing environment, create a <code>vitest.config.ts</code> file in your project root:</p>
<pre><code class="lang-tsx">// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: false, // Disable global APIs
    environment: 'jsdom', // Use jsdom for DOM simulation
    setupFiles: ['src/test-setup.ts'], // Global setup file
    include: ['src/**/*.spec.ts'], // Test file pattern
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      exclude: [
        'node_modules/',
        'src/test-setup.ts',
      ]
    }
  }
});
</code></pre>
<p><strong>Configuration Options Explained:</strong></p>
<ul>
<li><p><code>globals: false</code> - Requires explicit imports (cleaner, more maintainable)</p>
</li>
<li><p><code>environment: 'jsdom'</code> - Fast Node-based DOM simulation</p>
</li>
<li><p><code>setupFiles</code> - Code to run before all tests</p>
</li>
<li><p><code>coverage</code> - Configure code coverage reporting</p>
</li>
</ul>
<p><strong>Create setup file:</strong></p>
<pre><code class="lang-tsx">// src/test-setup.ts
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

// Initialize Angular testing environment
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);
</code></pre>
<hr />
<h2 id="heading-step-9-real-browser-testing-with-playwright">Step 9: Real Browser Testing with Playwright</h2>
<p>For end-to-end component testing in real browsers, configure Vitest to use Playwright:</p>
<pre><code class="lang-tsx">// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: false,
    browser: {
      enabled: true,
      name: 'chromium', // or 'firefox', 'webkit'
      provider: 'playwright',
      headless: true,
      screenshotOnFailure: true
    },
    include: ['src/**/*.spec.ts']
  }
});
</code></pre>
<p><strong>Benefits of Browser Testing:</strong></p>
<ul>
<li><p>Tests run in actual browser environments</p>
</li>
<li><p>More accurate DOM behavior</p>
</li>
<li><p>Better debugging with browser DevTools</p>
</li>
<li><p>Support for Chrome, Firefox, and Safari</p>
</li>
</ul>
<p><strong>Example browser test:</strong></p>
<pre><code class="lang-tsx">import { describe, it, expect } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent (Browser)', () =&gt; {
  it('should render in real browser', async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [AppComponent]
    }).compileComponents();

    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();

    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h1')).toBeTruthy();
  });
});
</code></pre>
<p>Run browser tests:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --browser
</code></pre>
<hr />
<h2 id="heading-common-pitfalls-and-how-to-avoid-them">Common Pitfalls and How to Avoid Them</h2>
<p><strong>Pitfall 1: Global type conflicts</strong></p>
<p>If you see errors like “Cannot find name ‘describe’”, you likely have conflicting types.</p>
<p>Solution: Use explicit imports and set <code>globals: false</code> in your config.</p>
<p><strong>Pitfall 2: Zone.js issues</strong></p>
<p>Some async tests might fail due to Zone.js timing.</p>
<p>Solution: Ensure zone.js polyfills are loaded in your test configuration:</p>
<pre><code class="lang-json"><span class="hljs-string">"polyfills"</span>: [<span class="hljs-string">"zone.js"</span>, <span class="hljs-string">"zone.js/testing"</span>]
</code></pre>
<p><strong>Pitfall 3: Slow test startup</strong></p>
<p>If tests still feel slow, you might be running in browser mode unnecessarily.</p>
<p>Solution: Use jsdom for unit tests, reserve browser mode for integration tests.</p>
<p><strong>Pitfall 4: Missing TestBed configuration</strong></p>
<p>Forgetting to configure TestBed properly is a common mistake.</p>
<p>Solution: Always initialize in beforeEach:</p>
<pre><code class="lang-tsx">beforeEach(async () =&gt; {
  await TestBed.configureTestingModule({
    imports: [YourComponent]
  }).compileComponents();
});
</code></pre>
<hr />
<h2 id="heading-bonus-tips-for-maximum-productivity">Bonus Tips for Maximum Productivity</h2>
<p><strong>Tip 1: Watch Mode</strong></p>
<p>Run tests in watch mode for instant feedback:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --watch
</code></pre>
<p>Vitest will re-run only affected tests when you save files.</p>
<p><strong>Tip 2: Run Specific Tests</strong></p>
<p>Focus on specific tests during development:</p>
<pre><code class="lang-tsx">it.only('should test this specific case', () =&gt; {
  // Only this test runs
});
</code></pre>
<p><strong>Tip 3: Skip Tests Temporarily</strong></p>
<pre><code class="lang-tsx">it.skip('will implement this later', () =&gt; {
  // Skipped
});
</code></pre>
<p><strong>Tip 4: Code Coverage</strong></p>
<p>Generate coverage reports:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --coverage
</code></pre>
<p>Open <code>coverage/index.html</code> to see detailed reports.</p>
<p><strong>Tip 5: Parallel Execution</strong></p>
<p>Vitest runs tests in parallel by default. For sequential execution:</p>
<pre><code class="lang-tsx">// vitest.config.ts
export default defineConfig({
  test: {
    pool: 'threads',
    poolOptions: {
      threads: {
        singleThread: true
      }
    }
  }
});
</code></pre>
<hr />
<h2 id="heading-recap-what-we-learned">Recap: What We Learned</h2>
<p>Let’s summarize the key takeaways:</p>
<ol>
<li><p><strong>Installation</strong>: Added Vitest, jsdom, and Playwright to replace Karma/Jasmine</p>
</li>
<li><p><strong>Configuration</strong>: Updated angular.json to use the new unit-test builder</p>
</li>
<li><p><strong>Cleanup</strong>: Removed Karma dependencies and configuration files</p>
</li>
<li><p><strong>TypeScript</strong>: Fixed typings by updating tsconfig.spec.json</p>
</li>
<li><p><strong>Modern Syntax</strong>: Wrote tests using signal-based inputs and standalone components</p>
</li>
<li><p><strong>Advanced Setup</strong>: Configured vitest.config.ts for custom behavior</p>
</li>
<li><p><strong>Browser Testing</strong>: Set up Playwright for real browser testing</p>
</li>
<li><p><strong>Best Practices</strong>: Learned common pitfalls and productivity tips</p>
</li>
</ol>
<p>Migration to Vitest is straightforward, and the benefits are immediate: faster tests, better DX, and modern tooling. Your test suite will run in a fraction of the time, and you’ll enjoy a smoother development workflow.</p>
<hr />
<h2 id="heading-take-action-now">Take Action Now</h2>
<p>Ready to supercharge your Angular testing workflow? Here’s what to do:</p>
<ol>
<li><p>Back up your current test configuration</p>
</li>
<li><p>Follow the steps above in a feature branch</p>
</li>
<li><p>Run your existing tests and fix any issues</p>
</li>
<li><p>Gradually adopt explicit imports and modern patterns</p>
</li>
<li><p>Share your migration experience with the community</p>
</li>
</ol>
<p>What challenges did you face during migration? Did this guide help you get unstuck?</p>
<p>Drop a comment below with your biggest testing pain point, and let’s solve it together.</p>
<p>Found this helpful? Give it a clap so other developers can discover this guide too.</p>
<p>Want more Angular tips, performance tricks, and modern development practices? Follow me for weekly insights that will level up your skills.</p>
<p>Have a specific topic you’d like me to cover next? Let me know in the comments. I read every single one and love hearing what you want to learn.</p>
<p>Happy testing, and may your test suites run blazingly fast!</p>
<hr />
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[npx knip: The Smart Way to Detect Dead Code in Your JavaScript & TypeScript Projects]]></title><description><![CDATA[Have you ever wondered how much of your codebase is actually being used? If you are maintaining a medium to large application, chances are you have accumulated dead code, unused dependencies, and orphaned files that nobody dares to touch. What if I t...]]></description><link>https://hashnode.rajatmalik.dev/npx-knip-the-smart-way-to-detect-dead-code-in-your-javascript-and-typescript-projects</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/npx-knip-the-smart-way-to-detect-dead-code-in-your-javascript-and-typescript-projects</guid><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Mon, 15 Dec 2025 13:30:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765618587541/64dc8ee9-2d3b-463a-ab8e-7a85a80cd5de.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever wondered how much of your codebase is actually being used? If you are maintaining a medium to large application, chances are you have accumulated dead code, unused dependencies, and orphaned files that nobody dares to touch. What if I told you there is a tool that can find all of this automatically in seconds?</p>
<p>In this article, you will learn how to use <strong>npx knip</strong> to detect and eliminate dead code, unused exports, redundant dependencies, and more—all without installing anything permanently. By the end, you will have a clear action plan to clean up your JavaScript or TypeScript project and keep it lean going forward.</p>
<p>Let me ask you this: When was the last time you audited your dependencies or checked for unused exports? If the answer is "never" or "I don't remember," keep reading.</p>
<hr />
<h2 id="heading-what-is-knip-and-why-should-you-care">What is Knip and Why Should You Care?</h2>
<p>Knip is a comprehensive tool that analyzes your JavaScript and TypeScript projects to find:</p>
<ul>
<li><p><strong>Unused files</strong> that are no longer imported anywhere</p>
</li>
<li><p><strong>Unused dependencies</strong> sitting in your package.json</p>
</li>
<li><p><strong>Unused exports</strong> from modules</p>
</li>
<li><p><strong>Unused types</strong> in TypeScript files</p>
</li>
<li><p><strong>Unreachable code</strong> and much more</p>
</li>
</ul>
<p>Think of Knip as a linter for your entire project structure, not just your code syntax. It works with Angular, React, Next.js, Vue, Node.js, and virtually any JavaScript ecosystem project.</p>
<p>The best part? You can run it with <code>npx knip</code> without installing it globally. That means zero setup friction.</p>
<hr />
<p>💬 <strong>Quick question:</strong> Have you ever deleted a component or service only to realize later that its imports are still scattered across your codebase? Drop a comment below—I would love to hear your cleanup horror stories.</p>
<hr />
<h2 id="heading-getting-started-with-knip">Getting Started with Knip</h2>
<p>Let's dive straight into action. Here is how you run Knip on any project.</p>
<h3 id="heading-step-1-run-knip-with-npx">Step 1: Run Knip with npx</h3>
<p>Navigate to your project root and run:</p>
<pre><code class="lang-bash">npx knip
</code></pre>
<p>That is it. Knip will analyze your project and show you a report of unused files, dependencies, and exports.</p>
<p><strong>Sample Output:</strong></p>
<pre><code class="lang-bash">Unused files (3)
  src/utils/old-helper.ts
  src/components/legacy-modal.component.ts
  src/services/deprecated-api.service.ts

Unused dependencies (5)
  lodash
  moment
  uuid
  axios
  rxjs-compat

Unused exports (12)
  calculateTax <span class="hljs-keyword">in</span> src/utils/math.ts
  parseDate <span class="hljs-keyword">in</span> src/utils/date-parser.ts
  LegacyUser <span class="hljs-keyword">in</span> src/models/user.model.ts
</code></pre>
<p>This output immediately tells you what is safe to remove. But before you start deleting, let's understand how Knip works.</p>
<hr />
<h2 id="heading-how-knip-works-under-the-hood">How Knip Works Under the Hood</h2>
<p>Knip uses static analysis to trace your codebase. It starts from your entry points (like <code>main.ts</code>, <code>app.component.ts</code>, or <code>index.ts</code>) and follows all imports to build a dependency graph.</p>
<p>Anything not reachable from these entry points gets flagged as unused. Here is what makes Knip smart:</p>
<ol>
<li><p><strong>Framework-aware:</strong> It understands Angular modules (though we use standalone now), React components, Next.js pages, and more</p>
</li>
<li><p><strong>Configurable:</strong> You can tell Knip about custom entry points or ignore certain patterns</p>
</li>
<li><p><strong>Fast:</strong> It analyzes thousands of files in seconds</p>
</li>
</ol>
<hr />
<h2 id="heading-real-world-example-cleaning-up-an-angular-project">Real-World Example: Cleaning Up an Angular Project</h2>
<p>Let's say you have an Angular 18 application using standalone components. Over time, you have accumulated unused services, old components, and forgotten utility functions. Here is how Knip helps.</p>
<h3 id="heading-project-structure-before-cleanup">Project Structure Before Cleanup:</h3>
<pre><code class="lang-typescript">src/
├── app/
│   ├── components/
│   │   ├── user-list.component.ts
│   │   ├── user-detail.component.ts
│   │   └── old-dashboard.component.ts  <span class="hljs-comment">// unused</span>
│   ├── services/
│   │   ├── user.service.ts
│   │   ├── auth.service.ts
│   │   └── legacy-api.service.ts  <span class="hljs-comment">// unused</span>
│   └── utils/
│       ├── format.ts
│       └── old-helpers.ts  <span class="hljs-comment">// unused</span>
└── main.ts
</code></pre>
<h3 id="heading-running-knip">Running Knip:</h3>
<pre><code class="lang-bash">npx knip
</code></pre>
<h3 id="heading-knip-output">Knip Output:</h3>
<pre><code class="lang-typescript">Unused files (<span class="hljs-number">2</span>)
  src/app/components/old-dashboard.component.ts
  src/app/services/legacy-api.service.ts

Unused <span class="hljs-built_in">exports</span> (<span class="hljs-number">3</span>)
  oldHelperFunction <span class="hljs-keyword">in</span> src/app/utils/old-helpers.ts
  formatCurrency <span class="hljs-keyword">in</span> src/app/utils/format.ts
  deprecatedMethod <span class="hljs-keyword">in</span> src/app/services/user.service.ts
</code></pre>
<p>Now you know exactly what to remove. But wait—what if Knip is wrong?</p>
<hr />
<h2 id="heading-configuring-knip-for-your-project">Configuring Knip for Your Project</h2>
<p>Sometimes Knip flags code that looks unused but is actually needed. For example:</p>
<ul>
<li><p>Entry points that Knip doesn't know about</p>
</li>
<li><p>Dynamic imports</p>
</li>
<li><p>Code used only in tests</p>
</li>
</ul>
<p>You can configure Knip using a <code>knip.json</code> or <code>knip.ts</code> file.</p>
<h3 id="heading-example-configuration">Example Configuration:</h3>
<pre><code class="lang-json">{
  <span class="hljs-attr">"entry"</span>: [<span class="hljs-string">"src/main.ts"</span>, <span class="hljs-string">"src/polyfills.ts"</span>],
  <span class="hljs-attr">"project"</span>: [<span class="hljs-string">"src/**/*.ts"</span>],
  <span class="hljs-attr">"ignore"</span>: [<span class="hljs-string">"src/**/*.spec.ts"</span>, <span class="hljs-string">"src/environments/**"</span>],
  <span class="hljs-attr">"ignoreDependencies"</span>: [<span class="hljs-string">"@types/*"</span>]
}
</code></pre>
<p>This tells Knip:</p>
<ul>
<li><p>Where your entry points are</p>
</li>
<li><p>Which files to analyze</p>
</li>
<li><p>What to ignore (like test files)</p>
</li>
<li><p>Which dependencies to skip (like type definitions)</p>
</li>
</ul>
<hr />
<p>👏 <strong>If this is making sense so far, hit that clap button—it helps other devs discover this too.</strong></p>
<hr />
<h2 id="heading-integrating-knip-into-your-cicd-pipeline">Integrating Knip into Your CI/CD Pipeline</h2>
<p>Once you have cleaned up your codebase, you want to keep it clean. The best way? Run Knip in your CI pipeline.</p>
<h3 id="heading-github-actions-example">GitHub Actions Example:</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Code</span> <span class="hljs-string">Health</span> <span class="hljs-string">Check</span>

<span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>, <span class="hljs-string">pull_request</span>]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">knip:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-number">18</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">install</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npx</span> <span class="hljs-string">knip</span>
</code></pre>
<p>Now every pull request gets checked for dead code automatically. If someone adds unused code, the build fails. Simple and effective.</p>
<hr />
<h2 id="heading-handling-common-knip-warnings">Handling Common Knip Warnings</h2>
<p>Let's address some scenarios you might encounter:</p>
<h3 id="heading-1-false-positives-with-dynamic-imports">1. False Positives with Dynamic Imports</h3>
<p>If you use dynamic imports like this:</p>
<pre><code class="lang-tsx">const moduleName = 'user-dashboard';
import(`./modules/${moduleName}.component`);
</code></pre>
<p>Knip might not detect these. Solution: Add them to your entry points or use the <code>ignore</code> configuration.</p>
<h3 id="heading-2-unused-dependencies-that-are-actually-needed">2. Unused Dependencies That Are Actually Needed</h3>
<p>Some packages like <code>@angular/animations</code> are needed at runtime but might not show up in your imports. Use <code>ignoreDependencies</code> for these:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"ignoreDependencies"</span>: [<span class="hljs-string">"@angular/animations"</span>, <span class="hljs-string">"zone.js"</span>]
}
</code></pre>
<h3 id="heading-3-files-only-used-in-production">3. Files Only Used in Production</h3>
<p>If certain files are only used in specific environments, you can create environment-specific configurations:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"production"</span>: {
    <span class="hljs-attr">"entry"</span>: [<span class="hljs-string">"src/main.ts"</span>, <span class="hljs-string">"src/production-setup.ts"</span>]
  }
}
</code></pre>
<hr />
<h2 id="heading-testing-your-cleanup-with-unit-tests">Testing Your Cleanup with Unit Tests</h2>
<p>After removing dead code, you should verify nothing broke. Here is how to write a quick smoke test for an Angular service:</p>
<h3 id="heading-example-testing-user-service-after-cleanup">Example: Testing User Service After Cleanup</h3>
<pre><code class="lang-tsx">import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';

describe('UserService', () =&gt; {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      providers: [
        UserService,
        provideHttpClient(),
        provideHttpClientTesting()
      ]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() =&gt; {
    httpMock.verify();
  });

  it('should be created', () =&gt; {
    expect(service).toBeTruthy();
  });

  it('should fetch users successfully', () =&gt; {
    const mockUsers = [
      { id: 1, name: 'John' },
      { id: 2, name: 'Jane' }
    ];

    service.getUsers().subscribe(users =&gt; {
      expect(users).toEqual(mockUsers);
      expect(users.length).toBe(2);
    });

    const req = httpMock.expectOne('api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });

  it('should handle errors gracefully', () =&gt; {
    service.getUsers().subscribe({
      next: () =&gt; fail('should have failed'),
      error: (error) =&gt; {
        expect(error.status).toBe(500);
      }
    });

    const req = httpMock.expectOne('api/users');
    req.flush('Server error', { status: 500, statusText: 'Server Error' });
  });
});
</code></pre>
<p>This ensures your service still works after removing unused methods or dependencies.</p>
<hr />
<p>💡 <strong>Pro tip incoming:</strong> Run Knip before and after major refactorings. It is like a safety net that catches orphaned code immediately.</p>
<hr />
<h2 id="heading-bonus-tips-for-keeping-your-codebase-clean">Bonus Tips for Keeping Your Codebase Clean</h2>
<p>Here are some practices I follow to prevent dead code accumulation:</p>
<ol>
<li><p><strong>Run Knip monthly:</strong> Set a calendar reminder to audit your codebase</p>
</li>
<li><p><strong>Review before merging:</strong> Make Knip checks part of your PR review process</p>
</li>
<li><p><strong>Delete aggressively:</strong> If code is unused for 3+ months, it is probably safe to remove</p>
</li>
<li><p><strong>Use feature flags:</strong> Instead of commenting out code, use feature flags and delete the code when the flag is removed</p>
</li>
<li><p><strong>Document why code exists:</strong> If something looks unused but isn't, add a comment explaining why</p>
</li>
</ol>
<hr />
<h2 id="heading-comparing-knip-to-other-tools">Comparing Knip to Other Tools</h2>
<p>You might be wondering: "How is Knip different from ESLint or Depcheck?"</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Tool</td><td>What It Finds</td><td>Speed</td><td>Configuration</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Knip</strong></td><td>Unused files, exports, dependencies, types</td><td>Fast</td><td>Moderate</td></tr>
<tr>
<td><strong>ESLint</strong></td><td>Code quality issues, unused variables</td><td>Fast</td><td>High</td></tr>
<tr>
<td><strong>Depcheck</strong></td><td>Unused dependencies only</td><td>Fast</td><td>Low</td></tr>
<tr>
<td><strong>Webpack Bundle Analyzer</strong></td><td>Bundle size, what's included</td><td>Slow</td><td>Low</td></tr>
</tbody>
</table>
</div><p>Knip is the most comprehensive for finding structural waste. Use it alongside ESLint for the best results.</p>
<hr />
<h2 id="heading-your-action-plan-clean-up-this-week">Your Action Plan: Clean Up This Week</h2>
<p>Here is what you can do right now:</p>
<ol>
<li><p><strong>Day 1:</strong> Run <code>npx knip</code> on your project and review the report</p>
</li>
<li><p><strong>Day 2:</strong> Create a <code>knip.json</code> config to tune the results</p>
</li>
<li><p><strong>Day 3:</strong> Remove obviously unused files and dependencies</p>
</li>
<li><p><strong>Day 4:</strong> Run your test suite to verify nothing broke</p>
</li>
<li><p><strong>Day 5:</strong> Add Knip to your CI pipeline</p>
</li>
</ol>
<p>By Friday, you will have a leaner, cleaner codebase.</p>
<hr />
<h2 id="heading-recap-what-we-covered">Recap: What We Covered</h2>
<p>Let's wrap up what you learned today:</p>
<ul>
<li><p><strong>What Knip is:</strong> A static analysis tool for finding dead code in JavaScript and TypeScript projects</p>
</li>
<li><p><strong>How to use it:</strong> Simply run <code>npx knip</code> in your project root</p>
</li>
<li><p><strong>Configuration:</strong> Customize Knip using <code>knip.json</code> to fit your project structure</p>
</li>
<li><p><strong>CI Integration:</strong> Add Knip to your pipeline to prevent future bloat</p>
</li>
<li><p><strong>Testing strategy:</strong> Write unit tests to verify your cleanup didn't break anything</p>
</li>
<li><p><strong>Best practices:</strong> Run Knip regularly and delete unused code aggressively</p>
</li>
</ul>
<p>The key takeaway? Dead code is not just clutter—it slows down builds, confuses developers, and increases maintenance burden. Knip makes cleaning it up trivial.</p>
<hr />
<h2 id="heading-lets-keep-the-conversation-going">Let's Keep the Conversation Going</h2>
<p>👇 <strong>I want to hear from you:</strong></p>
<p>💬 <strong>What percentage of your codebase do you think is unused?</strong> Drop your guess in the comments—I am curious to see the range.</p>
<p>👏 <strong>If this saved you even 10 minutes of manual searching, smash that clap button.</strong> It helps other developers discover this tool too.</p>
<p>📬 <strong>Want more practical dev tips like this?</strong> Follow me for weekly insights on Angular, TypeScript, and modern frontend development. I share one actionable tip every week.</p>
<p>🔥 <strong>Challenge:</strong> Run Knip on your project this week and share your results. Tag me with your findings—I will feature the most interesting cleanups in my next article.</p>
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[From 5MB to 500KB: The Hidden Cost of providedIn: 'root' in Angular – A Performance Deep Dive]]></title><description><![CDATA[Ever shipped an Angular app and wondered why your main bundle is still bloated even after using providedIn: 'root' everywhere?
Here's a question for you: Do you actually know where your services end up in production?
If you're like most Angular devs ...]]></description><link>https://hashnode.rajatmalik.dev/from-5mb-to-500kb-the-hidden-cost-of-providedin-root-in-angular-a-performance-deep-dive</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/from-5mb-to-500kb-the-hidden-cost-of-providedin-root-in-angular-a-performance-deep-dive</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 03 Dec 2025 03:30:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764414633135/e03bb6b4-3a88-45b9-9821-6a7710d59712.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever shipped an Angular app and wondered why your main bundle is <em>still</em> bloated even after using <code>providedIn: 'root'</code> everywhere?</p>
<p>Here's a question for you: <strong>Do you actually know where your services end up in production?</strong></p>
<p>If you're like most Angular devs (myself included, until recently), you probably assumed <code>providedIn: 'root'</code> magically optimizes everything. Well, buckle up—we're about to uncover some surprising truths about Angular's dependency injection that could save your app <strong>30-50% in bundle size</strong>.</p>
<p>By the end of this article, you'll know:</p>
<ul>
<li><p>The <em>real</em> truth about where <code>providedIn: 'root'</code> services live</p>
</li>
<li><p>When tree-shaking actually works (and when it fails spectacularly)</p>
</li>
<li><p>How to optimize your services for a lightweight global context</p>
</li>
<li><p>Modern Angular patterns that actually make a difference</p>
</li>
</ul>
<p>Let's dive in!</p>
<hr />
<h2 id="heading-the-myth-we-all-believed">The Myth We All Believed</h2>
<p>Quick poll: How many of you thought <code>providedIn: 'root'</code> automatically meant "optimized and tree-shakeable"?</p>
<p><em>raises hand</em></p>
<p>Yeah, me too. But here's what's actually happening under the hood...</p>
<pre><code class="lang-tsx">// Angular  syntax - What we typically write
import { Injectable, inject } from '@angular/core';

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

  getUser(id: string) {
    return this.http.get(`/api/users/${id}`);
  }
}
</code></pre>
<p>Looks clean, right? But here's the kicker: <strong>This service might still end up in your main bundle even if it's only used in a lazy-loaded module!</strong></p>
<p>💬 <em>Have you ever analyzed your bundle and found services where they shouldn't be? Drop a comment—I'm curious how common this is!</em></p>
<hr />
<h2 id="heading-the-good-the-bad-and-the-bundle-size">The Good, The Bad, and The Bundle Size</h2>
<h3 id="heading-the-good-when-providedin-root-shines">✅ The Good: When providedIn: 'root' Shines</h3>
<p>Let's be clear—<code>providedIn: 'root'</code> isn't evil. It's actually brilliant when used correctly:</p>
<pre><code class="lang-tsx">// Angular  - Perfect use case for root-provided service
import { Injectable, signal, computed } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ThemeService {
  // Using signals (Angular 16+)
  private readonly isDarkMode = signal(false);

  readonly theme = computed(() =&gt;
    this.isDarkMode() ? 'dark' : 'light'
  );

  toggleTheme() {
    this.isDarkMode.update(current =&gt; !current);
  }
}
</code></pre>
<p><strong>Why this works:</strong> Global state that's genuinely needed everywhere = perfect for root injection.</p>
<h3 id="heading-the-bad-when-things-go-wrong">❌ The Bad: When Things Go Wrong</h3>
<p>Here's where developers get burned:</p>
<pre><code class="lang-tsx">// This looks innocent but can bloat your main bundle
@Injectable({
  providedIn: 'root'
})
export class HeavyAnalyticsService {
  private readonly analytics = inject(AnalyticsLibrary); // 200KB library
  private readonly featureFlags = inject(FeatureFlagService);
  private readonly logger = inject(LoggingService);

  // Only used in admin module...
  trackAdminAction(action: string) {
    // Complex tracking logic
  }
}
</code></pre>
<p>Even if this service is <em>only</em> imported in a lazy-loaded admin module, the entire chain of dependencies might get pulled into your main bundle.</p>
<p><strong>The result?</strong> Your users download analytics code they'll never use. Ouch.</p>
<hr />
<h2 id="heading-the-solution-smart-service-architecture-in-angular">The Solution: Smart Service Architecture in Angular</h2>
<p>Let me show you three patterns that actually work in production:</p>
<h3 id="heading-pattern-1-module-scoped-services-the-classic">Pattern 1: Module-Scoped Services (The Classic)</h3>
<pre><code class="lang-tsx">// feature/admin/services/admin-analytics.service.ts
import { Injectable, inject } from '@angular/core';

@Injectable() // No providedIn!
export class AdminAnalyticsService {
  private readonly analytics = inject(AnalyticsLibrary);

  trackAdminAction(action: string) {
    // Heavy logic stays in the admin module
  }
}

// feature/admin/admin.module.ts
@NgModule({
  providers: [AdminAnalyticsService] // Provided at module level
})
export class AdminModule { }
</code></pre>
<h3 id="heading-pattern-2-lazy-injectable-pattern-my-personal-favorite">Pattern 2: Lazy-Injectable Pattern (My Personal Favorite) 🎯</h3>
<pre><code class="lang-tsx">// Angular  with standalone components
import { Injectable, inject } from '@angular/core';

@Injectable({
  providedIn: 'any' // Creates separate instance per lazy module
})
export class FeatureStateService {
  private readonly state = signal&lt;FeatureState&gt;(initialState);

  // Each lazy module gets its own instance
  updateState(updates: Partial&lt;FeatureState&gt;) {
    this.state.update(current =&gt; ({ ...current, ...updates }));
  }
}
</code></pre>
<h3 id="heading-pattern-3-token-based-injection-for-ultimate-control">Pattern 3: Token-Based Injection (For Ultimate Control)</h3>
<pre><code class="lang-tsx">// Angular - Using injection tokens for flexibility
import { InjectionToken, inject } from '@angular/core';

export interface Logger {
  log(message: string): void;
}

export const LOGGER_TOKEN = new InjectionToken&lt;Logger&gt;('Logger');

// In your main.ts
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: LOGGER_TOKEN,
      useFactory: () =&gt; {
        // Only load heavy logger in development
        return environment.production
          ? new SimpleLogger()
          : new DetailedLogger();
      }
    }
  ]
});

// Usage in any component
export class MyComponent {
  private readonly logger = inject(LOGGER_TOKEN);
}
</code></pre>
<p>👏 <em>If these patterns just saved you from a refactoring nightmare, hit that clap button! Your future self will thank you.</em></p>
<hr />
<h2 id="heading-lets-test-it-writing-unit-tests-in-angular">Let's Test It! Writing Unit Tests in Angular</h2>
<p>Here's something most articles skip—how do you actually test these optimized services?</p>
<pre><code class="lang-tsx">// user.service.spec.ts - Angular testing syntax
import { TestBed } from '@angular/core/testing';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';

describe('UserService', () =&gt; {
  let service: UserService;
  let httpTesting: HttpTestingController;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        UserService
      ]
    });

    service = TestBed.inject(UserService);
    httpTesting = TestBed.inject(HttpTestingController);
  });

  it('should fetch user with correct endpoint', () =&gt; {
    const mockUser = { id: '123', name: 'Dev' };

    service.getUser('123').subscribe(user =&gt; {
      expect(user).toEqual(mockUser);
    });

    const req = httpTesting.expectOne('/api/users/123');
    expect(req.request.method).toBe('GET');
    req.flush(mockUser);
  });

  afterEach(() =&gt; {
    httpTesting.verify();
  });
});
</code></pre>
<p>💡 <strong>Pro Tip:</strong> Always test your services in isolation first, then test them within their lazy-loaded context to ensure proper scoping!</p>
<hr />
<h2 id="heading-bonus-tips-bundle-optimization-tricks">🎯 Bonus Tips: Bundle Optimization Tricks</h2>
<h3 id="heading-1-use-bundle-analyzer-your-new-best-friend">1. Use Bundle Analyzer (Your New Best Friend)</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Install webpack-bundle-analyzer</span>
npm install --save-dev webpack-bundle-analyzer

<span class="hljs-comment"># Add to angular.json</span>
<span class="hljs-string">"build"</span>: {
  <span class="hljs-string">"options"</span>: {
    <span class="hljs-string">"statsJson"</span>: <span class="hljs-literal">true</span>
  }
}

<span class="hljs-comment"># Analyze your bundle</span>
ng build --stats-json
npx webpack-bundle-analyzer dist/your-app/stats.json
</code></pre>
<h3 id="heading-2-the-import-cost-vs-code-extension">2. The "Import Cost" VS Code Extension</h3>
<p>Install it. Use it. Love it. It shows you the size of every import in real-time. Mind = blown.</p>
<h3 id="heading-3-lazy-load-everything-thats-not-critical">3. Lazy Load Everything That's Not Critical</h3>
<pre><code class="lang-tsx">// Angular - Standalone component lazy loading
const routes: Routes = [
  {
    path: 'admin',
    loadComponent: () =&gt; import('./admin/admin.component')
      .then(m =&gt; m.AdminComponent),
    providers: [AdminService] // Service only loads with component!
  }
];
</code></pre>
<h3 id="heading-4-use-dynamic-imports-for-heavy-libraries">4. Use Dynamic Imports for Heavy Libraries</h3>
<pre><code class="lang-tsx">// Instead of this
import * as Chart from 'chart.js';

// Do this
async loadChart() {
  const { Chart } = await import('chart.js');
  return new Chart(/* ... */);
}
</code></pre>
<hr />
<h2 id="heading-quick-recap-your-action-plan">📊 Quick Recap: Your Action Plan</h2>
<p>Let's wrap this up with what you can do <em>right now</em>:</p>
<ol>
<li><p><strong>Audit your services:</strong> Run bundle analyzer today. Find services in wrong bundles.</p>
</li>
<li><p><strong>Question every</strong> <code>providedIn: 'root'</code>: Does it <em>really</em> need to be global?</p>
</li>
<li><p><strong>Use</strong> <code>providedIn: 'any'</code> for feature-specific services</p>
</li>
<li><p><strong>Leverage injection tokens</strong> for swappable implementations</p>
</li>
<li><p><strong>Test your lazy loading:</strong> Ensure services stay where they belong</p>
</li>
</ol>
<p><strong>The golden rule:</strong> If a service is only used in a lazy module, it shouldn't be in your main bundle. Period.</p>
<hr />
<h2 id="heading-lets-keep-this-conversation-going">🚀 Let's Keep This Conversation Going!</h2>
<p>Alright, dev to dev—this stuff matters. Your users on slow connections will thank you, and your Lighthouse scores will love you.</p>
<p><strong>💬 Your turn:</strong> What's the biggest bundle size win you've achieved? Got a horror story about a 5MB main bundle? Drop it in the comments—let's learn from each other's pain!</p>
<p><strong>👏 Found this helpful?</strong> Smash that clap button (you can hit it up to 50 times, just saying 😉). Every clap helps other devs discover these optimization tricks.</p>
<p><strong>📬 Want more Angular performance tips?</strong> I drop one actionable optimization technique every week. [Follow me here] to join 5,000+ devs leveling up their Angular game.</p>
<p><strong>🎯 Action challenge:</strong> Analyze your main bundle this week and share your before/after stats in the comments. Best improvement gets a shoutout in my next article!</p>
<p>Remember: Every KB you shave off is a user who doesn't bounce. Let's build faster apps together! 🚀</p>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Angular 21 Vitest Testing Revolution: Complete Karma-to-Vitest Migration Guide, ICOV Coverage, Faster DX & Modern Testing Workflows (2025 Edition)]]></title><description><![CDATA[Have You Ever Waited 10 Minutes for Your Test Suite to Run?
If you're an Angular developer, you've probably experienced this pain. You make a small change, run your tests, grab coffee, check your phone, and maybe start wondering if you chose the wron...]]></description><link>https://hashnode.rajatmalik.dev/angular-21-vitest-testing-revolution-complete-karma-to-vitest-migration-guide-icov-coverage-faster-dx-and-modern-testing-workflows-2025-edition</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/angular-21-vitest-testing-revolution-complete-karma-to-vitest-migration-guide-icov-coverage-faster-dx-and-modern-testing-workflows-2025-edition</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 02 Dec 2025 13:30:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764409228766/2cb75832-9ed9-49a5-865e-1910a1384cea.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h2 id="heading-have-you-ever-waited-10-minutes-for-your-test-suite-to-run"><strong>Have You Ever Waited 10 Minutes for Your Test Suite to Run?</strong></h2>
<p>If you're an Angular developer, you've probably experienced this pain. You make a small change, run your tests, grab coffee, check your phone, and maybe start wondering if you chose the wrong career. Meanwhile, your CI/CD pipeline is burning through minutes (and money) running Karma with Jasmine.</p>
<p>Here's the good news: Angular 21 just changed the game entirely.</p>
<p>The Angular team officially adopted <strong>Vitest</strong> as the default testing framework, leaving behind the decade-old Karma + Jasmine combo. And this isn't just a minor upgrade—it's a complete paradigm shift in how we think about testing Angular applications.</p>
<p><strong>In this article, you'll discover:</strong></p>
<ul>
<li><p>Why Angular dumped Karma after all these years</p>
</li>
<li><p>How Vitest makes your tests run 10x faster (seriously)</p>
</li>
<li><p>Step-by-step migration guide from Jasmine to Vitest</p>
</li>
<li><p>Real-world examples for testing components, async operations, and browser APIs</p>
</li>
<li><p>The new ICOV coverage format and why it matters</p>
</li>
<li><p>Best practices that will make your team's testing workflow smoother</p>
</li>
</ul>
<p>Whether you're maintaining a legacy Angular app or starting fresh with Angular 21, this guide will show you exactly how to leverage Vitest's power. Let's dive in.</p>
<hr />
<h2 id="heading-what-is-vitest-and-why-angular-finally-made-the-switch"><strong>What is Vitest? (And Why Angular Finally Made the Switch)</strong></h2>
<p><strong>Vitest</strong> is a blazing-fast unit testing framework built on top of Vite. Think of it as the modern successor to Jest, but designed specifically for the Vite ecosystem with native ESM support, instant hot module replacement, and TypeScript built right in.</p>
<h3 id="heading-the-karma-problem"><strong>The Karma Problem</strong></h3>
<p>Let's be honest—Karma served us well for years, but it was showing its age:</p>
<ul>
<li><p><strong>Slow startup times</strong>: Launching a real browser for every test run</p>
</li>
<li><p><strong>Poor watch mode</strong>: Waiting seconds for file changes to register</p>
</li>
<li><p><strong>Complex configuration</strong>: Remember wrestling with webpack configs?</p>
</li>
<li><p><strong>Flaky tests</strong>: Browser timing issues causing random failures</p>
</li>
<li><p><strong>No native ESM</strong>: Forced transpilation slowing everything down</p>
</li>
</ul>
<h3 id="heading-why-vitest-won"><strong>Why Vitest Won</strong></h3>
<p>The Angular team didn't make this decision lightly. Here's what tipped the scales:</p>
<p><strong>1. Speed That Actually Matters</strong> Vitest runs tests in a Node.js environment with happy-dom or jsdom, eliminating browser overhead. We're talking 10-20x faster execution.</p>
<p><strong>2. Developer Experience That Doesn't Suck</strong> Hot module reload in watch mode means instant feedback. Change a test, save, and see results before you even look at the terminal.</p>
<p><strong>3. Modern JavaScript Native</strong> Built for ESM from the ground up. No more transpilation gymnastics.</p>
<p><strong>4. TypeScript Without the Headache</strong> TypeScript support isn't bolted on—it's core to Vitest's architecture.</p>
<p><strong>5. Future-Proof Architecture</strong> As Angular moves toward ESM-only and standalone components, Vitest aligns perfectly with this direction.</p>
<p>Think about it this way: Karma was designed when Internet Explorer 9 was still relevant. Vitest was designed for the JavaScript ecosystem we have today.</p>
<hr />
<h2 id="heading-key-features-of-vitest-in-angular-21"><strong>Key Features of Vitest in Angular 21</strong></h2>
<p>Let's explore what makes Vitest a game-changer with actual code examples.</p>
<h3 id="heading-1-lightning-fast-execution"><strong>1. Lightning-Fast Execution</strong></h3>
<p>Here's a simple component test to see the speed difference:</p>
<pre><code class="lang-tsx">// user-profile.component.ts
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  template: `
    &lt;div class="profile-card"&gt;
      &lt;h2&gt;{{ name() }}&lt;/h2&gt;
      &lt;p&gt;{{ email() }}&lt;/p&gt;
      &lt;button (click)="handleEdit()"&gt;Edit Profile&lt;/button&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .profile-card { padding: 1rem; border: 1px solid #ddd; }
  `]
})
export class UserProfileComponent {
  name = input.required&lt;string&gt;();
  email = input.required&lt;string&gt;();
  editClicked = output&lt;void&gt;();

  handleEdit() {
    this.editClicked.emit();
  }
}
</code></pre>
<p><strong>Vitest Test:</strong></p>
<pre><code class="lang-tsx">// user-profile.component.spec.ts
import { describe, it, expect, vi } from 'vitest';
import { ComponentFixture } from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { UserProfileComponent } from './user-profile.component';

describe('UserProfileComponent', () =&gt; {
  let component: UserProfileComponent;
  let fixture: ComponentFixture&lt;UserProfileComponent&gt;;

  beforeEach(async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [UserProfileComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(UserProfileComponent);
    component = fixture.componentInstance;
  });

  it('should render user information correctly', () =&gt; {
    fixture.componentRef.setInput('name', 'John Doe');
    fixture.componentRef.setInput('email', 'john@example.com');
    fixture.detectChanges();

    const compiled = fixture.nativeElement;
    expect(compiled.querySelector('h2')?.textContent).toBe('John Doe');
    expect(compiled.querySelector('p')?.textContent).toBe('john@example.com');
  });

  it('should emit event when edit button is clicked', () =&gt; {
    const editSpy = vi.fn();
    component.editClicked.subscribe(editSpy);

    fixture.componentRef.setInput('name', 'Jane Smith');
    fixture.componentRef.setInput('email', 'jane@example.com');
    fixture.detectChanges();

    const button = fixture.nativeElement.querySelector('button');
    button.click();

    expect(editSpy).toHaveBeenCalledTimes(1);
  });
});
</code></pre>
<p>With Karma, this test might take 3-5 seconds to run. With Vitest? Usually under 100ms.</p>
<h3 id="heading-2-hot-module-reload-in-watch-mode"><strong>2. Hot Module Reload in Watch Mode</strong></h3>
<p>When you run tests in watch mode, Vitest only re-runs tests related to changed files:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --watch
</code></pre>
<p>Change your component, save, and boom—instant feedback. No full test suite re-run.</p>
<h3 id="heading-3-snapshot-testing-made-simple"><strong>3. Snapshot Testing Made Simple</strong></h3>
<p>Snapshot testing helps catch unexpected UI changes:</p>
<pre><code class="lang-tsx">// navigation.component.spec.ts
import { describe, it, expect } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { NavigationComponent } from './navigation.component';

describe('NavigationComponent Snapshots', () =&gt; {
  it('should match snapshot for default state', () =&gt; {
    const fixture = TestBed.createComponent(NavigationComponent);
    fixture.detectChanges();

    expect(fixture.nativeElement.innerHTML).toMatchSnapshot();
  });
});
</code></pre>
<p>First run creates the snapshot. Subsequent runs compare against it.</p>
<h3 id="heading-4-built-in-mocking-with-vi"><strong>4. Built-in Mocking with vi</strong></h3>
<p>Vitest's <code>vi</code> utility replaces Jasmine's spy system:</p>
<pre><code class="lang-tsx">// data.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class DataService {
  private http = inject(HttpClient);

  fetchUsers(): Observable&lt;any[]&gt; {
    return this.http.get&lt;any[]&gt;('/api/users');
  }
}

// data.service.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('DataService', () =&gt; {
  let service: DataService;
  let httpMock: any;

  beforeEach(() =&gt; {
    httpMock = {
      get: vi.fn()
    };

    TestBed.configureTestingModule({
      providers: [
        { provide: HttpClient, useValue: httpMock }
      ]
    });

    service = TestBed.inject(DataService);
  });

  it('should fetch users from API', (done) =&gt; {
    const mockUsers = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ];

    httpMock.get.mockReturnValue(of(mockUsers));

    service.fetchUsers().subscribe(users =&gt; {
      expect(users).toEqual(mockUsers);
      expect(httpMock.get).toHaveBeenCalledWith('/api/users');
      done();
    });
  });
});
</code></pre>
<p><strong>What just happened here?</strong> We used <code>vi.fn()</code> to create a mock function—cleaner and more flexible than Jasmine spies.</p>
<h3 id="heading-5-browser-like-environment-with-happy-dom"><strong>5. Browser-Like Environment with happy-dom</strong></h3>
<p>Vitest uses happy-dom by default, providing DOM APIs without a real browser:</p>
<pre><code class="lang-tsx">// storage.service.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';

describe('LocalStorage Mocking', () =&gt; {
  beforeEach(() =&gt; {
    // Clear storage before each test
    localStorage.clear();
  });

  it('should save and retrieve data from localStorage', () =&gt; {
    localStorage.setItem('theme', 'dark');
    expect(localStorage.getItem('theme')).toBe('dark');
  });

  it('should handle missing keys gracefully', () =&gt; {
    expect(localStorage.getItem('nonexistent')).toBeNull();
  });
});
</code></pre>
<p>Happy-dom provides localStorage, sessionStorage, and other browser APIs out of the box.</p>
<p>Have you tried running Angular tests with instant hot reload yet? If not, you're missing out on one of the best DX improvements in years.</p>
<hr />
<h2 id="heading-migration-from-karmajasmine-to-vitest-step-by-step-guide"><strong>Migration from Karma/Jasmine to Vitest: Step-by-Step Guide</strong></h2>
<p>Migrating might sound daunting, but Angular 21 makes it surprisingly smooth. Let's walk through it.</p>
<h3 id="heading-step-1-update-angular-to-version-21"><strong>Step 1: Update Angular to Version 21</strong></h3>
<pre><code class="lang-bash">ng update @angular/core@21 @angular/cli@21
</code></pre>
<h3 id="heading-step-2-add-vitest-to-your-project"><strong>Step 2: Add Vitest to Your Project</strong></h3>
<pre><code class="lang-bash">ng add @angular/vitest
</code></pre>
<p>This command automatically:</p>
<ul>
<li><p>Installs Vitest and related dependencies</p>
</li>
<li><p>Creates <code>vitest.config.ts</code></p>
</li>
<li><p>Updates <code>angular.json</code> test configuration</p>
</li>
<li><p>Migrates existing test setup files</p>
</li>
</ul>
<h3 id="heading-step-3-understanding-the-configuration"><strong>Step 3: Understanding the Configuration</strong></h3>
<p>Your new <code>vitest.config.ts</code> will look like this:</p>
<pre><code class="lang-tsx">// vitest.config.ts
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';

export default defineConfig({
  plugins: [angular()],
  test: {
    globals: true,
    environment: 'happy-dom',
    setupFiles: ['src/test-setup.ts'],
    include: ['**/*.spec.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html', 'lcov', 'icov'],
      exclude: [
        'node_modules/',
        'src/test-setup.ts',
      ],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 80,
        statements: 80
      }
    }
  }
});
</code></pre>
<h3 id="heading-step-4-migrating-test-syntax"><strong>Step 4: Migrating Test Syntax</strong></h3>
<p>Here's a comparison table of common patterns:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Jasmine</strong></td><td><strong>Vitest</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>jasmine.createSpy()</code></td><td><code>vi.fn()</code></td></tr>
<tr>
<td><code>spyOn(obj, 'method')</code></td><td><code>vi.spyOn(obj, 'method')</code></td></tr>
<tr>
<td><code>spyOn().and.returnValue()</code></td><td><code>vi.fn().mockReturnValue()</code></td></tr>
<tr>
<td><code>expect().toHaveBeenCalled()</code></td><td><code>expect().toHaveBeenCalled()</code> (same)</td></tr>
<tr>
<td><code>fakeAsync()</code></td><td><code>vi.useFakeTimers()</code></td></tr>
<tr>
<td><code>tick(100)</code></td><td><code>await vi.advanceTimersByTimeAsync(100)</code></td></tr>
<tr>
<td><code>flush()</code></td><td><code>await vi.runAllTimersAsync()</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-step-5-real-migration-example"><strong>Step 5: Real Migration Example</strong></h3>
<p><strong>Before (Jasmine):</strong></p>
<pre><code class="lang-tsx">// auth.service.spec.ts (Jasmine)
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('AuthService - Jasmine', () =&gt; {
  let service: AuthService;
  let httpSpy: jasmine.SpyObj&lt;HttpClient&gt;;

  beforeEach(() =&gt; {
    const spy = jasmine.createSpyObj('HttpClient', ['post']);

    TestBed.configureTestingModule({
      providers: [
        AuthService,
        { provide: HttpClient, useValue: spy }
      ]
    });

    service = TestBed.inject(AuthService);
    httpSpy = TestBed.inject(HttpClient) as jasmine.SpyObj&lt;HttpClient&gt;;
  });

  it('should login user successfully', (done) =&gt; {
    const mockResponse = { token: 'abc123', user: { id: 1, name: 'John' } };
    httpSpy.post.and.returnValue(of(mockResponse));

    service.login('john@example.com', 'password').subscribe(response =&gt; {
      expect(response.token).toBe('abc123');
      expect(httpSpy.post).toHaveBeenCalledWith('/api/auth/login', {
        email: 'john@example.com',
        password: 'password'
      });
      done();
    });
  });
});
</code></pre>
<p><strong>After (Vitest):</strong></p>
<pre><code class="lang-tsx">// auth.service.spec.ts (Vitest)
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('AuthService - Vitest', () =&gt; {
  let service: AuthService;
  let httpMock: any;

  beforeEach(() =&gt; {
    httpMock = {
      post: vi.fn()
    };

    TestBed.configureTestingModule({
      providers: [
        AuthService,
        { provide: HttpClient, useValue: httpMock }
      ]
    });

    service = TestBed.inject(AuthService);
  });

  it('should login user successfully', async () =&gt; {
    const mockResponse = { token: 'abc123', user: { id: 1, name: 'John' } };
    httpMock.post.mockReturnValue(of(mockResponse));

    const response = await service.login('john@example.com', 'password');

    expect(response.token).toBe('abc123');
    expect(httpMock.post).toHaveBeenCalledWith('/api/auth/login', {
      email: 'john@example.com',
      password: 'password'
    });
  });
});
</code></pre>
<p><strong>Key Changes:</strong></p>
<ul>
<li><p>Import from <code>vitest</code> instead of relying on globals</p>
</li>
<li><p>Use <code>vi.fn()</code> instead of <code>jasmine.createSpyObj()</code></p>
</li>
<li><p>Use <code>mockReturnValue()</code> instead of <code>and.returnValue()</code></p>
</li>
<li><p>Can use async/await instead of done callback</p>
</li>
</ul>
<h3 id="heading-step-6-migrating-async-tests"><strong>Step 6: Migrating Async Tests</strong></h3>
<p><strong>Jasmine fakeAsync:</strong></p>
<pre><code class="lang-tsx">it('should handle delayed response', fakeAsync(() =&gt; {
  let result: string;
  setTimeout(() =&gt; result = 'done', 1000);

  tick(1000);

  expect(result).toBe('done');
}));
</code></pre>
<p><strong>Vitest equivalent:</strong></p>
<pre><code class="lang-tsx">it('should handle delayed response', async () =&gt; {
  vi.useFakeTimers();

  let result: string;
  setTimeout(() =&gt; result = 'done', 1000);

  await vi.advanceTimersByTimeAsync(1000);

  expect(result).toBe('done');

  vi.useRealTimers();
});
</code></pre>
<p>What's your biggest concern about migrating your existing test suite? Is it the time investment or the learning curve?</p>
<hr />
<h2 id="heading-coverage-in-vitest-amp-angular-21"><strong>Coverage in Vitest &amp; Angular 21</strong></h2>
<p>Code coverage isn't just a metric—it's a conversation about quality. Here's how to make it work for you in Angular 21.</p>
<h3 id="heading-enabling-coverage"><strong>Enabling Coverage</strong></h3>
<p>Run tests with coverage:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --coverage
</code></pre>
<p>Or configure it to run automatically in <code>vitest.config.ts</code>:</p>
<pre><code class="lang-tsx">export default defineConfig({
  test: {
    coverage: {
      enabled: true,
      provider: 'v8', // or 'istanbul'
      reporter: ['text', 'json', 'html', 'lcov', 'icov'],
      reportsDirectory: './coverage',
      exclude: [
        'node_modules/',
        'src/test-setup.ts',
        '**/*.spec.ts',
        '**/*.config.ts',
        '**/index.ts'
      ],
      thresholds: {
        lines: 80,
        functions: 75,
        branches: 70,
        statements: 80
      }
    }
  }
});
</code></pre>
<h3 id="heading-understanding-coverage-providers"><strong>Understanding Coverage Providers</strong></h3>
<p>Vitest supports two coverage providers:</p>
<p><strong>1. v8 (Default)</strong></p>
<ul>
<li><p>Faster execution</p>
</li>
<li><p>Native to V8 JavaScript engine</p>
</li>
<li><p>Better for large codebases</p>
</li>
</ul>
<p><strong>2. Istanbul (c8)</strong></p>
<ul>
<li><p>More detailed reports</p>
</li>
<li><p>Better source map support</p>
</li>
<li><p>Industry standard</p>
</li>
</ul>
<h3 id="heading-setting-coverage-thresholds"><strong>Setting Coverage Thresholds</strong></h3>
<p>Make your CI/CD fail if coverage drops:</p>
<pre><code class="lang-tsx">coverage: {
  thresholds: {
    lines: 80,
    functions: 80,
    branches: 75,
    statements: 80,
    // Per-file thresholds
    perFile: true
  }
}
</code></pre>
<h3 id="heading-reading-coverage-reports"><strong>Reading Coverage Reports</strong></h3>
<p>After running tests with coverage, open <code>coverage/index.html</code>:</p>
<pre><code class="lang-typescript">Coverage Summary
================
Statements   : <span class="hljs-number">87.5</span>% ( <span class="hljs-number">350</span>/<span class="hljs-number">400</span> )
Branches     : <span class="hljs-number">82.3</span>% ( <span class="hljs-number">142</span>/<span class="hljs-number">172</span> )
Functions    : <span class="hljs-number">91.2</span>% ( <span class="hljs-number">104</span>/<span class="hljs-number">114</span> )
Lines        : <span class="hljs-number">88.1</span>% ( <span class="hljs-number">339</span>/<span class="hljs-number">385</span> )
</code></pre>
<p>Red files need attention. Green files are well-tested. Focus on the red.</p>
<hr />
<h2 id="heading-icov-file-support-in-angular-21-the-game-changer"><strong>ICOV File Support in Angular 21: The Game-Changer</strong></h2>
<p>This is one of the most underrated features in Angular 21. Let me explain why it matters.</p>
<h3 id="heading-what-is-icov"><strong>What is ICOV?</strong></h3>
<p><strong>ICOV</strong> (Improved Coverage) is a new coverage format introduced by Microsoft. Think of it as LCOV's smarter, more efficient cousin.</p>
<p><strong>Traditional LCOV Format:</strong></p>
<pre><code class="lang-typescript">TN:
SF:<span class="hljs-regexp">/src/</span>app/services/user.service.ts
FN:<span class="hljs-number">10</span>,fetchUser
FN:<span class="hljs-number">25</span>,updateUser
FNDA:<span class="hljs-number">5</span>,fetchUser
FNDA:<span class="hljs-number">2</span>,updateUser
DA:<span class="hljs-number">10</span>,<span class="hljs-number">5</span>
DA:<span class="hljs-number">11</span>,<span class="hljs-number">5</span>
DA:<span class="hljs-number">12</span>,<span class="hljs-number">4</span>
</code></pre>
<p><strong>New ICOV Format:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"files"</span>: {
    <span class="hljs-attr">"/src/app/services/user.service.ts"</span>: {
      <span class="hljs-attr">"functions"</span>: {
        <span class="hljs-attr">"fetchUser"</span>: { <span class="hljs-attr">"line"</span>: <span class="hljs-number">10</span>, <span class="hljs-attr">"count"</span>: <span class="hljs-number">5</span> },
        <span class="hljs-attr">"updateUser"</span>: { <span class="hljs-attr">"line"</span>: <span class="hljs-number">25</span>, <span class="hljs-attr">"count"</span>: <span class="hljs-number">2</span> }
      },
      <span class="hljs-attr">"branches"</span>: {},
      <span class="hljs-attr">"lines"</span>: {
        <span class="hljs-attr">"10"</span>: <span class="hljs-number">5</span>,
        <span class="hljs-attr">"11"</span>: <span class="hljs-number">5</span>,
        <span class="hljs-attr">"12"</span>: <span class="hljs-number">4</span>
      }
    }
  }
}
</code></pre>
<h3 id="heading-why-angular-introduced-icov-support"><strong>Why Angular Introduced ICOV Support</strong></h3>
<p><strong>1. Better Tooling Integration</strong> Azure DevOps, GitHub Actions, and modern CI/CD platforms prefer JSON-based formats for easier parsing.</p>
<p><strong>2. Smaller File Sizes</strong> JSON compression works better than LCOV's text format—important for large monorepos.</p>
<p><strong>3. Richer Metadata</strong> ICOV can include source maps, branch details, and function coverage in a structured way.</p>
<p><strong>4. Future-Proof</strong> As tools like SonarQube and CodeCov adopt ICOV, Angular is ready.</p>
<h3 id="heading-how-angular-21-generates-icov"><strong>How Angular 21 Generates ICOV</strong></h3>
<p>Simply add <code>'icov'</code> to your coverage reporters:</p>
<pre><code class="lang-tsx">// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      reporter: ['text', 'html', 'lcov', 'icov'], // Add 'icov'
      reportsDirectory: './coverage'
    }
  }
});
</code></pre>
<p>After running <code>ng test --coverage</code>, you'll find:</p>
<ul>
<li><p><code>coverage/lcov.info</code> (traditional)</p>
</li>
<li><p><code>coverage/coverage.icov</code> (new format)</p>
</li>
</ul>
<h3 id="heading-where-icov-is-used"><strong>Where ICOV is Used</strong></h3>
<p><strong>Azure DevOps:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishCodeCoverageResults@1</span>
  <span class="hljs-attr">inputs:</span>
    <span class="hljs-attr">codeCoverageTool:</span> <span class="hljs-string">'icov'</span>
    <span class="hljs-attr">summaryFileLocation:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/coverage/coverage.icov'</span>
</code></pre>
<p><strong>GitHub Actions:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Coverage</span>
  <span class="hljs-attr">uses:</span> <span class="hljs-string">codecov/codecov-action@v3</span>
  <span class="hljs-attr">with:</span>
    <span class="hljs-attr">files:</span> <span class="hljs-string">./coverage/coverage.icov</span>
    <span class="hljs-attr">format:</span> <span class="hljs-string">icov</span>
</code></pre>
<p><strong>SonarQube (Future Support):</strong></p>
<pre><code class="lang-typescript">sonar.javascript.icov.reportPaths=coverage/coverage.icov
</code></pre>
<h3 id="heading-sample-icov-output"><strong>Sample ICOV Output</strong></h3>
<pre><code class="lang-json">{
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"timestamp"</span>: <span class="hljs-string">"2025-11-29T10:30:00.000Z"</span>,
  <span class="hljs-attr">"files"</span>: {
    <span class="hljs-attr">"/src/app/components/user-list.component.ts"</span>: {
      <span class="hljs-attr">"lines"</span>: {
        <span class="hljs-attr">"5"</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">"8"</span>: <span class="hljs-number">10</span>,
        <span class="hljs-attr">"12"</span>: <span class="hljs-number">8</span>,
        <span class="hljs-attr">"15"</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">"20"</span>: <span class="hljs-number">6</span>
      },
      <span class="hljs-attr">"functions"</span>: {
        <span class="hljs-attr">"ngOnInit"</span>: { <span class="hljs-attr">"line"</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">"count"</span>: <span class="hljs-number">10</span> },
        <span class="hljs-attr">"loadUsers"</span>: { <span class="hljs-attr">"line"</span>: <span class="hljs-number">12</span>, <span class="hljs-attr">"count"</span>: <span class="hljs-number">8</span> },
        <span class="hljs-attr">"deleteUser"</span>: { <span class="hljs-attr">"line"</span>: <span class="hljs-number">15</span>, <span class="hljs-attr">"count"</span>: <span class="hljs-number">0</span> }
      },
      <span class="hljs-attr">"branches"</span>: {
        <span class="hljs-attr">"12:0"</span>: { <span class="hljs-attr">"count"</span>: <span class="hljs-number">5</span> },
        <span class="hljs-attr">"12:1"</span>: { <span class="hljs-attr">"count"</span>: <span class="hljs-number">3</span> }
      }
    }
  }
}
</code></pre>
<p><strong>What this tells us:</strong></p>
<ul>
<li><p>Line 15 (<code>deleteUser</code> function) was never executed—needs test coverage</p>
</li>
<li><p>Branch at line 12 has both paths tested</p>
</li>
<li><p><code>ngOnInit</code> and <code>loadUsers</code> are well-covered</p>
</li>
</ul>
<hr />
<h2 id="heading-terminal-output-in-vitest-what-youll-actually-see"><strong>Terminal Output in Vitest: What You'll Actually See</strong></h2>
<p>Let's look at real terminal output so you know what to expect.</p>
<h3 id="heading-single-file-test-summary"><strong>Single File Test Summary</strong></h3>
<pre><code class="lang-bash">$ ng <span class="hljs-built_in">test</span> user.service.spec.ts

 ✓ src/app/services/user.service.spec.ts (4)
   ✓ UserService (4)
     ✓ should be created
     ✓ should fetch users from API
     ✓ should handle API errors gracefully
     ✓ should cache user data correctly

 Test Files  1 passed (1)
      Tests  4 passed (4)
   Start at  10:30:45
   Duration  127ms
</code></pre>
<h3 id="heading-overall-test-summary"><strong>Overall Test Summary</strong></h3>
<pre><code class="lang-bash">$ ng <span class="hljs-built_in">test</span>

 ✓ src/app/components/user-list.component.spec.ts (6) 89ms
 ✓ src/app/services/user.service.spec.ts (4) 127ms
 ✓ src/app/pipes/format-date.pipe.spec.ts (3) 45ms
 ✓ src/app/guards/auth.guard.spec.ts (5) 156ms

 Test Files  4 passed (4)
      Tests  18 passed (18)
   Start at  10:30:45
   Duration  412ms (transform 89ms, setup 12ms, collect 134ms, tests 177ms)
</code></pre>
<h3 id="heading-coverage-summary"><strong>Coverage Summary</strong></h3>
<pre><code class="lang-bash">$ ng <span class="hljs-built_in">test</span> --coverage

 % Coverage report from v8
------------------------------------|---------|----------|---------|---------|
File                                | % Stmts | % Branch | % Funcs | % Lines |
------------------------------------|---------|----------|---------|---------|
All files                           |   87.50 |    82.35 |   91.17 |   88.12 |
 src/app/components                 |   92.30 |    88.88 |   95.00 |   93.10 |
  user-list.component.ts            |   95.45 |    91.66 |  100.00 |   96.29 |
  user-detail.component.ts          |   89.47 |    85.71 |   90.00 |   90.47 |
 src/app/services                   |   84.21 |    77.77 |   88.23 |   85.00 |
  user.service.ts                   |   91.66 |    87.50 |   93.75 |   92.30 |
  auth.service.ts                   |   76.92 |    66.66 |   83.33 |   78.26 |
 src/app/guards                     |   80.00 |    75.00 |   85.00 |   81.81 |
  auth.guard.ts                     |   80.00 |    75.00 |   85.00 |   81.81 |
------------------------------------|---------|----------|---------|---------|
</code></pre>
<h3 id="heading-failed-tests-output"><strong>Failed Tests Output</strong></h3>
<pre><code class="lang-bash">$ ng <span class="hljs-built_in">test</span>

 ✓ src/app/services/user.service.spec.ts (3)
 ✗ src/app/components/user-list.component.spec.ts (1 failed)

 FAIL  src/app/components/user-list.component.spec.ts &gt; UserListComponent &gt; should display user names
AssertionError: expected <span class="hljs-string">'John Smith'</span> to be <span class="hljs-string">'John Doe'</span>
 ❯ src/app/components/user-list.component.spec.ts:45:52
    43|     fixture.detectChanges();
    44|     const nameElement = fixture.nativeElement.querySelector(<span class="hljs-string">'.user-name'</span>);
    45|     expect(nameElement.textContent).toBe(<span class="hljs-string">'John Doe'</span>);
      |                                     ^
    46|   });
    47| });

 Test Files  1 failed | 1 passed (2)
      Tests  1 failed | 3 passed (4)
</code></pre>
<h3 id="heading-slow-tests-warning"><strong>Slow Tests Warning</strong></h3>
<pre><code class="lang-bash"> SLOW  src/app/services/heavy-computation.service.spec.ts &gt; should calculate complex data
       Duration: 2.5s (threshold: 1s)
</code></pre>
<p>Vitest automatically highlights tests that take longer than expected—super helpful for identifying performance bottlenecks.</p>
<hr />
<h2 id="heading-handling-edge-cases-in-vitest-real-angular-examples"><strong>Handling Edge Cases in Vitest: Real Angular Examples</strong></h2>
<p>This is where Vitest really shines. Let's tackle the tricky stuff.</p>
<h3 id="heading-1-testing-async-operations"><strong>1. Testing Async Operations</strong></h3>
<p><strong>Async/Await Pattern:</strong></p>
<pre><code class="lang-tsx">// notification.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class NotificationService {
  private http = inject(HttpClient);

  async sendNotification(message: string): Promise&lt;{ success: boolean }&gt; {
    const response = await firstValueFrom(
      this.http.post&lt;{ success: boolean }&gt;('/api/notifications', { message })
    );
    return response;
  }

  async getNotifications(): Promise&lt;any[]&gt; {
    return firstValueFrom(this.http.get&lt;any[]&gt;('/api/notifications'));
  }
}

// notification.service.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { NotificationService } from './notification.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('NotificationService - Async Tests', () =&gt; {
  let service: NotificationService;
  let httpMock: any;

  beforeEach(() =&gt; {
    httpMock = {
      post: vi.fn(),
      get: vi.fn()
    };

    TestBed.configureTestingModule({
      providers: [
        { provide: HttpClient, useValue: httpMock }
      ]
    });

    service = TestBed.inject(NotificationService);
  });

  it('should send notification successfully', async () =&gt; {
    httpMock.post.mockReturnValue(of({ success: true }));

    const result = await service.sendNotification('Hello World');

    expect(result.success).toBe(true);
    expect(httpMock.post).toHaveBeenCalledWith('/api/notifications', {
      message: 'Hello World'
    });
  });

  it('should handle network errors gracefully', async () =&gt; {
    httpMock.post.mockReturnValue(
      new Promise((_, reject) =&gt; reject(new Error('Network error')))
    );

    await expect(service.sendNotification('Test')).rejects.toThrow('Network error');
  });
});
</code></pre>
<h3 id="heading-2-testing-timers-with-fake-timers"><strong>2. Testing Timers with Fake Timers</strong></h3>
<p><strong>setTimeout/setInterval:</strong></p>
<pre><code class="lang-tsx">// polling.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class PollingService {
  private intervalId?: number;
  public callCount = 0;

  startPolling(callback: () =&gt; void, interval: number): void {
    this.intervalId = window.setInterval(() =&gt; {
      this.callCount++;
      callback();
    }, interval);
  }

  stopPolling(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = undefined;
    }
  }
}

// polling.service.spec.ts
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { PollingService } from './polling.service';

describe('PollingService - Timer Tests', () =&gt; {
  let service: PollingService;

  beforeEach(() =&gt; {
    vi.useFakeTimers();
    TestBed.configureTestingModule({});
    service = TestBed.inject(PollingService);
  });

  afterEach(() =&gt; {
    vi.useRealTimers();
  });

  it('should call callback every 1000ms', async () =&gt; {
    const callback = vi.fn();

    service.startPolling(callback, 1000);

    // Advance time by 3000ms
    await vi.advanceTimersByTimeAsync(3000);

    expect(callback).toHaveBeenCalledTimes(3);
    expect(service.callCount).toBe(3);

    service.stopPolling();
  });

  it('should stop polling when stopPolling is called', async () =&gt; {
    const callback = vi.fn();

    service.startPolling(callback, 1000);
    await vi.advanceTimersByTimeAsync(2000);

    service.stopPolling();

    await vi.advanceTimersByTimeAsync(2000);

    // Should still be 2, not 4
    expect(callback).toHaveBeenCalledTimes(2);
  });
});
</code></pre>
<h3 id="heading-3-mocking-browser-apis"><strong>3. Mocking Browser APIs</strong></h3>
<p><strong>LocalStorage:</strong></p>
<pre><code class="lang-tsx">// theme.service.ts
import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ThemeService {
  theme = signal&lt;'light' | 'dark'&gt;('light');

  constructor() {
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme === 'dark' || savedTheme === 'light') {
      this.theme.set(savedTheme);
    }
  }

  toggleTheme(): void {
    const newTheme = this.theme() === 'light' ? 'dark' : 'light';
    this.theme.set(newTheme);
    localStorage.setItem('theme', newTheme);
  }
}

// theme.service.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { ThemeService } from './theme.service';

describe('ThemeService - LocalStorage Tests', () =&gt; {
  beforeEach(() =&gt; {
    localStorage.clear();
  });

  it('should initialize with light theme by default', () =&gt; {
    const service = TestBed.inject(ThemeService);
    expect(service.theme()).toBe('light');
  });

  it('should load saved theme from localStorage', () =&gt; {
    localStorage.setItem('theme', 'dark');

    const service = TestBed.inject(ThemeService);
    expect(service.theme()).toBe('dark');
  });

  it('should save theme to localStorage when toggled', () =&gt; {
    const service = TestBed.inject(ThemeService);

    service.toggleTheme();

    expect(localStorage.getItem('theme')).toBe('dark');
    expect(service.theme()).toBe('dark');
  });
});
</code></pre>
<p><strong>Window.location:</strong></p>
<pre><code class="lang-tsx">// navigation.service.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';

describe('Window Location Mocking', () =&gt; {
  beforeEach(() =&gt; {
    // Mock window.location
    delete (window as any).location;
    window.location = {
      href: '&lt;http://localhost:4200/&gt;',
      pathname: '/',
      search: '',
      hash: ''
    } as any;
  });

  it('should navigate to external URL', () =&gt; {
    const navigateSpy = vi.spyOn(window.location, 'href', 'set');

    window.location.href = '&lt;https://example.com&gt;';

    expect(navigateSpy).toHaveBeenCalledWith('&lt;https://example.com&gt;');
  });
});
</code></pre>
<p><strong>IntersectionObserver:</strong></p>
<pre><code class="lang-tsx">// lazy-load.directive.spec.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';

describe('IntersectionObserver Mocking', () =&gt; {
  let observeMock: any;
  let unobserveMock: any;

  beforeEach(() =&gt; {
    observeMock = vi.fn();
    unobserveMock = vi.fn();

    global.IntersectionObserver = vi.fn().mockImplementation((callback) =&gt; ({
      observe: observeMock,
      unobserve: unobserveMock,
      disconnect: vi.fn(),
    }));
  });

  it('should observe element for lazy loading', () =&gt; {
    const element = document.createElement('img');
    const observer = new IntersectionObserver(() =&gt; {});

    observer.observe(element);

    expect(observeMock).toHaveBeenCalledWith(element);
  });
});
</code></pre>
<h3 id="heading-4-testing-dom-interactions-with-happy-dom"><strong>4. Testing DOM Interactions with Happy-DOM</strong></h3>
<pre><code class="lang-tsx">// dropdown.component.ts
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-dropdown',
  template: `
    &lt;div class="dropdown"&gt;
      &lt;button (click)="toggle()" class="dropdown-toggle"&gt;
        {{ isOpen() ? 'Close' : 'Open' }} Menu
      &lt;/button&gt;
      @if (isOpen()) {
        &lt;ul class="dropdown-menu"&gt;
          &lt;li&gt;&lt;a href="/profile"&gt;Profile&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="/settings"&gt;Settings&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href="/logout"&gt;Logout&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      }
    &lt;/div&gt;
  `,
  styles: [`
    .dropdown { position: relative; }
    .dropdown-menu { position: absolute; top: 100%; }
  `]
})
export class DropdownComponent {
  isOpen = signal(false);

  toggle(): void {
    this.isOpen.update(value =&gt; !value);
  }
}

// dropdown.component.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DropdownComponent } from './dropdown.component';

describe('DropdownComponent - DOM Interactions', () =&gt; {
  let component: DropdownComponent;
  let fixture: ComponentFixture&lt;DropdownComponent&gt;;

  beforeEach(async () =&gt; {
    await TestBed.configureTestingComponent({
      imports: [DropdownComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(DropdownComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should toggle dropdown menu on button click', () =&gt; {
    const button = fixture.nativeElement.querySelector('.dropdown-toggle');

    // Initially closed
    expect(component.isOpen()).toBe(false);
    expect(fixture.nativeElement.querySelector('.dropdown-menu')).toBeNull();

    // Click to open
    button.click();
    fixture.detectChanges();

    expect(component.isOpen()).toBe(true);
    expect(fixture.nativeElement.querySelector('.dropdown-menu')).not.toBeNull();

    // Click to close
    button.click();
    fixture.detectChanges();

    expect(component.isOpen()).toBe(false);
    expect(fixture.nativeElement.querySelector('.dropdown-menu')).toBeNull();
  });

  it('should display correct button text based on state', () =&gt; {
    const button = fixture.nativeElement.querySelector('.dropdown-toggle');

    expect(button.textContent.trim()).toBe('Open Menu');

    button.click();
    fixture.detectChanges();

    expect(button.textContent.trim()).toBe('Close Menu');
  });

  it('should render menu items when open', () =&gt; {
    component.isOpen.set(true);
    fixture.detectChanges();

    const menuItems = fixture.nativeElement.querySelectorAll('.dropdown-menu li');
    expect(menuItems.length).toBe(3);
    expect(menuItems[0].textContent).toBe('Profile');
    expect(menuItems[1].textContent).toBe('Settings');
    expect(menuItems[2].textContent).toBe('Logout');
  });
});
</code></pre>
<p>Have you run into issues testing browser APIs in the past? This approach makes it way simpler.</p>
<hr />
<h2 id="heading-component-testing-in-angular-21-vitest-complete-example"><strong>Component Testing in Angular 21 + Vitest: Complete Example</strong></h2>
<p>Let's build a realistic component test using Angular Testing Library integration.</p>
<h3 id="heading-the-component"><strong>The Component</strong></h3>
<pre><code class="lang-tsx">// todo-list.component.ts
import { Component, signal, computed } from '@angular/core';
import { FormsModule } from '@angular/forms';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

@Component({
  selector: 'app-todo-list',
  imports: [FormsModule],
  template: `
    &lt;div class="todo-container"&gt;
      &lt;h2&gt;My Todo List&lt;/h2&gt;

      &lt;div class="todo-input"&gt;
        &lt;input
          type="text"
          [(ngModel)]="newTodoText"
          (keyup.enter)="addTodo()"
          placeholder="Add a new todo..."
          data-testid="todo-input"
        /&gt;
        &lt;button
          (click)="addTodo()"
          [disabled]="!newTodoText().trim()"
          data-testid="add-button"
        &gt;
          Add
        &lt;/button&gt;
      &lt;/div&gt;

      &lt;div class="todo-stats"&gt;
        &lt;span data-testid="total-count"&gt;Total: {{ totalCount() }}&lt;/span&gt;
        &lt;span data-testid="completed-count"&gt;Completed: {{ completedCount() }}&lt;/span&gt;
        &lt;span data-testid="pending-count"&gt;Pending: {{ pendingCount() }}&lt;/span&gt;
      &lt;/div&gt;

      &lt;ul class="todo-list"&gt;
        @for (todo of todos(); track todo.id) {
          &lt;li [class.completed]="todo.completed" [attr.data-testid]="'todo-' + todo.id"&gt;
            &lt;input
              type="checkbox"
              [checked]="todo.completed"
              (change)="toggleTodo(todo.id)"
              [attr.data-testid]="'checkbox-' + todo.id"
            /&gt;
            &lt;span class="todo-text"&gt;{{ todo.text }}&lt;/span&gt;
            &lt;button
              (click)="deleteTodo(todo.id)"
              [attr.data-testid]="'delete-' + todo.id"
              class="delete-btn"
            &gt;
              Delete
            &lt;/button&gt;
          &lt;/li&gt;
        }
      &lt;/ul&gt;

      @if (todos().length === 0) {
        &lt;p class="empty-state" data-testid="empty-state"&gt;
          No todos yet. Add one above!
        &lt;/p&gt;
      }
    &lt;/div&gt;
  `,
  styles: [`
    .todo-container { max-width: 600px; margin: 0 auto; padding: 20px; }
    .todo-input { display: flex; gap: 10px; margin-bottom: 20px; }
    .todo-input input { flex: 1; padding: 8px; }
    .todo-stats { display: flex; gap: 15px; margin-bottom: 20px; color: #666; }
    .todo-list { list-style: none; padding: 0; }
    .todo-list li { display: flex; align-items: center; gap: 10px; padding: 10px; border-bottom: 1px solid #eee; }
    .todo-list li.completed .todo-text { text-decoration: line-through; color: #999; }
    .delete-btn { margin-left: auto; color: red; }
    .empty-state { text-align: center; color: #999; padding: 40px; }
  `]
})
export class TodoListComponent {
  todos = signal&lt;Todo[]&gt;([]);
  newTodoText = signal('');
  private nextId = 1;

  totalCount = computed(() =&gt; this.todos().length);
  completedCount = computed(() =&gt; this.todos().filter(t =&gt; t.completed).length);
  pendingCount = computed(() =&gt; this.todos().filter(t =&gt; !t.completed).length);

  addTodo(): void {
    const text = this.newTodoText().trim();
    if (!text) return;

    this.todos.update(todos =&gt; [
      ...todos,
      { id: this.nextId++, text, completed: false }
    ]);
    this.newTodoText.set('');
  }

  toggleTodo(id: number): void {
    this.todos.update(todos =&gt;
      todos.map(todo =&gt;
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }

  deleteTodo(id: number): void {
    this.todos.update(todos =&gt; todos.filter(todo =&gt; todo.id !== id));
  }
}
</code></pre>
<h3 id="heading-comprehensive-test-suite"><strong>Comprehensive Test Suite</strong></h3>
<pre><code class="lang-tsx">// todo-list.component.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TodoListComponent } from './todo-list.component';

describe('TodoListComponent', () =&gt; {
  let component: TodoListComponent;
  let fixture: ComponentFixture&lt;TodoListComponent&gt;;

  beforeEach(async () =&gt; {
    await TestBed.configureTestingModule({
      imports: [TodoListComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TodoListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  describe('Initial Rendering', () =&gt; {
    it('should create the component', () =&gt; {
      expect(component).toBeTruthy();
    });

    it('should display empty state when no todos', () =&gt; {
      const emptyState = fixture.nativeElement.querySelector('[data-testid="empty-state"]');
      expect(emptyState).toBeTruthy();
      expect(emptyState.textContent).toContain('No todos yet');
    });

    it('should display correct initial stats', () =&gt; {
      const total = fixture.nativeElement.querySelector('[data-testid="total-count"]');
      const completed = fixture.nativeElement.querySelector('[data-testid="completed-count"]');
      const pending = fixture.nativeElement.querySelector('[data-testid="pending-count"]');

      expect(total.textContent).toBe('Total: 0');
      expect(completed.textContent).toBe('Completed: 0');
      expect(pending.textContent).toBe('Pending: 0');
    });
  });

  describe('Adding Todos', () =&gt; {
    it('should add a new todo when clicking add button', () =&gt; {
      const input = fixture.nativeElement.querySelector('[data-testid="todo-input"]');
      const button = fixture.nativeElement.querySelector('[data-testid="add-button"]');

      input.value = 'Buy groceries';
      input.dispatchEvent(new Event('input'));
      fixture.detectChanges();

      button.click();
      fixture.detectChanges();

      const todoItems = fixture.nativeElement.querySelectorAll('.todo-list li');
      expect(todoItems.length).toBe(1);
      expect(todoItems[0].textContent).toContain('Buy groceries');
    });

    it('should add todo when pressing Enter key', () =&gt; {
      const input = fixture.nativeElement.querySelector('[data-testid="todo-input"]');

      input.value = 'Write tests';
      input.dispatchEvent(new Event('input'));
      fixture.detectChanges();

      const enterEvent = new KeyboardEvent('keyup', { key: 'Enter' });
      input.dispatchEvent(enterEvent);
      fixture.detectChanges();

      expect(component.todos().length).toBe(1);
      expect(component.todos()[0].text).toBe('Write tests');
    });

    it('should not add empty todos', () =&gt; {
      const button = fixture.nativeElement.querySelector('[data-testid="add-button"]');

      component.newTodoText.set('   ');
      fixture.detectChanges();

      button.click();
      fixture.detectChanges();

      expect(component.todos().length).toBe(0);
    });

    it('should clear input after adding todo', () =&gt; {
      const input = fixture.nativeElement.querySelector('[data-testid="todo-input"]');
      const button = fixture.nativeElement.querySelector('[data-testid="add-button"]');

      input.value = 'Test todo';
      input.dispatchEvent(new Event('input'));
      component.newTodoText.set('Test todo');
      fixture.detectChanges();

      button.click();
      fixture.detectChanges();

      expect(component.newTodoText()).toBe('');
    });

    it('should update stats after adding todos', () =&gt; {
      component.newTodoText.set('First todo');
      component.addTodo();
      component.newTodoText.set('Second todo');
      component.addTodo();
      fixture.detectChanges();

      const total = fixture.nativeElement.querySelector('[data-testid="total-count"]');
      const pending = fixture.nativeElement.querySelector('[data-testid="pending-count"]');

      expect(total.textContent).toBe('Total: 2');
      expect(pending.textContent).toBe('Pending: 2');
    });
  });

  describe('Toggling Todos', () =&gt; {
    beforeEach(() =&gt; {
      component.newTodoText.set('Test todo');
      component.addTodo();
      fixture.detectChanges();
    });

    it('should toggle todo completion on checkbox click', () =&gt; {
      const checkbox = fixture.nativeElement.querySelector('[data-testid="checkbox-1"]');

      expect(component.todos()[0].completed).toBe(false);

      checkbox.click();
      fixture.detectChanges();

      expect(component.todos()[0].completed).toBe(true);
    });

    it('should apply completed class when todo is completed', () =&gt; {
      const checkbox = fixture.nativeElement.querySelector('[data-testid="checkbox-1"]');

      checkbox.click();
      fixture.detectChanges();

      const todoItem = fixture.nativeElement.querySelector('[data-testid="todo-1"]');
      expect(todoItem.classList.contains('completed')).toBe(true);
    });

    it('should update completed count when toggling', () =&gt; {
      const checkbox = fixture.nativeElement.querySelector('[data-testid="checkbox-1"]');

      checkbox.click();
      fixture.detectChanges();

      const completedCount = fixture.nativeElement.querySelector('[data-testid="completed-count"]');
      const pendingCount = fixture.nativeElement.querySelector('[data-testid="pending-count"]');

      expect(completedCount.textContent).toBe('Completed: 1');
      expect(pendingCount.textContent).toBe('Pending: 0');
    });
  });

  describe('Deleting Todos', () =&gt; {
    beforeEach(() =&gt; {
      component.newTodoText.set('Todo 1');
      component.addTodo();
      component.newTodoText.set('Todo 2');
      component.addTodo();
      fixture.detectChanges();
    });

    it('should delete todo when clicking delete button', () =&gt; {
      const deleteButton = fixture.nativeElement.querySelector('[data-testid="delete-1"]');

      expect(component.todos().length).toBe(2);

      deleteButton.click();
      fixture.detectChanges();

      expect(component.todos().length).toBe(1);
      expect(component.todos()[0].text).toBe('Todo 2');
    });

    it('should show empty state after deleting all todos', () =&gt; {
      const delete1 = fixture.nativeElement.querySelector('[data-testid="delete-1"]');
      const delete2 = fixture.nativeElement.querySelector('[data-testid="delete-2"]');

      delete1.click();
      fixture.detectChanges();

      delete2.click();
      fixture.detectChanges();

      const emptyState = fixture.nativeElement.querySelector('[data-testid="empty-state"]');
      expect(emptyState).toBeTruthy();
    });

    it('should update stats after deletion', () =&gt; {
      const deleteButton = fixture.nativeElement.querySelector('[data-testid="delete-1"]');

      deleteButton.click();
      fixture.detectChanges();

      const total = fixture.nativeElement.querySelector('[data-testid="total-count"]');
      expect(total.textContent).toBe('Total: 1');
    });
  });

  describe('Computed Properties', () =&gt; {
    it('should correctly calculate total count', () =&gt; {
      component.todos.set([
        { id: 1, text: 'Todo 1', completed: false },
        { id: 2, text: 'Todo 2', completed: true },
        { id: 3, text: 'Todo 3', completed: false }
      ]);

      expect(component.totalCount()).toBe(3);
    });

    it('should correctly calculate completed count', () =&gt; {
      component.todos.set([
        { id: 1, text: 'Todo 1', completed: false },
        { id: 2, text: 'Todo 2', completed: true },
        { id: 3, text: 'Todo 3', completed: true }
      ]);

      expect(component.completedCount()).toBe(2);
    });

    it('should correctly calculate pending count', () =&gt; {
      component.todos.set([
        { id: 1, text: 'Todo 1', completed: false },
        { id: 2, text: 'Todo 2', completed: true },
        { id: 3, text: 'Todo 3', completed: false }
      ]);

      expect(component.pendingCount()).toBe(2);
    });
  });
});
</code></pre>
<p>This test suite covers:</p>
<ul>
<li><p>Initial rendering and empty states</p>
</li>
<li><p>Adding todos via button and Enter key</p>
</li>
<li><p>Input validation and clearing</p>
</li>
<li><p>Toggling completion status</p>
</li>
<li><p>Deleting todos</p>
</li>
<li><p>Stats calculations</p>
</li>
<li><p>Edge cases like empty strings and multiple operations</p>
</li>
</ul>
<hr />
<h2 id="heading-integration-with-cicd-making-it-production-ready"><strong>Integration with CI/CD: Making It Production-Ready</strong></h2>
<p>Let's make sure your tests run smoothly in your pipeline.</p>
<h3 id="heading-github-actions-example"><strong>GitHub Actions Example</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># .github/workflows/test.yml</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Tests</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span>, <span class="hljs-string">develop</span> ]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">main</span> ]

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">test:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>

    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">Node.js</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-string">'20'</span>
          <span class="hljs-attr">cache:</span> <span class="hljs-string">'npm'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span> <span class="hljs-string">with</span> <span class="hljs-string">coverage</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test:ci</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">coverage</span> <span class="hljs-string">thresholds</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test:coverage:check</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">coverage</span> <span class="hljs-string">to</span> <span class="hljs-string">Codecov</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">codecov/codecov-action@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">files:</span> <span class="hljs-string">./coverage/coverage.icov,./coverage/lcov.info</span>
          <span class="hljs-attr">format:</span> <span class="hljs-string">icov</span>
          <span class="hljs-attr">fail_ci_if_error:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">coverage</span> <span class="hljs-string">artifacts</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">coverage-report</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">coverage/</span>
</code></pre>
<h3 id="heading-packagejson-scripts"><strong>Package.json Scripts</strong></h3>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"ng test"</span>,
    <span class="hljs-attr">"test:ci"</span>: <span class="hljs-string">"ng test --run --coverage"</span>,
    <span class="hljs-attr">"test:coverage"</span>: <span class="hljs-string">"ng test --coverage"</span>,
    <span class="hljs-attr">"test:coverage:check"</span>: <span class="hljs-string">"vitest --run --coverage --coverage.thresholds.lines=80"</span>
  }
}
</code></pre>
<h3 id="heading-azure-devops-pipeline"><strong>Azure DevOps Pipeline</strong></h3>
<pre><code class="lang-yaml"><span class="hljs-comment"># azure-pipelines.yml</span>
<span class="hljs-attr">trigger:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">develop</span>

<span class="hljs-attr">pool:</span>
  <span class="hljs-attr">vmImage:</span> <span class="hljs-string">'ubuntu-latest'</span>

<span class="hljs-attr">steps:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">NodeTool@0</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">versionSpec:</span> <span class="hljs-string">'20.x'</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install Node.js'</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Install dependencies'</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">script:</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">test:ci</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Run Vitest tests'</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishCodeCoverageResults@1</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">codeCoverageTool:</span> <span class="hljs-string">'icov'</span>
      <span class="hljs-attr">summaryFileLocation:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/coverage/coverage.icov'</span>
      <span class="hljs-attr">pathToSources:</span> <span class="hljs-string">'$(Build.SourcesDirectory)/src'</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Publish coverage results'</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">task:</span> <span class="hljs-string">PublishTestResults@2</span>
    <span class="hljs-attr">inputs:</span>
      <span class="hljs-attr">testResultsFormat:</span> <span class="hljs-string">'JUnit'</span>
      <span class="hljs-attr">testResultsFiles:</span> <span class="hljs-string">'**/junit.xml'</span>
      <span class="hljs-attr">failTaskOnFailedTests:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">displayName:</span> <span class="hljs-string">'Publish test results'</span>
</code></pre>
<h3 id="heading-fail-on-coverage-threshold"><strong>Fail on Coverage Threshold</strong></h3>
<p>Update your <code>vitest.config.ts</code>:</p>
<pre><code class="lang-tsx">export default defineConfig({
  test: {
    coverage: {
      thresholds: {
        lines: 80,
        functions: 75,
        branches: 70,
        statements: 80
      },
      // Fail CI if thresholds aren't met
      thresholdAutoUpdate: false
    }
  }
});
</code></pre>
<p>When coverage drops below threshold:</p>
<pre><code class="lang-bash">ERROR: Coverage <span class="hljs-keyword">for</span> lines (78.5%) does not meet global threshold (80%)
</code></pre>
<p>Your CI/CD pipeline will fail, preventing low-quality code from merging.</p>
<hr />
<h2 id="heading-best-practices-and-recommendations"><strong>Best Practices and Recommendations</strong></h2>
<p>Let me share some hard-won wisdom from real-world Angular testing.</p>
<h3 id="heading-1-folder-structure"><strong>1. Folder Structure</strong></h3>
<pre><code class="lang-typescript">src/
├── app/
│   ├── components/
│   │   ├── user-list/
│   │   │   ├── user-list.component.ts
│   │   │   ├── user-list.component.spec.ts
│   │   │   └── user-list.component.css
│   ├── services/
│   │   ├── user.service.ts
│   │   ├── user.service.spec.ts
│   ├── guards/
│   │   ├── auth.guard.ts
│   │   ├── auth.guard.spec.ts
│   ├── pipes/
│   │   ├── format-date.pipe.ts
│   │   ├── format-date.pipe.spec.ts
├── test/
│   ├── helpers/
│   │   ├── mock-data.ts
│   │   ├── test-utils.ts
│   │   └── custom-matchers.ts
│   └── fixtures/
│       ├── user-fixtures.ts
│       └── api-responses.ts
├── test-setup.ts
└── vitest.config.ts
</code></pre>
<p><strong>Why this works:</strong></p>
<ul>
<li><p>Tests live next to the code they test</p>
</li>
<li><p>Shared test utilities in dedicated folder</p>
</li>
<li><p>Easy to find and maintain</p>
</li>
</ul>
<h3 id="heading-2-naming-conventions"><strong>2. Naming Conventions</strong></h3>
<pre><code class="lang-tsx">// Good: Descriptive, action-oriented
it('should display error message when API call fails', () =&gt; {});
it('should disable submit button when form is invalid', () =&gt; {});

// Bad: Vague, implementation-focused
it('should work', () =&gt; {});
it('should test the method', () =&gt; {});
</code></pre>
<p><strong>Pattern to follow:</strong></p>
<ul>
<li><p>Start with "should"</p>
</li>
<li><p>Describe the behavior, not the implementation</p>
</li>
<li><p>Include the condition and expected outcome</p>
</li>
</ul>
<h3 id="heading-3-organize-test-utilities"><strong>3. Organize Test Utilities</strong></h3>
<pre><code class="lang-tsx">// test/helpers/test-utils.ts
import { ComponentFixture } from '@angular/core/testing';

export function findByTestId&lt;T&gt;(
  fixture: ComponentFixture&lt;T&gt;,
  testId: string
): HTMLElement {
  return fixture.nativeElement.querySelector(`[data-testid="${testId}"]`);
}

export function clickButton&lt;T&gt;(
  fixture: ComponentFixture&lt;T&gt;,
  testId: string
): void {
  const button = findByTestId(fixture, testId);
  button.click();
  fixture.detectChanges();
}

export async function fillInput&lt;T&gt;(
  fixture: ComponentFixture&lt;T&gt;,
  testId: string,
  value: string
): Promise&lt;void&gt; {
  const input = findByTestId(fixture, testId) as HTMLInputElement;
  input.value = value;
  input.dispatchEvent(new Event('input'));
  fixture.detectChanges();
  await fixture.whenStable();
}

// Usage in tests
import { findByTestId, clickButton, fillInput } from '../../../test/helpers/test-utils';

it('should add todo', async () =&gt; {
  await fillInput(fixture, 'todo-input', 'New todo');
  clickButton(fixture, 'add-button');

  const todoItem = findByTestId(fixture, 'todo-1');
  expect(todoItem).toBeTruthy();
});
</code></pre>
<h3 id="heading-4-mocking-guidelines"><strong>4. Mocking Guidelines</strong></h3>
<p><strong>Do mock:</strong></p>
<ul>
<li><p>External HTTP calls</p>
</li>
<li><p>Browser APIs (localStorage, navigator, etc.)</p>
</li>
<li><p>Third-party services</p>
</li>
<li><p>Time-dependent code (setTimeout, Date.now())</p>
</li>
</ul>
<p><strong>Don't mock:</strong></p>
<ul>
<li><p>Angular core functionality (TestBed, ComponentFixture)</p>
</li>
<li><p>Simple utility functions</p>
</li>
<li><p>Your own business logic (test it instead)</p>
</li>
</ul>
<pre><code class="lang-tsx">// Good: Mock external dependency
const httpMock = {
  get: vi.fn().mockReturnValue(of(mockData))
};

// Bad: Mocking your own logic defeats the purpose
const userServiceMock = {
  calculateAge: vi.fn().mockReturnValue(25) // Just test the real calculateAge!
};
</code></pre>
<h3 id="heading-5-avoid-snapshot-overuse"><strong>5. Avoid Snapshot Overuse</strong></h3>
<p>Snapshots are useful but can become a maintenance nightmare.</p>
<p><strong>Use snapshots for:</strong></p>
<ul>
<li><p>Complex, static HTML structures</p>
</li>
<li><p>Generated content that rarely changes</p>
</li>
<li><p>Third-party component output</p>
</li>
</ul>
<p><strong>Don't use snapshots for:</strong></p>
<ul>
<li><p>Dynamic content</p>
</li>
<li><p>Forms with user interaction</p>
</li>
<li><p>Anything that changes frequently</p>
</li>
</ul>
<pre><code class="lang-tsx">// Good: Targeted assertion
it('should display user name', () =&gt; {
  expect(fixture.nativeElement.querySelector('.user-name').textContent).toBe('John');
});

// Questionable: Entire component snapshot
it('should render correctly', () =&gt; {
  expect(fixture.nativeElement.innerHTML).toMatchSnapshot();
});
</code></pre>
<h3 id="heading-6-test-behavior-not-implementation"><strong>6. Test Behavior, Not Implementation</strong></h3>
<pre><code class="lang-tsx">// Bad: Testing implementation details
it('should call ngOnInit', () =&gt; {
  const spy = vi.spyOn(component, 'ngOnInit');
  component.ngOnInit();
  expect(spy).toHaveBeenCalled();
});

// Good: Testing behavior
it('should load users on component initialization', () =&gt; {
  // ngOnInit is called automatically by TestBed
  expect(component.users().length).toBeGreaterThan(0);
});
</code></pre>
<h3 id="heading-7-keep-tests-independent"><strong>7. Keep Tests Independent</strong></h3>
<pre><code class="lang-tsx">// Bad: Tests depend on each other
describe('UserService', () =&gt; {
  let users: User[];

  it('should add user', () =&gt; {
    users = service.addUser({ name: 'John' });
    expect(users.length).toBe(1);
  });

  it('should find user', () =&gt; {
    // Depends on previous test!
    const user = service.findUser('John');
    expect(user).toBeTruthy();
  });
});

// Good: Each test is independent
describe('UserService', () =&gt; {
  beforeEach(() =&gt; {
    service.reset();
  });

  it('should add user', () =&gt; {
    const users = service.addUser({ name: 'John' });
    expect(users.length).toBe(1);
  });

  it('should find user', () =&gt; {
    service.addUser({ name: 'John' });
    const user = service.findUser('John');
    expect(user).toBeTruthy();
  });
});
</code></pre>
<h3 id="heading-8-use-descriptive-test-data"><strong>8. Use Descriptive Test Data</strong></h3>
<pre><code class="lang-tsx">// Bad: Magic numbers and strings
it('should calculate discount', () =&gt; {
  expect(service.calculate(100, 0.1)).toBe(90);
});

// Good: Named constants with context
it('should apply 10% discount correctly', () =&gt; {
  const originalPrice = 100;
  const discountRate = 0.1;
  const expectedPrice = 90;

  expect(service.calculate(originalPrice, discountRate)).toBe(expectedPrice);
});
</code></pre>
<hr />
<h2 id="heading-bonus-tips-level-up-your-vitest-game"><strong>Bonus Tips: Level Up Your Vitest Game</strong></h2>
<p>Here are some insider tricks that most developers miss.</p>
<h3 id="heading-tip-1-use-custom-matchers"><strong>Tip 1: Use Custom Matchers</strong></h3>
<pre><code class="lang-tsx">// test/helpers/custom-matchers.ts
import { expect } from 'vitest';

expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received &gt;= floor &amp;&amp; received &lt;= ceiling;
    return {
      pass,
      message: () =&gt;
        pass
          ? `expected ${received} not to be within range ${floor} - ${ceiling}`
          : `expected ${received} to be within range ${floor} - ${ceiling}`
    };
  }
});

// Usage
expect(response.time).toBeWithinRange(100, 200);
</code></pre>
<h3 id="heading-tip-2-parallel-test-execution"><strong>Tip 2: Parallel Test Execution</strong></h3>
<pre><code class="lang-tsx">// vitest.config.ts
export default defineConfig({
  test: {
    pool: 'threads',
    poolOptions: {
      threads: {
        singleThread: false,
        isolate: true
      }
    },
    maxConcurrency: 5 // Run up to 5 test files in parallel
  }
});
</code></pre>
<h3 id="heading-tip-3-test-specific-timeouts"><strong>Tip 3: Test-Specific Timeouts</strong></h3>
<pre><code class="lang-tsx">// For slow integration tests
it('should handle large dataset', async () =&gt; {
  await service.processLargeFile();
  expect(service.processed).toBe(true);
}, 10000); // 10 second timeout for this test only
</code></pre>
<h3 id="heading-tip-4-debug-failing-tests"><strong>Tip 4: Debug Failing Tests</strong></h3>
<pre><code class="lang-tsx">// vitest.config.ts
export default defineConfig({
  test: {
    // Stop on first failure
    bail: 1,

    // Retry flaky tests
    retry: 2
  }
});
</code></pre>
<p>Run specific test file:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> src/app/services/user.service.spec.ts
</code></pre>
<p>Run tests matching pattern:</p>
<pre><code class="lang-bash">ng <span class="hljs-built_in">test</span> --reporter=verbose --grep=<span class="hljs-string">"should handle errors"</span>
</code></pre>
<h3 id="heading-tip-5-environment-variables-for-tests"><strong>Tip 5: Environment Variables for Tests</strong></h3>
<pre><code class="lang-tsx">// test-setup.ts
import { beforeAll } from 'vitest';

beforeAll(() =&gt; {
  process.env.API_URL = '&lt;http://localhost:4200/api&gt;';
  process.env.NODE_ENV = 'test';
});

// Usage in tests
it('should use test API URL', () =&gt; {
  expect(service.apiUrl).toBe('&lt;http://localhost:4200/api&gt;');
});
</code></pre>
<hr />
<h2 id="heading-recap-why-vitest-is-angulars-future"><strong>Recap: Why Vitest is Angular's Future</strong></h2>
<p>Let's bring it all together.</p>
<p><strong>What we've covered:</strong></p>
<p><strong>1. The Why:</strong> Angular dropped Karma because Vitest is faster, more modern, and built for today's JavaScript ecosystem.</p>
<p><strong>2. Key Features:</strong> Instant hot reload, native TypeScript, powerful mocking with <code>vi</code>, and browser-like environments without the browser overhead.</p>
<p><strong>3. Migration:</strong> Step-by-step guide from Jasmine to Vitest, including syntax changes and real code examples.</p>
<p><strong>4. Coverage:</strong> How to enable ICOV format, set thresholds, and integrate with modern CI/CD pipelines.</p>
<p><strong>5. Real-World Testing:</strong> Async operations, timers, browser APIs, DOM interactions—all the tricky stuff you actually need.</p>
<p><strong>6. Best Practices:</strong> Folder structure, naming conventions, when to mock, and how to keep tests maintainable.</p>
<p><strong>The Bottom Line:</strong></p>
<p>Vitest isn't just a testing framework—it's a developer experience upgrade. Your tests run 10x faster. Your feedback loop is instant. Your CI/CD pipeline finishes before you context-switch.</p>
<p>If you're starting a new Angular 21 project, Vitest is a no-brainer. If you have an existing app, the migration effort pays for itself in saved time within weeks.</p>
<p>The Angular team made the right call. Now it's your turn.</p>
<hr />
<h2 id="heading-what-did-you-think"><strong>What Did You Think?</strong></h2>
<p>I'd genuinely love to hear your experience with Vitest. Have you migrated from Karma yet? What challenges did you face? Drop a comment below—especially if you've run into edge cases not covered here.</p>
<p><strong>Found this helpful?</strong> Give it a clap (or five) so other Angular developers can discover this guide.</p>
<p><strong>Want more Angular insights?</strong> Follow me for weekly deep-dives into Angular best practices, performance optimization, and modern development techniques. I also share exclusive tips in my newsletter—join the community of developers who want to level up their Angular game.</p>
<p><strong>Action Points:</strong></p>
<ol>
<li><p>Try Vitest in a small project first—get comfortable before migrating your main app</p>
</li>
<li><p>Set up CI/CD integration early—automated testing saves teams</p>
</li>
<li><p>Share this with your team—discuss migration timeline and strategy</p>
</li>
<li><p>Bookmark this guide—you'll reference it during migration</p>
</li>
</ol>
<p>What topic should I cover next? Vote in the comments:</p>
<ul>
<li><p>A) Advanced Vitest patterns and custom utilities</p>
</li>
<li><p>B) Angular Signals testing strategies</p>
</li>
<li><p>C) E2E testing with Playwright in Angular 21</p>
</li>
</ul>
<p>Let's keep the conversation going. See you in the comments.</p>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[The Angular Service Scoping Mistake That's Killing Your App Performance (And How to Fix It)]]></title><description><![CDATA[Quick question: How many times have you added providedIn: 'root' to an Angular service without thinking twice?
If you're like most developers (myself included for way too long), the answer is probably "every single time." But here's the thing — that ...]]></description><link>https://hashnode.rajatmalik.dev/the-angular-service-scoping-mistake-thats-killing-your-app-performance-and-how-to-fix-it</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/the-angular-service-scoping-mistake-thats-killing-your-app-performance-and-how-to-fix-it</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 26 Nov 2025 13:30:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763800691287/caac5ffe-cb11-4d36-8e19-ce306eb8c41f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Quick question:</strong> How many times have you added <code>providedIn: 'root'</code> to an Angular service without thinking twice?</p>
<p>If you're like most developers (myself included for way too long), the answer is probably "every single time." But here's the thing — that innocent little line might be quietly bloating your app and creating memory leaks you didn't even know existed.</p>
<p><strong>By the end of this article, you'll know:</strong></p>
<ul>
<li><p>Why root-level services aren't always the best choice</p>
</li>
<li><p>When and how to scope services properly</p>
</li>
<li><p>Practical examples with real code you can use today</p>
</li>
<li><p>How to write unit tests for scoped services</p>
</li>
<li><p>Pro tips that'll make your Angular apps leaner and faster</p>
</li>
</ul>
<p>💬 <strong>Before we dive in, tell me:</strong> Have you ever wondered why your Angular app feels sluggish even with lazy loading? Drop a comment — I bet service scoping is part of the puzzle!</p>
<hr />
<h2 id="heading-the-root-problem-pun-intended">The Root Problem (Pun Intended) 🤔</h2>
<p>Let's start with a scenario that probably sounds familiar. You're building an e-commerce app, and you create a <code>ProductService</code>:</p>
<pre><code class="lang-tsx">@Injectable({
  providedIn: 'root'  // 👈 The automatic choice
})
export class ProductService {
  private products: Product[] = [];

  constructor(private http: HttpClient) {
    // Load all products immediately
    this.loadProducts();
  }

  private loadProducts() {
    this.http.get&lt;Product[]&gt;('/api/products').subscribe(
      products =&gt; this.products = products
    );
  }

  getProducts(): Product[] {
    return this.products;
  }
}
</code></pre>
<p>Looks innocent, right? But here's what's happening under the hood:</p>
<ol>
<li><p><strong>Your service loads ALL products</strong> the moment your app starts</p>
</li>
<li><p><strong>It stays in memory</strong> even when users are on completely unrelated pages</p>
</li>
<li><p><strong>It's creating unnecessary HTTP requests</strong> before users even need the data</p>
</li>
</ol>
<p><strong>Question for you:</strong> How many of your services are doing exactly this? 🤯</p>
<hr />
<h2 id="heading-the-smarter-approach-strategic-service-scoping">The Smarter Approach: Strategic Service Scoping 🎯</h2>
<p>Instead of making everything root-level, let's be intentional about where our services live. Here are the three main scoping strategies:</p>
<h3 id="heading-1-component-level-scoping">1. Component-Level Scoping</h3>
<p>Perfect for services that should have a fresh instance per component:</p>
<pre><code class="lang-tsx">@Injectable()
export class ShoppingCartService {
  private items: CartItem[] = [];

  addItem(item: CartItem) {
    this.items.push(item);
  }

  getItems(): CartItem[] {
    return [...this.items];
  }

  clearCart() {
    this.items = [];
  }
}

// In your component
@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  providers: [ShoppingCartService]  // 👈 Scoped to this component
})
export class ProductListComponent {
  constructor(private cartService: ShoppingCartService) {}
}
</code></pre>
<h3 id="heading-2-module-level-scoping">2. Module-Level Scoping</h3>
<p>Great for services that should be shared within a feature module:</p>
<pre><code class="lang-tsx">@Injectable()
export class UserProfileService {
  private userProfile: UserProfile | null = null;

  constructor(private http: HttpClient) {}

  loadUserProfile(userId: string): Observable&lt;UserProfile&gt; {
    if (!this.userProfile) {
      return this.http.get&lt;UserProfile&gt;(`/api/users/${userId}`).pipe(
        tap(profile =&gt; this.userProfile = profile)
      );
    }
    return of(this.userProfile);
  }
}

// In your feature module
@NgModule({
  providers: [UserProfileService],  // 👈 Scoped to this module
  // ... other module config
})
export class UserModule {}
</code></pre>
<h3 id="heading-3-smart-root-level-services">3. Smart Root-Level Services</h3>
<p>Only use root-level for truly global services:</p>
<pre><code class="lang-tsx">@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  private currentUser$ = new BehaviorSubject&lt;User | null&gt;(null);

  constructor(private http: HttpClient) {
    // Only load auth state - truly global concern
    this.loadStoredAuthState();
  }

  // Methods that are needed app-wide
  getCurrentUser(): Observable&lt;User | null&gt; {
    return this.currentUser$.asObservable();
  }
}
</code></pre>
<p>💡 <strong>Pro tip:</strong> Ask yourself: "Does every single page of my app need this service?" If not, don't make it root-level!</p>
<hr />
<h2 id="heading-real-world-example-e-commerce-product-categories">Real-World Example: E-Commerce Product Categories 🛍️</h2>
<p>Let's build a practical example. Imagine you have different product categories, and each category has its own complex filtering logic:</p>
<pre><code class="lang-tsx">// ❌ Bad: Root-level service loading everything
@Injectable({
  providedIn: 'root'
})
export class BadProductService {
  private electronicsProducts: Product[] = [];
  private clothingProducts: Product[] = [];
  private booksProducts: Product[] = [];

  constructor(private http: HttpClient) {
    // Loading everything immediately - wasteful!
    this.loadAllCategories();
  }

  private loadAllCategories() {
    // This runs even if user only visits electronics
    this.http.get&lt;Product[]&gt;('/api/electronics').subscribe(/*...*/);
    this.http.get&lt;Product[]&gt;('/api/clothing').subscribe(/*...*/);
    this.http.get&lt;Product[]&gt;('/api/books').subscribe(/*...*/);
  }
}
</code></pre>
<pre><code class="lang-tsx">// ✅ Good: Category-specific services
@Injectable()
export class ElectronicsService {
  private products: Product[] = [];
  private filters: ElectronicsFilter = {};

  constructor(private http: HttpClient) {}

  loadProducts(): Observable&lt;Product[]&gt; {
    if (this.products.length === 0) {
      return this.http.get&lt;Product[]&gt;('/api/electronics').pipe(
        tap(products =&gt; this.products = products)
      );
    }
    return of(this.products);
  }

  applyFilters(filters: ElectronicsFilter): Product[] {
    // Category-specific filtering logic
    return this.products.filter(product =&gt; {
      return this.matchesElectronicsFilters(product, filters);
    });
  }

  private matchesElectronicsFilters(product: Product, filters: ElectronicsFilter): boolean {
    // Complex electronics-specific filtering
    return true; // Simplified for demo
  }
}

// Provide it only in the Electronics module
@NgModule({
  providers: [ElectronicsService],
  // ... other config
})
export class ElectronicsModule {}
</code></pre>
<p><strong>Think about it:</strong> Why should your app load clothing filters when the user is browsing electronics? 🤷‍♂️</p>
<hr />
<h2 id="heading-unit-testing-scoped-services">Unit Testing Scoped Services 🧪</h2>
<p>Here's how to properly test your scoped services:</p>
<pre><code class="lang-tsx">describe('ElectronicsService', () =&gt; {
  let service: ElectronicsService;
  let httpMock: jasmine.SpyObj&lt;HttpClient&gt;;

  beforeEach(() =&gt; {
    const httpSpy = jasmine.createSpyObj('HttpClient', ['get']);

    TestBed.configureTestingModule({
      providers: [
        ElectronicsService,
        { provide: HttpClient, useValue: httpSpy }
      ]
    });

    service = TestBed.inject(ElectronicsService);
    httpMock = TestBed.inject(HttpClient) as jasmine.SpyObj&lt;HttpClient&gt;;
  });

  it('should load products only once', fakeAsync(() =&gt; {
    const mockProducts = [
      { id: 1, name: 'iPhone', category: 'electronics' },
      { id: 2, name: 'MacBook', category: 'electronics' }
    ];

    httpMock.get.and.returnValue(of(mockProducts));

    // First call
    service.loadProducts().subscribe();
    tick();

    // Second call
    service.loadProducts().subscribe();
    tick();

    // HTTP should be called only once
    expect(httpMock.get).toHaveBeenCalledTimes(1);
  }));

  it('should apply filters correctly', () =&gt; {
    // Test your filtering logic
    const filters: ElectronicsFilter = { brand: 'Apple', maxPrice: 1000 };
    const results = service.applyFilters(filters);

    expect(results).toBeDefined();
    // Add more specific assertions based on your filter logic
  });
});
</code></pre>
<p><strong>Quick question:</strong> How do you usually test your services? Are you testing the scoping behavior too? 💭</p>
<hr />
<h2 id="heading-pro-tips-thatll-make-you-stand-out">Pro Tips That'll Make You Stand Out 🔥</h2>
<h3 id="heading-1-lazy-service-loading-pattern">1. Lazy Service Loading Pattern</h3>
<pre><code class="lang-tsx">@Injectable()
export class LazyDataService {
  private data$ = new BehaviorSubject&lt;any[]&gt;([]);

  constructor(private http: HttpClient) {}

  getData(): Observable&lt;any[]&gt; {
    if (this.data$.getValue().length === 0) {
      this.http.get&lt;any[]&gt;('/api/data').subscribe(
        data =&gt; this.data$.next(data)
      );
    }
    return this.data$.asObservable();
  }
}
</code></pre>
<h3 id="heading-2-service-cleanup-pattern">2. Service Cleanup Pattern</h3>
<pre><code class="lang-tsx">@Injectable()
export class CleanupService implements OnDestroy {
  private destroy$ = new Subject&lt;void&gt;();

  constructor(private http: HttpClient) {}

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  getData(): Observable&lt;any&gt; {
    return this.http.get('/api/data').pipe(
      takeUntil(this.destroy$)
    );
  }
}
</code></pre>
<h3 id="heading-3-service-factory-pattern">3. Service Factory Pattern</h3>
<pre><code class="lang-tsx">export function createCategoryService(category: string) {
  return new CategoryService(category);
}

@NgModule({
  providers: [
    {
      provide: CategoryService,
      useFactory: createCategoryService,
      deps: ['categoryName']
    }
  ]
})
export class DynamicCategoryModule {}
</code></pre>
<p>👏 <strong>If these patterns are new to you, give this article a clap so others can discover these techniques too!</strong></p>
<hr />
<h2 id="heading-bonus-tips-for-angular-performance">Bonus Tips for Angular Performance 💨</h2>
<ol>
<li><p><strong>Use OnPush Change Detection</strong> with scoped services for better performance</p>
</li>
<li><p><strong>Implement proper service disposal</strong> in your modules</p>
</li>
<li><p><strong>Consider using Angular's tree-shakable providers</strong> for truly optional services</p>
</li>
<li><p><strong>Monitor your bundle size</strong> — scoped services help keep chunks smaller</p>
</li>
</ol>
<p><strong>Your turn:</strong> What's one performance tip you wish you'd known earlier? Share it in the comments! 👇</p>
<hr />
<h2 id="heading-recap-your-service-scoping-cheat-sheet">Recap: Your Service Scoping Cheat Sheet 📋</h2>
<p><strong>✅ Use Root-Level Services For:</strong></p>
<ul>
<li><p>Authentication and authorization</p>
</li>
<li><p>Global configuration</p>
</li>
<li><p>App-wide state management</p>
</li>
<li><p>Core utilities (logging, error handling)</p>
</li>
</ul>
<p><strong>✅ Use Module-Level Services For:</strong></p>
<ul>
<li><p>Feature-specific business logic</p>
</li>
<li><p>Data services for specific domains</p>
</li>
<li><p>Services shared within a feature module</p>
</li>
</ul>
<p><strong>✅ Use Component-Level Services For:</strong></p>
<ul>
<li><p>Temporary state management</p>
</li>
<li><p>Component-specific calculations</p>
</li>
<li><p>Services that need fresh instances</p>
</li>
</ul>
<p><strong>❌ Avoid Root-Level Services For:</strong></p>
<ul>
<li><p>Heavy data loading services</p>
</li>
<li><p>Feature-specific utilities</p>
</li>
<li><p>Services that aren't needed app-wide</p>
</li>
</ul>
<hr />
<h2 id="heading-whats-next">What's Next? 🚀</h2>
<p>Now that you know how to scope your services properly, your Angular apps will be:</p>
<ul>
<li><p><strong>Faster</strong> at startup (no unnecessary service initialization)</p>
</li>
<li><p><strong>Smaller</strong> in memory footprint</p>
</li>
<li><p><strong>More maintainable</strong> with clear service boundaries</p>
</li>
<li><p><strong>Better performing</strong> with lazy loading</p>
</li>
</ul>
<p><strong>Action points for you:</strong></p>
<ol>
<li><p>Audit your current services — how many are unnecessarily root-level?</p>
</li>
<li><p>Refactor one service this week using the patterns above</p>
</li>
<li><p>Add proper unit tests for your scoped services</p>
</li>
<li><p>Monitor your app's performance before and after</p>
</li>
</ol>
<hr />
<h2 id="heading-lets-connect">Let's Connect! 🤝</h2>
<p><strong>💬 What did you think?</strong> Have you been over-using root-level services too? Or do you have a different approach to service scoping? I'd love to hear your experience in the comments!</p>
<p><strong>👏 Found this helpful?</strong> If this article saved you from a performance headache, smash that clap button (or give it 5 claps!) so other developers can find it too.</p>
<p><strong>📬 Want more Angular tips like this?</strong> I share practical Angular insights every week. Follow me here on Medium or connect with me on LinkedIn [@sadhana_dev] for more developer tips that actually work.</p>
<p><strong>🔥 What should I write about next?</strong> Vote in the comments:</p>
<ul>
<li><p>A) Advanced Angular Testing Patterns</p>
</li>
<li><p>B) Angular Performance Optimization Deep Dive</p>
</li>
<li><p>C) Building Scalable Angular Architecture</p>
</li>
</ul>
<p>Let's build better Angular apps together! 🚀</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Angular v21 Has Landed: Are You Ready for the Signal Revolution?]]></title><description><![CDATA[Introduction
Have you ever felt like Angular was carrying too much legacy weight? Like you were constantly fighting with zone.js, or wished your forms could be more reactive and type-safe without all that boilerplate?
Angular v21 just changed the gam...]]></description><link>https://hashnode.rajatmalik.dev/angular-v21-has-landed-are-you-ready-for-the-signal-revolution</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/angular-v21-has-landed-are-you-ready-for-the-signal-revolution</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 25 Nov 2025 13:30:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763799682629/aa197960-1cdd-48da-8cf6-bbb8a582690d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>Have you ever felt like Angular was carrying too much legacy weight? Like you were constantly fighting with zone.js, or wished your forms could be more reactive and type-safe without all that boilerplate?</p>
<p>Angular v21 just changed the game.</p>
<p>After working with Angular for over a decade and building multiple component libraries, I can confidently say this is the most strategic release since standalone components. The Angular team has delivered features that fundamentally reshape how we think about change detection, forms, testing, and accessibility.</p>
<p>In this article, you'll learn:</p>
<ul>
<li><p>How to leverage Signal-based Forms for fully type-safe, reactive form handling</p>
</li>
<li><p>Why zoneless change detection matters and how to migrate</p>
</li>
<li><p>Building accessible UIs with Angular Aria's headless components</p>
</li>
<li><p>Setting up Vitest as your default test runner</p>
</li>
<li><p>Practical migration strategies for existing applications</p>
</li>
</ul>
<p>Let's dive into what makes Angular v21 a milestone release and, more importantly, how you can start using these features today.</p>
<hr />
<h2 id="heading-the-big-picture-why-angular-v21-matters">The Big Picture: Why Angular v21 Matters</h2>
<p>Angular v21 isn't just another incremental update. It represents a fundamental shift in Angular's philosophy:</p>
<p><strong>From zone.js-driven change detection to signals-first reactive primitivesFrom verbose forms API to declarative, type-safe signal formsFrom Karma/Jasmine to Vitest for faster, modern testingFrom building accessibility from scratch to headless, reusable patterns</strong></p>
<p>For library authors and enterprise teams, this means rethinking architectural decisions around state management, change detection, and component design patterns.</p>
<hr />
<h2 id="heading-feature-1-signal-based-forms-experimental">Feature #1: Signal-Based Forms (Experimental)</h2>
<h3 id="heading-whats-different">What's Different?</h3>
<p>The new signal-based forms API lets you define your entire form model using signals, with full type safety and schema-based validation—no ControlValueAccessor boilerplate needed.</p>
<h3 id="heading-traditional-forms-vs-signal-forms">Traditional Forms vs Signal Forms</h3>
<p><strong>Old Approach (Reactive Forms):</strong></p>
<pre><code class="lang-tsx">import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-user-form',
  imports: [ReactiveFormsModule],
  template: `
    &lt;form [formGroup]="userForm" (ngSubmit)="onSubmit()"&gt;
      &lt;input formControlName="email" type="email" /&gt;
      &lt;input formControlName="password" type="password" /&gt;
      &lt;button type="submit"&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  `
})
export class UserFormComponent {
  userForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.userForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(8)]]
    });
  }

  onSubmit() {
    if (this.userForm.valid) {
      console.log(this.userForm.value);
    }
  }
}
</code></pre>
<p><strong>New Approach (Signal Forms):</strong></p>
<pre><code class="lang-tsx">import { Component, signal } from '@angular/core';
import { FormField } from '@angular/forms';

interface UserForm {
  email: string;
  password: string;
}

@Component({
  selector: 'app-user-form',
  imports: [],
  template: `
    &lt;form (submit)="onSubmit()"&gt;
      &lt;input
        [value]="form().email"
        (input)="updateEmail($event)"
        type="email"
      /&gt;
      @if (emailError()) {
        &lt;span class="error"&gt;{{ emailError() }}&lt;/span&gt;
      }

      &lt;input
        [value]="form().password"
        (input)="updatePassword($event)"
        type="password"
      /&gt;
      @if (passwordError()) {
        &lt;span class="error"&gt;{{ passwordError() }}&lt;/span&gt;
      }

      &lt;button type="submit" [disabled]="!isValid()"&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  `,
  styles: [`
    .error { color: red; font-size: 0.875rem; }
  `]
})
export class SignalUserFormComponent {
  form = signal&lt;UserForm&gt;({
    email: '',
    password: ''
  });

  emailError = signal&lt;string | null&gt;(null);
  passwordError = signal&lt;string | null&gt;(null);

  isValid = () =&gt; !this.emailError() &amp;&amp; !this.passwordError() &amp;&amp;
                   this.form().email &amp;&amp; this.form().password;

  updateEmail(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.form.update(f =&gt; ({ ...f, email: value }));

    // Validation
    if (!value) {
      this.emailError.set('Email is required');
    } else if (!this.isValidEmail(value)) {
      this.emailError.set('Invalid email format');
    } else {
      this.emailError.set(null);
    }
  }

  updatePassword(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.form.update(f =&gt; ({ ...f, password: value }));

    // Validation
    if (!value) {
      this.passwordError.set('Password is required');
    } else if (value.length &lt; 8) {
      this.passwordError.set('Password must be at least 8 characters');
    } else {
      this.passwordError.set(null);
    }
  }

  isValidEmail(email: string): boolean {
    return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);
  }

  onSubmit() {
    if (this.isValid()) {
      console.log('Form submitted:', this.form());
    }
  }
}
</code></pre>
<h3 id="heading-key-benefits">Key Benefits</h3>
<p><strong>Full Type Safety:</strong> TypeScript knows your form structure <strong>Reactive by Default:</strong> Every change automatically triggers updates <strong>No Boilerplate:</strong> Direct signal manipulation, no FormControl wrapper needed <strong>Composable Validation:</strong> Write validators as simple functions</p>
<p>Want to see how this scales with nested forms or arrays? Let me know in the comments what form patterns you'd like to explore.</p>
<h3 id="heading-unit-testing-signal-forms">Unit Testing Signal Forms</h3>
<pre><code class="lang-tsx">import { TestBed } from '@angular/core/testing';
import { SignalUserFormComponent } from './signal-user-form.component';

describe('SignalUserFormComponent', () =&gt; {
  let component: SignalUserFormComponent;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [SignalUserFormComponent]
    });

    const fixture = TestBed.createComponent(SignalUserFormComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create the component', () =&gt; {
    expect(component).toBeTruthy();
  });

  it('should initialize with empty form', () =&gt; {
    expect(component.form()).toEqual({
      email: '',
      password: ''
    });
  });

  it('should update email and validate', () =&gt; {
    const input = { target: { value: 'test@example.com' } } as any;
    component.updateEmail(input);

    expect(component.form().email).toBe('test@example.com');
    expect(component.emailError()).toBeNull();
  });

  it('should show error for invalid email', () =&gt; {
    const input = { target: { value: 'invalid-email' } } as any;
    component.updateEmail(input);

    expect(component.emailError()).toBe('Invalid email format');
  });

  it('should validate password length', () =&gt; {
    const input = { target: { value: 'short' } } as any;
    component.updatePassword(input);

    expect(component.passwordError()).toBe('Password must be at least 8 characters');
  });

  it('should mark form as valid when all fields are correct', () =&gt; {
    component.updateEmail({ target: { value: 'test@example.com' } } as any);
    component.updatePassword({ target: { value: 'password123' } } as any);

    expect(component.isValid()).toBe(true);
  });
});
</code></pre>
<hr />
<h2 id="heading-feature-2-zoneless-change-detection">Feature #2: Zoneless Change Detection</h2>
<h3 id="heading-what-changed">What Changed?</h3>
<p>New Angular v21 apps run without zone.js by default. This means:</p>
<ul>
<li><p>Smaller bundle sizes (no zone.js overhead)</p>
</li>
<li><p>Better performance (explicit change detection)</p>
</li>
<li><p>More control over when updates happen</p>
</li>
<li><p>Cleaner async handling</p>
</li>
</ul>
<h3 id="heading-how-to-build-zoneless-components">How to Build Zoneless Components</h3>
<pre><code class="lang-tsx">import { Component, signal, effect } from '@angular/core';
import { HttpClient } from '@angular/common/http';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-list',
  imports: [],
  template: `
    &lt;div&gt;
      &lt;h2&gt;Users&lt;/h2&gt;

      @if (loading()) {
        &lt;p&gt;Loading users...&lt;/p&gt;
      }

      @if (error()) {
        &lt;p class="error"&gt;{{ error() }}&lt;/p&gt;
      }

      @if (users().length &gt; 0) {
        &lt;ul&gt;
          @for (user of users(); track user.id) {
            &lt;li&gt;
              &lt;strong&gt;{{ user.name }}&lt;/strong&gt; - {{ user.email }}
            &lt;/li&gt;
          }
        &lt;/ul&gt;
      }

      &lt;button (click)="loadUsers()"&gt;Refresh&lt;/button&gt;
    &lt;/div&gt;
  `,
  styles: [`
    .error { color: red; }
    ul { list-style: none; padding: 0; }
    li { padding: 0.5rem; border-bottom: 1px solid #eee; }
  `]
})
export class UserListComponent {
  users = signal&lt;User[]&gt;([]);
  loading = signal(false);
  error = signal&lt;string | null&gt;(null);

  constructor(private http: HttpClient) {
    this.loadUsers();
  }

  loadUsers() {
    this.loading.set(true);
    this.error.set(null);

    this.http.get&lt;User[]&gt;('&lt;https://jsonplaceholder.typicode.com/users&gt;')
      .subscribe({
        next: (data) =&gt; {
          this.users.set(data);
          this.loading.set(false);
        },
        error: (err) =&gt; {
          this.error.set('Failed to load users');
          this.loading.set(false);
        }
      });
  }
}
</code></pre>
<h3 id="heading-notice-whats-different">Notice What's Different?</h3>
<p>No more NgZone.run() calls No manual change detection triggers Signals handle reactivity automatically Control flow syntax (@if, @for) works seamlessly</p>
<h3 id="heading-unit-testing-zoneless-components">Unit Testing Zoneless Components</h3>
<pre><code class="lang-tsx">import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { UserListComponent } from './user-list.component';

describe('UserListComponent (Zoneless)', () =&gt; {
  let component: UserListComponent;
  let httpMock: HttpTestingController;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [UserListComponent],
      providers: [
        provideHttpClient(),
        provideHttpClientTesting()
      ]
    });

    httpMock = TestBed.inject(HttpTestingController);
    const fixture = TestBed.createComponent(UserListComponent);
    component = fixture.componentInstance;
  });

  afterEach(() =&gt; {
    httpMock.verify();
  });

  it('should load users on initialization', () =&gt; {
    const mockUsers = [
      { id: 1, name: 'John Doe', email: 'john@example.com' }
    ];

    const req = httpMock.expectOne('&lt;https://jsonplaceholder.typicode.com/users&gt;');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);

    expect(component.users()).toEqual(mockUsers);
    expect(component.loading()).toBe(false);
  });

  it('should handle errors gracefully', () =&gt; {
    const req = httpMock.expectOne('&lt;https://jsonplaceholder.typicode.com/users&gt;');
    req.error(new ProgressEvent('error'));

    expect(component.error()).toBe('Failed to load users');
    expect(component.loading()).toBe(false);
  });

  it('should refresh users on button click', () =&gt; {
    // Clear initial request
    httpMock.expectOne('&lt;https://jsonplaceholder.typicode.com/users&gt;').flush([]);

    component.loadUsers();

    const req = httpMock.expectOne('&lt;https://jsonplaceholder.typicode.com/users&gt;');
    expect(component.loading()).toBe(true);
    req.flush([{ id: 2, name: 'Jane', email: 'jane@example.com' }]);

    expect(component.loading()).toBe(false);
  });
});
</code></pre>
<hr />
<h2 id="heading-feature-3-angular-aria-headless-accessibility">Feature #3: Angular Aria - Headless Accessibility</h2>
<p>Angular Aria provides fully accessible, headless UI components. You bring the styling, Angular brings the accessibility logic.</p>
<h3 id="heading-building-an-accessible-combobox">Building an Accessible Combobox</h3>
<pre><code class="lang-tsx">import { Component, signal } from '@angular/core';

interface Option {
  id: string;
  label: string;
}

@Component({
  selector: 'app-search-combobox',
  imports: [],
  template: `
    &lt;div class="combobox-container"&gt;
      &lt;label for="search-input"&gt;Search frameworks:&lt;/label&gt;

      &lt;input
        id="search-input"
        type="text"
        [value]="searchTerm()"
        (input)="onSearch($event)"
        (focus)="isOpen.set(true)"
        role="combobox"
        aria-expanded="{{ isOpen() }}"
        aria-controls="options-list"
        aria-autocomplete="list"
      /&gt;

      @if (isOpen() &amp;&amp; filteredOptions().length &gt; 0) {
        &lt;ul
          id="options-list"
          role="listbox"
          class="options-list"
        &gt;
          @for (option of filteredOptions(); track option.id) {
            &lt;li
              role="option"
              [attr.aria-selected]="selectedId() === option.id"
              [class.selected]="selectedId() === option.id"
              (click)="selectOption(option)"
            &gt;
              {{ option.label }}
            &lt;/li&gt;
          }
        &lt;/ul&gt;
      }

      @if (selectedOption()) {
        &lt;p&gt;Selected: &lt;strong&gt;{{ selectedOption()!.label }}&lt;/strong&gt;&lt;/p&gt;
      }
    &lt;/div&gt;
  `,
  styles: [`
    .combobox-container {
      position: relative;
      width: 300px;
    }

    input {
      width: 100%;
      padding: 0.5rem;
      border: 1px solid #ccc;
      border-radius: 4px;
    }

    .options-list {
      position: absolute;
      width: 100%;
      max-height: 200px;
      overflow-y: auto;
      background: white;
      border: 1px solid #ccc;
      border-radius: 4px;
      margin: 0;
      padding: 0;
      list-style: none;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }

    .options-list li {
      padding: 0.5rem;
      cursor: pointer;
    }

    .options-list li:hover {
      background: #f0f0f0;
    }

    .options-list li.selected {
      background: #007bff;
      color: white;
    }
  `]
})
export class SearchComboboxComponent {
  options = signal&lt;Option[]&gt;([
    { id: '1', label: 'Angular' },
    { id: '2', label: 'React' },
    { id: '3', label: 'Vue' },
    { id: '4', label: 'Svelte' },
    { id: '5', label: 'Solid' }
  ]);

  searchTerm = signal('');
  isOpen = signal(false);
  selectedId = signal&lt;string | null&gt;(null);

  filteredOptions = signal&lt;Option[]&gt;([]);
  selectedOption = signal&lt;Option | null&gt;(null);

  constructor() {
    this.filteredOptions.set(this.options());
  }

  onSearch(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.searchTerm.set(value);

    const filtered = this.options().filter(opt =&gt;
      opt.label.toLowerCase().includes(value.toLowerCase())
    );

    this.filteredOptions.set(filtered);
    this.isOpen.set(true);
  }

  selectOption(option: Option) {
    this.selectedId.set(option.id);
    this.selectedOption.set(option);
    this.searchTerm.set(option.label);
    this.isOpen.set(false);
  }
}
</code></pre>
<h3 id="heading-unit-testing-accessible-components">Unit Testing Accessible Components</h3>
<pre><code class="lang-tsx">import { TestBed } from '@angular/core/testing';
import { SearchComboboxComponent } from './search-combobox.component';

describe('SearchComboboxComponent', () =&gt; {
  let component: SearchComboboxComponent;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [SearchComboboxComponent]
    });

    const fixture = TestBed.createComponent(SearchComboboxComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should filter options based on search term', () =&gt; {
    const event = { target: { value: 'ang' } } as any;
    component.onSearch(event);

    expect(component.filteredOptions().length).toBe(1);
    expect(component.filteredOptions()[0].label).toBe('Angular');
  });

  it('should select option when clicked', () =&gt; {
    const option = { id: '1', label: 'Angular' };
    component.selectOption(option);

    expect(component.selectedOption()).toEqual(option);
    expect(component.selectedId()).toBe('1');
    expect(component.isOpen()).toBe(false);
  });

  it('should open dropdown on focus', () =&gt; {
    component.isOpen.set(false);
    const event = { target: { value: '' } } as any;
    component.onSearch(event);

    expect(component.isOpen()).toBe(true);
  });
});
</code></pre>
<hr />
<h2 id="heading-feature-4-vitest-as-default-test-runner">Feature #4: Vitest as Default Test Runner</h2>
<p>Vitest is now the default for new Angular projects. Here's why it matters:</p>
<p>Faster test execution Better ESM support Hot module reloading for tests Compatible with Vite's ecosystem</p>
<h3 id="heading-setting-up-vitest-in-existing-projects">Setting Up Vitest in Existing Projects</h3>
<pre><code class="lang-bash">npm install -D vitest @vitest/ui @angular/build
</code></pre>
<p><strong>vitest.config.ts:</strong></p>
<pre><code class="lang-tsx">import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['src/test-setup.ts'],
    include: ['src/**/*.{test,spec}.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
    },
  },
});
</code></pre>
<hr />
<h2 id="heading-migration-strategies-for-existing-apps">Migration Strategies for Existing Apps</h2>
<h3 id="heading-step-1-audit-your-codebase">Step 1: Audit Your Codebase</h3>
<p>Run this command to see what needs updating:</p>
<pre><code class="lang-bash">ng update @angular/core@21 @angular/cli@21
</code></pre>
<h3 id="heading-step-2-enable-zoneless-gradually">Step 2: Enable Zoneless Gradually</h3>
<pre><code class="lang-tsx">import { bootstrapApplication } from '@angular/platform-browser';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});
</code></pre>
<h3 id="heading-step-3-migrate-forms-selectively">Step 3: Migrate Forms Selectively</h3>
<p>Don't migrate all forms at once. Start with new features using Signal Forms, then gradually refactor existing forms.</p>
<h3 id="heading-step-4-update-test-configuration">Step 4: Update Test Configuration</h3>
<p>Switch to Vitest for new test files while keeping existing Karma tests running.</p>
<hr />
<h2 id="heading-bonus-tips">Bonus Tips</h2>
<p><strong>Tip 1:</strong> Use the Angular CLI's built-in migration schematics to automate control flow syntax updates.</p>
<p><strong>Tip 2:</strong> Leverage the new CLDR updates (v47) for better internationalization support—especially if you're building multi-language apps.</p>
<p><strong>Tip 3:</strong> Explore the Material Design utility classes for design tokens. They make theming much easier.</p>
<p><strong>Tip 4:</strong> Check out the new Angular + AI documentation section on angular.dev if you're building AI-powered features.</p>
<p><strong>Tip 5:</strong> The regex support in templates is a game-changer for validation without custom validators.</p>
<hr />
<h2 id="heading-quick-recap">Quick Recap</h2>
<p>Angular v21 is a strategic inflection point for the framework:</p>
<p>Signal-based Forms bring type safety and reactivity to form handling Zoneless architecture means better performance and smaller bundles Angular Aria makes accessibility a first-class citizen Vitest provides a modern, faster testing experience The ecosystem is maturing toward signals-first, reactive patterns</p>
<p>For new projects, adopt v21 immediately. For existing apps, plan a phased migration focusing on high-impact areas first.</p>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
<hr />
<p>Reference: <a target="_blank" href="https://blog.angular.dev/announcing-angular-v21-57946c34f14b">Official Angular v21 Announcement</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Use SVG Icons with Angular Material's mat-icon Component (The Right Way)]]></title><description><![CDATA[Ever spent hours trying to get that perfect icon to show up in your Angular app, only to end up with a frustrating square or a blurry mess?
I've been there too. Font icons seemed like the obvious choice until I realized they're actually holding us ba...]]></description><link>https://hashnode.rajatmalik.dev/how-to-use-svg-icons-with-angular-materials-mat-icon-component-the-right-way</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/how-to-use-svg-icons-with-angular-materials-mat-icon-component-the-right-way</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 19 Nov 2025 13:30:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763183816292/fca27fba-7884-4f49-ad28-4c80d6b10d0d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever spent hours trying to get that perfect icon to show up in your Angular app, only to end up with a frustrating square or a blurry mess?</p>
<p>I've been there too. Font icons seemed like the obvious choice until I realized they're actually holding us back. What if I told you there's a cleaner, more scalable way to handle icons in Angular Material that gives you pixel-perfect results every time?</p>
<p><strong>Here's what you'll learn by the end of this article:</strong></p>
<ul>
<li><p>Why SVG icons are superior to font icons (and when you should make the switch)</p>
</li>
<li><p>How to properly configure Angular Material's <code>mat-icon</code> for SVG icons</p>
</li>
<li><p>Step-by-step setup with real code examples</p>
</li>
<li><p>Pro tips for organizing and optimizing your icon workflow</p>
</li>
<li><p>Common pitfalls and how to avoid them</p>
</li>
</ul>
<p><strong>Before we dive in—what's your biggest frustration with icons in Angular?</strong> Drop a comment below; I genuinely want to know what's been tripping you up!</p>
<hr />
<h2 id="heading-why-svg-icons-beat-font-icons-every-time">Why SVG Icons Beat Font Icons Every Time</h2>
<p>Let's be honest—font icons were a clever hack for their time, but they come with baggage:</p>
<ul>
<li><p><strong>Accessibility issues</strong> (screen readers struggle with them)</p>
</li>
<li><p><strong>Loading dependencies</strong> (entire font files for a few icons)</p>
</li>
<li><p><strong>Styling limitations</strong> (you're stuck with single colors)</p>
</li>
<li><p><strong>Rendering inconsistencies</strong> across different browsers</p>
</li>
</ul>
<p>SVG icons, on the other hand, are:</p>
<p><strong>Scalable</strong> without quality loss</p>
<p><strong>Accessible</strong> with proper ARIA labels</p>
<p><strong>Lightweight</strong> (only load what you need)</p>
<p><strong>Customizable</strong> (colors, gradients, animations)</p>
<p><strong>Crisp</strong> on all screen densities</p>
<hr />
<h2 id="heading-setting-up-your-angular-material-project">Setting Up Your Angular Material Project</h2>
<p>First things first—let's make sure you have Angular Material properly installed. If you're starting fresh:</p>
<pre><code class="lang-bash">ng add @angular/material
</code></pre>
<p>Choose your theme, enable animations, and include the Angular Material typography styles when prompted.</p>
<p><strong>Already have Angular Material?</strong> Perfect! Let's jump into the icon setup.</p>
<hr />
<h2 id="heading-step-1-configure-the-maticonmodule">Step 1: Configure the MatIconModule</h2>
<p>In your <code>app.module.ts</code> (or wherever you're importing Angular Material modules):</p>
<pre><code class="lang-tsx">import { NgModule } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    // ... other imports
    MatIconModule,
    HttpClientModule, // Required for loading SVG icons
  ],
  // ... rest of your module
})
export class AppModule { }
</code></pre>
<p><strong>Why do we need</strong> <code>HttpClientModule</code>? Angular Material fetches SVG icons via HTTP requests, so this is essential for the setup to work.</p>
<hr />
<h2 id="heading-step-2-register-your-svg-icons">Step 2: Register Your SVG Icons</h2>
<p>Here's where the magic happens. In your component or service, you'll register your SVG icons with Angular Material's <code>MatIconRegistry</code>:</p>
<pre><code class="lang-tsx">import { Component, OnInit } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {

  constructor(
    private matIconRegistry: MatIconRegistry,
    private domSanitizer: DomSanitizer
  ) {}

  ngOnInit() {
    // Register a single SVG icon
    this.matIconRegistry.addSvgIcon(
      'custom-user',
      this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/user.svg')
    );

    // Register an entire icon set
    this.matIconRegistry.addSvgIconSet(
      this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/icon-set.svg')
    );
  }
}
</code></pre>
<p><strong>Hold up—what's with the</strong> <code>DomSanitizer</code>? Angular's security model blocks external resources by default. The <code>bypassSecurityTrustResourceUrl</code> method tells Angular that your SVG files are safe to load.</p>
<hr />
<h2 id="heading-step-3-use-your-svg-icons-in-templates">Step 3: Use Your SVG Icons in Templates</h2>
<p>Now comes the satisfying part—actually using your icons:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- Using a registered custom icon --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span> <span class="hljs-attr">svgIcon</span>=<span class="hljs-string">"custom-user"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Using Material Design icons (if you have the icon font loaded) --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>&gt;</span>home<span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Styling your icons --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span> <span class="hljs-attr">svgIcon</span>=<span class="hljs-string">"custom-user"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"large-icon"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
</code></pre>
<p>And in your CSS:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.large-icon</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">48px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">48px</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">48px</span>;
}

<span class="hljs-comment">// Custom colors</span>
mat-<span class="hljs-attribute">icon</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#3f51b5</span>;
}

<span class="hljs-comment">// Hover effects</span>
mat-<span class="hljs-attribute">icon</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#303f9f</span>;
  <span class="hljs-attribute">transform</span>: scale(<span class="hljs-number">1.1</span>);
  <span class="hljs-attribute">transition</span>: all <span class="hljs-number">0.2s</span> ease;
}
</code></pre>
<p><strong>Quick question for you:</strong> Are you currently using font icons in your Angular project? What's holding you back from switching to SVG?</p>
<hr />
<h2 id="heading-pro-tip-create-an-icon-service">Pro Tip: Create an Icon Service</h2>
<p>For larger projects, I recommend creating a dedicated service to manage your icons. This keeps your components clean and your icon registration centralized:</p>
<pre><code class="lang-tsx">import { Injectable } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root'
})
export class IconService {

  constructor(
    private matIconRegistry: MatIconRegistry,
    private domSanitizer: DomSanitizer
  ) {
    this.registerIcons();
  }

  private registerIcons(): void {
    const icons = [
      'user',
      'settings',
      'dashboard',
      'notifications',
      'logout'
    ];

    icons.forEach(icon =&gt; {
      this.matIconRegistry.addSvgIcon(
        icon,
        this.domSanitizer.bypassSecurityTrustResourceUrl(`assets/icons/${icon}.svg`)
      );
    });
  }
}
</code></pre>
<p>Then inject this service in your <code>AppComponent</code> constructor:</p>
<pre><code class="lang-tsx">export class AppComponent {
  constructor(private iconService: IconService) {
    // Icons are automatically registered when the service is injected
  }
}
</code></pre>
<hr />
<h2 id="heading-step-4-organize-your-svg-files">Step 4: Organize Your SVG Files</h2>
<p>Here's how I structure my icon assets:</p>
<pre><code class="lang-typescript">src/
  assets/
    icons/
      ui/
        user.svg
        settings.svg
        dashboard.svg
      social/
        facebook.svg
        twitter.svg
        linkedin.svg
      actions/
        save.svg
        <span class="hljs-keyword">delete</span>.svg
        edit.svg
</code></pre>
<p>This organization makes it easy to:</p>
<ul>
<li><p>Find icons quickly</p>
</li>
<li><p>Maintain consistent naming</p>
</li>
<li><p>Scale your icon library</p>
</li>
<li><p>Implement icon namespacing if needed</p>
</li>
</ul>
<hr />
<h2 id="heading-common-pitfalls-and-how-to-avoid-them">Common Pitfalls (And How to Avoid Them)</h2>
<p><strong>1. Forgetting the HttpClientModule</strong></p>
<pre><code class="lang-tsx">// This won't work without HttpClientModule
this.matIconRegistry.addSvgIcon(...)

// Import HttpClientModule in your module
imports: [HttpClientModule, MatIconModule]
</code></pre>
<p><strong>2. SVG viewBox Issues</strong> Make sure your SVG files have proper viewBox attributes:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Good SVG structure --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"&lt;http://www.w3.org/2000/svg&gt;"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- icon content --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
</code></pre>
<p><strong>3. Icon Not Showing Up</strong> Check your browser's network tab—you might see 404 errors for missing SVG files.</p>
<p><strong>4. Security Errors</strong> Always use <code>DomSanitizer.bypassSecurityTrustResourceUrl()</code> when registering icons.</p>
<hr />
<h2 id="heading-bonus-icon-sets-for-multiple-namespaces">Bonus: Icon Sets for Multiple Namespaces</h2>
<p>Want to organize icons by category? Use icon sets with namespaces:</p>
<pre><code class="lang-tsx">// Register namespaced icon sets
this.matIconRegistry.addSvgIconSetInNamespace(
  'ui',
  this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/ui-icons.svg')
);

this.matIconRegistry.addSvgIconSetInNamespace(
  'social',
  this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/social-icons.svg')
);
</code></pre>
<p>Then use them like this:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span> <span class="hljs-attr">svgIcon</span>=<span class="hljs-string">"ui:user"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span> <span class="hljs-attr">svgIcon</span>=<span class="hljs-string">"social:facebook"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
</code></pre>
<hr />
<h2 id="heading-performance-optimization-tips">Performance Optimization Tips</h2>
<p><strong>1. Lazy Load Icons</strong> Only register icons when they're needed:</p>
<pre><code class="lang-tsx">async loadDashboardIcons() {
  const icons = await import('./dashboard-icons');
  icons.registerDashboardIcons(this.matIconRegistry, this.domSanitizer);
}
</code></pre>
<p><strong>2. Use Angular's Built-in Icon Library</strong> For common icons, consider using Angular Material's built-in icons:</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- No registration needed --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>&gt;</span>home<span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>&gt;</span>settings<span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>&gt;</span>person<span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
</code></pre>
<p><strong>3. Optimize Your SVG Files</strong> Use tools like <a target="_blank" href="https://github.com/svg/svgo">SVGO</a> to compress your SVG files:</p>
<pre><code class="lang-bash">npm install -g svgo
svgo assets/icons/*.svg
</code></pre>
<hr />
<h2 id="heading-real-world-example-building-a-navigation-menu">Real-World Example: Building a Navigation Menu</h2>
<p>Let's put it all together with a practical example:</p>
<pre><code class="lang-tsx">// navigation.component.ts
export class NavigationComponent implements OnInit {
  menuItems = [
    { icon: 'dashboard', label: 'Dashboard', route: '/dashboard' },
    { icon: 'custom-user', label: 'Profile', route: '/profile' },
    { icon: 'settings', label: 'Settings', route: '/settings' },
    { icon: 'logout', label: 'Logout', route: '/logout' }
  ];

  constructor(
    private matIconRegistry: MatIconRegistry,
    private domSanitizer: DomSanitizer
  ) {}

  ngOnInit() {
    this.registerCustomIcons();
  }

  private registerCustomIcons() {
    this.matIconRegistry.addSvgIcon(
      'custom-user',
      this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/user.svg')
    );
  }
}
</code></pre>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!-- navigation.component.html --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"sidebar"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-item"</span> *<span class="hljs-attr">ngFor</span>=<span class="hljs-string">"let item of menuItems"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">mat-icon</span>
      [<span class="hljs-attr">svgIcon</span>]=<span class="hljs-string">"item.icon.startsWith('custom-') ? item.icon : null"</span>
      <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-icon"</span>&gt;</span>
      {{ item.icon.startsWith('custom-') ? '' : item.icon }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">mat-icon</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-label"</span>&gt;</span>{{ item.label }}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
</code></pre>
<pre><code class="lang-scss"><span class="hljs-comment">/* navigation.component.scss */</span>
<span class="hljs-selector-class">.sidebar</span> {
  <span class="hljs-selector-class">.nav-item</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span> <span class="hljs-number">16px</span>;
    <span class="hljs-attribute">cursor</span>: pointer;
    <span class="hljs-attribute">transition</span>: background-color <span class="hljs-number">0.2s</span> ease;

    &amp;<span class="hljs-selector-pseudo">:hover</span> {
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
    }

    <span class="hljs-selector-class">.nav-icon</span> {
      <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">12px</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    }

    <span class="hljs-selector-class">.nav-label</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>;
    }
  }
}
</code></pre>
<p><strong>Try this yourself:</strong> Can you modify the example above to add hover animations to the icons? Share your solution in the comments!</p>
<hr />
<h2 id="heading-whats-next">What's Next?</h2>
<p>You now have everything you need to implement SVG icons in your Angular Material projects. But don't stop here—consider exploring:</p>
<ul>
<li><p><strong>Icon animation libraries</strong> like LottieFiles for complex animations</p>
</li>
<li><p><strong>Icon generation tools</strong> that create optimized SVG sprites</p>
</li>
<li><p><strong>Design system integration</strong> for consistent icon usage across teams</p>
</li>
</ul>
<hr />
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<p><strong>SVG icons are superior</strong> to font icons for modern web applications</p>
<p><strong>Angular Material's mat-icon</strong> makes SVG integration straightforward</p>
<p><strong>Proper organization</strong> and service-based architecture scales well</p>
<p><strong>Performance optimization</strong> keeps your app fast and responsive</p>
<p><strong>Security considerations</strong> are handled by Angular's DomSanitizer</p>
<hr />
<h2 id="heading-lets-keep-the-conversation-going">Let's Keep the Conversation Going!</h2>
<p><strong>I want to hear from you:</strong></p>
<p><strong>What did you learn?</strong> Have you tried implementing SVG icons in your Angular project yet? What challenges did you face?</p>
<p><strong>Drop a comment below—I read every single one and often turn great questions into future articles!</strong></p>
<hr />
<p>🔥 <strong>Found this helpful?</strong> 👏 <strong>Hit that clap button</strong> (or give it 5 claps!) so other developers can discover this guide too.</p>
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Mock Standalone Angular Services and RxJS Timers in Jest (Without Memory Leaks) With Best Practises Of Jest, Angular]]></title><description><![CDATA[🧠 Introduction

Have you ever struggled to mock services, constants, timers, or injected dependencies in Angular—especially after switching to standalone components?
You're not alone.

With Angular moving towards standalone APIs and modular design, ...]]></description><link>https://hashnode.rajatmalik.dev/how-to-mock-standalone-angular-services-and-rxjs-timers-in-jest-without-memory-leaks-with-best-practises-of-jest-angular</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/how-to-mock-standalone-angular-services-and-rxjs-timers-in-jest-without-memory-leaks-with-best-practises-of-jest-angular</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 18 Nov 2025 13:30:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763182086192/d6a7d4c8-9023-4519-b8c3-8cb201e73d94.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">🧠 Introduction</h2>
<blockquote>
<p>Have you ever struggled to mock services, constants, timers, or injected dependencies in Angular—especially after switching to standalone components?</p>
<p>You're not alone.</p>
</blockquote>
<p>With Angular moving towards <strong>standalone APIs</strong> and <strong>modular design</strong>, our testing practices must evolve too. If you're still using <code>TestBed.configureTestingModule()</code> for everything—you might be doing more than you need.</p>
<p>In this guide, you'll learn <strong>exactly how to mock Angular services, RxJS-based timers, external libraries, constants, and directives in Jest</strong>, using <strong>modern Angular testing practices</strong> that are <strong>clean, scalable, and memory-safe.</strong></p>
<hr />
<h2 id="heading-what-youll-learn">🎯 What You’ll Learn</h2>
<ul>
<li><p>How to test <strong>standalone Angular services</strong> with Jest</p>
</li>
<li><p>Mocking <strong>HTTP requests</strong> using <code>HttpClientTestingModule</code> with standalone setup</p>
</li>
<li><p>Handling <strong>dependency injection</strong> with external libraries, constants, pipes, or directives</p>
</li>
<li><p>Simulating <strong>timers</strong> and RxJS <code>interval</code>, <code>timer</code>, or <code>setTimeout</code> with <code>jest.useFakeTimers()</code></p>
</li>
<li><p>Making Jest tests <strong>memory-efficient</strong> (why and how)</p>
</li>
<li><p>Practical <strong>code demos</strong> for each use case</p>
</li>
<li><p>Jest best practice</p>
</li>
<li><p>Angular best practice</p>
</li>
</ul>
<hr />
<h2 id="heading-example-standalone-angular-service">🔧 Example: Standalone Angular Service</h2>
<h3 id="heading-userservicets">📦 <code>user.service.ts</code></h3>
<pre><code class="lang-tsx">
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { API_URL } from './tokens'; // a custom InjectionToken

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private baseUrl = inject(API_URL); // Using custom token

  getUser(id: number): Observable&lt;any&gt; {
    return this.http.get(`${this.baseUrl}/users/${id}`);
  }

  getUserWithDelay(id: number): Observable&lt;any&gt; {
    return timer(1000).pipe(
      map(() =&gt; ({ id, name: 'Delayed User' }))
    );
  }
}
</code></pre>
<hr />
<h2 id="heading-unit-test-using-standalone-jest-setup">✅ Unit Test Using Standalone Jest Setup</h2>
<h3 id="heading-userservicespects">🔬 <code>user.service.spec.ts</code></h3>
<pre><code class="lang-tsx">
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { API_URL } from './tokens';
import { jest } from '@jest/globals';

describe('UserService (Standalone + Jest)', () =&gt; {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        UserService,
        { provide: API_URL, useValue: '&lt;https://mock.api&gt;' }
      ]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() =&gt; {
    httpMock.verify(); // ✅ Ensures no memory leaks from open HTTP calls
    jest.clearAllTimers(); // ✅ Reset timers between tests
  });

  it('should fetch user by ID', () =&gt; {
    const mockUser = { id: 1, name: 'Jane' };

    service.getUser(1).subscribe(user =&gt; {
      expect(user).toEqual(mockUser);
    });

    const req = httpMock.expectOne('&lt;https://mock.api/users/1&gt;');
    expect(req.request.method).toBe('GET');
    req.flush(mockUser);
  });

  it('should return delayed user with fake timer', () =&gt; {
    jest.useFakeTimers(); // ✅ Prevent real timer execution
    let result: any;

    service.getUserWithDelay(2).subscribe(user =&gt; (result = user));

    jest.advanceTimersByTime(1000); // Simulate delay
    expect(result).toEqual({ id: 2, name: 'Delayed User' });
  });
});
</code></pre>
<h2 id="heading-section-spying-and-mocking-with-jestfn">🧠 Section: Spying and Mocking With <code>jest.fn()</code></h2>
<p>How to:</p>
<ul>
<li><p>Replace service methods</p>
</li>
<li><p>Simulate return values or throw errors</p>
</li>
<li><p>Combine <code>jest.spyOn()</code> with dependency injection</p>
</li>
</ul>
<pre><code class="lang-tsx">
const mockUserService = {
  getUser: jest.fn().mockReturnValue(of({ id: 1, name: 'Mock User' })),
};
</code></pre>
<hr />
<h2 id="heading-section-error-handling-amp-retry-tests">🛠️ Section: Error Handling &amp; Retry Tests</h2>
<p>Demo:</p>
<ul>
<li><p>Simulating 500 errors</p>
</li>
<li><p>Handling retries with <code>retry()</code> operator</p>
</li>
<li><p>Verifying failure UI states</p>
</li>
</ul>
<hr />
<h2 id="heading-bonus-shared-mocks-amp-dry-test-setup">📦 Bonus: Shared Mocks &amp; DRY Test Setup</h2>
<p>How to:</p>
<ul>
<li><p>Create reusable <code>mock-user.service.ts</code></p>
</li>
<li><p>Abstract mocks in <code>__mocks__</code> folder for auto-mocking</p>
</li>
<li><p>Mock with dynamic data using factory functions</p>
</li>
</ul>
<hr />
<h2 id="heading-advanced-use-case-injecting-pipes-and-directives-in-angular-services">🚀 <strong>Advanced Use Case: Injecting Pipes and Directives in Angular Services</strong></h2>
<h3 id="heading-why-would-a-service-inject-a-pipe-or-directive">❓ Why would a service inject a pipe or directive?</h3>
<ul>
<li><p>You may be using <strong>custom pipes</strong> for transformation logic you want to reuse (e.g., formatting currency, date, slugs).</p>
</li>
<li><p>You might inject a <strong>directive</strong> that provides some shared behavior or config via <code>@Directive({ providers: [...] })</code>.</p>
</li>
</ul>
<p>👉 These scenarios are uncommon but powerful—especially in <strong>design systems</strong>, <strong>custom form libraries</strong>, or <strong>rendering engines</strong>.</p>
<h2 id="heading-demo-use-case-injecting-a-pipe-and-a-directive-in-a-service">🎯 Demo Use Case: Injecting a Pipe and a Directive in a Service</h2>
<h3 id="heading-slugifypipets">🧩 <code>slugify.pipe.ts</code></h3>
<pre><code class="lang-tsx">
import { Pipe, PipeTransform, inject } from '@angular/core';

@Pipe({ name: 'slugify', standalone: true })
export class SlugifyPipe implements PipeTransform {
  transform(value: string): string {
    return value.toLowerCase().replace(/\\s+/g, '-');
  }
}
</code></pre>
<h3 id="heading-feature-toggledirectivets">📍 <code>feature-toggle.directive.ts</code></h3>
<pre><code class="lang-tsx">
import { Directive, inject } from '@angular/core';

@Directive({
  selector: '[appFeatureToggle]',
  standalone: true,
  providers: [{ provide: FeatureToggleDirective, useExisting: FeatureToggleDirective }]
})
export class FeatureToggleDirective {
  isFeatureEnabled = true; // Could be dynamically set
}
</code></pre>
<hr />
<h3 id="heading-seoservicets-using-both">💡 <code>seo.service.ts</code> — Using Both</h3>
<pre><code class="lang-tsx">
import { Injectable, inject } from '@angular/core';
import { SlugifyPipe } from './slugify.pipe';
import { FeatureToggleDirective } from './feature-toggle.directive';

@Injectable({ providedIn: 'root' })
export class SeoService {
  private slugify = inject(SlugifyPipe);
  private featureToggle = inject(FeatureToggleDirective);

  getSeoSlug(title: string): string {
    if (this.featureToggle.isFeatureEnabled) {
      return this.slugify.transform(title);
    }
    return 'seo-disabled';
  }
}
</code></pre>
<hr />
<h2 id="heading-jest-unit-test-mocking-pipes-and-directives">✅ Jest Unit Test: Mocking Pipes and Directives</h2>
<h3 id="heading-seoservicespects">🧪 <code>seo.service.spec.ts</code></h3>
<pre><code class="lang-tsx">
import { TestBed } from '@angular/core/testing';
import { SeoService } from './seo.service';
import { SlugifyPipe } from './slugify.pipe';
import { FeatureToggleDirective } from './feature-toggle.directive';

describe('SeoService with Pipe &amp; Directive Injection', () =&gt; {
  let service: SeoService;

  const mockSlugifyPipe: Partial&lt;SlugifyPipe&gt; = {
    transform: jest.fn().mockImplementation((s: string) =&gt; `slug--${s}`)
  };

  const mockFeatureToggleDirective: Partial&lt;FeatureToggleDirective&gt; = {
    isFeatureEnabled: true
  };

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      providers: [
        SeoService,
        { provide: SlugifyPipe, useValue: mockSlugifyPipe },
        { provide: FeatureToggleDirective, useValue: mockFeatureToggleDirective }
      ]
    });

    service = TestBed.inject(SeoService);
  });

  it('should return slugified title when feature is enabled', () =&gt; {
    const result = service.getSeoSlug('Hello World');
    expect(result).toBe('slug--Hello World');
  });

  it('should return "seo-disabled" if feature is off', () =&gt; {
    (mockFeatureToggleDirective.isFeatureEnabled as boolean) = false;
    const result = service.getSeoSlug('Hello World');
    expect(result).toBe('seo-disabled');
  });
});
</code></pre>
<hr />
<h2 id="heading-real-developer-talk-why-you-should-care">🧵 Real Developer Talk: Why You Should Care</h2>
<p>💡 The standalone approach is Angular’s future—and Jest is your ticket to faster, smarter, and more scalable tests.</p>
<p>Say goodbye to heavy test modules and long setup code.</p>
<p>Say hello to <strong>on-demand injection</strong>, <strong>fast feedback loops</strong>, and <strong>confidence in every refactor.</strong></p>
<hr />
<h2 id="heading-best-practices-summary">📌 Best Practices Summary</h2>
<h3 id="heading-angular-testing-best-practices">✅ Angular Testing Best Practices</h3>
<ul>
<li><p>Use <code>inject()</code> instead of constructor injection where practical</p>
</li>
<li><p>Avoid global providers when not needed</p>
</li>
<li><p>Always call <code>httpMock.verify()</code> in <code>afterEach</code></p>
</li>
<li><p>Use <code>jest.useFakeTimers()</code> + <code>jest.clearAllTimers()</code> for memory-safe timer mocks</p>
</li>
</ul>
<h3 id="heading-jest-best-practices">✅ Jest Best Practices</h3>
<ul>
<li><p>Prefer <code>jest.fn()</code> for mocks, not manual stubs</p>
</li>
<li><p>Use <code>jest.mock()</code> or <code>jest.spyOn()</code> for external dependencies</p>
</li>
<li><p>Clean up side effects (<code>timers</code>, <code>mocks</code>) in <code>afterEach</code></p>
</li>
<li><p>Keep test cases <strong>small, focused</strong>, and <strong>stateless</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-takeaway-best-practices">🧠 Takeaway Best Practices</h2>
<p>✅ <strong>Never directly depend on DOM-based directive logic in services</strong>, unless:</p>
<ul>
<li><p>The directive is <strong>logic-based</strong> (feature flags, configuration, etc.)</p>
</li>
<li><p>It’s provided via DI using <code>useExisting</code></p>
</li>
</ul>
<p>✅ For <strong>pipes</strong>, prefer:</p>
<ul>
<li><p><code>@Pipe({ standalone: true })</code> with proper <code>providedIn</code> injection</p>
</li>
<li><p>Creating <strong>mock pipes</strong> using <code>jest.fn()</code> in unit tests</p>
</li>
</ul>
<p>✅ Avoid leaking real logic in tests. Always:</p>
<ul>
<li><p>Use <strong>fake timers</strong></p>
</li>
<li><p>Use <strong>jest mocks</strong> for all dependencies</p>
</li>
<li><p>Avoid DOM usage in unit tests</p>
</li>
</ul>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I'd love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[10 Angular Unit Tests to Prevent Memory Leaks You Didn't Know You Needed]]></title><description><![CDATA[Master Angular component cleanup with real-world unit tests for setTimeout, Observables, DOM listeners, AsyncPipe, and more. A complete guide to writing leak-free Angular apps.
Perfect — here are 10 comprehensive unit test cases in Angular to ensure ...]]></description><link>https://hashnode.rajatmalik.dev/10-angular-unit-tests-to-prevent-memory-leaks-you-didnt-know-you-needed</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/10-angular-unit-tests-to-prevent-memory-leaks-you-didnt-know-you-needed</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 12 Nov 2025 13:30:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762599599751/4a1f7e83-a180-458d-b24f-e8a9442dee64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Master Angular component cleanup with real-world unit tests for <code>setTimeout</code>, <code>Observables</code>, DOM listeners, <code>AsyncPipe</code>, and more. A complete guide to writing leak-free Angular apps.</p>
<p>Perfect — here are <strong>10 comprehensive unit test cases</strong> in Angular to ensure <strong>memory leaks are avoided</strong>. These cover all critical and edge async scenarios, such as:</p>
<ul>
<li><p><code>setTimeout</code></p>
</li>
<li><p><code>setInterval</code></p>
</li>
<li><p><code>RxJS subscriptions</code></p>
</li>
<li><p><code>AsyncPipe</code></p>
</li>
<li><p><code>EventListeners</code></p>
</li>
<li><p><code>Subjects</code></p>
</li>
<li><p><code>AnimationFrames</code></p>
</li>
<li><p><code>WebSocket/Custom cleanup</code></p>
</li>
<li><p><code>ResizeObserver</code></p>
</li>
<li><p><code>MutationObserver</code></p>
</li>
</ul>
<hr />
<h3 id="heading-1-unsubscribe-from-rxjs-with-takeuntil">✅ 1. Unsubscribe from RxJS with <code>takeUntil</code></h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should call destroy$.next() and complete() on ngOnDestroy'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> nextSpy = jest.spyOn((component <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).destroy$, <span class="hljs-string">'next'</span>);
  <span class="hljs-keyword">const</span> completeSpy = jest.spyOn((component <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).destroy$, <span class="hljs-string">'complete'</span>);

component.ngOnDestroy();
  expect(nextSpy).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
  expect(completeSpy).toHaveBeenCalledTimes(<span class="hljs-number">1</span>);
});
</code></pre>
<hr />
<h3 id="heading-2-clear-setinterval-on-destroy">✅ 2. Clear <code>setInterval</code> on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should clear interval on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> clearSpy = jest.spyOn(<span class="hljs-built_in">window</span>, <span class="hljs-string">'clearInterval'</span>);
  component.intervalId = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {}, <span class="hljs-number">1000</span>);

component.ngOnDestroy();
  expect(clearSpy).toHaveBeenCalledWith(component.intervalId);
  <span class="hljs-built_in">clearInterval</span>(component.intervalId); <span class="hljs-comment">// cleanup</span>
});
</code></pre>
<hr />
<h3 id="heading-3-clear-settimeout-on-destroy">✅ 3. Clear <code>setTimeout</code> on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should clear timeout on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> clearSpy = jest.spyOn(<span class="hljs-built_in">window</span>, <span class="hljs-string">'clearTimeout'</span>);
  component.timeoutId = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {}, <span class="hljs-number">1000</span>);

component.ngOnDestroy();
  expect(clearSpy).toHaveBeenCalledWith(component.timeoutId);
  <span class="hljs-built_in">clearTimeout</span>(component.timeoutId); <span class="hljs-comment">// cleanup</span>
});
</code></pre>
<hr />
<h3 id="heading-4-remove-event-listener-on-destroy">✅ 4. Remove Event Listener on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should remove event listener on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> removeSpy = jest.spyOn(<span class="hljs-built_in">document</span>, <span class="hljs-string">'removeEventListener'</span>);
  component.listener = jest.fn();

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'click'</span>, component.listener);
  component.ngOnDestroy();
  expect(removeSpy).toHaveBeenCalledWith(<span class="hljs-string">'click'</span>, component.listener);
});
</code></pre>
<hr />
<h3 id="heading-5-verify-asyncpipe-usage-no-manual-cleanup-required">✅ 5. Verify <code>AsyncPipe</code> Usage — No Manual Cleanup Required</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should have data$ defined for async pipe usage'</span>, <span class="hljs-function">() =&gt;</span> {
  expect(component.data$).toBeDefined();
});
</code></pre>
<blockquote>
<p><em>Note: AsyncPipe automatically unsubscribes on destroy.</em></p>
</blockquote>
<hr />
<h3 id="heading-6-unsubscribe-from-manual-subject">✅ 6. Unsubscribe from Manual Subject</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should unsubscribe from subject'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> sub = <span class="hljs-keyword">new</span> Subject&lt;<span class="hljs-built_in">void</span>&gt;();
  <span class="hljs-keyword">const</span> spy = jest.fn();
  sub.subscribe(spy);

sub.next();
  sub.complete();
  expect(spy).toHaveBeenCalled();
});
</code></pre>
<hr />
<h3 id="heading-7-cancel-requestanimationframe-on-destroy">✅ 7. Cancel <code>requestAnimationFrame</code> on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should cancel requestAnimationFrame on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> cancelSpy = jest.spyOn(<span class="hljs-built_in">window</span>, <span class="hljs-string">'cancelAnimationFrame'</span>);
  component.rafId = requestAnimationFrame(<span class="hljs-function">() =&gt;</span> {});

component.ngOnDestroy();
  expect(cancelSpy).toHaveBeenCalledWith(component.rafId);
});
</code></pre>
<hr />
<h3 id="heading-8-close-custom-websocket-or-observer-on-destroy">✅ 8. Close Custom WebSocket or Observer on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should close websocket connection on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  component.socket = { close: jest.fn() } <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
  component.ngOnDestroy();

expect(component.socket.close).toHaveBeenCalled();
});
</code></pre>
<hr />
<h3 id="heading-9-disconnect-resizeobserver-on-destroy">✅ 9. Disconnect <code>ResizeObserver</code> on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should disconnect ResizeObserver on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  component.resizeObserver = { disconnect: jest.fn() } <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
  component.ngOnDestroy();

expect(component.resizeObserver.disconnect).toHaveBeenCalled();
});
</code></pre>
<hr />
<h3 id="heading-10-disconnect-mutationobserver-on-destroy">✅ 10. Disconnect <code>MutationObserver</code> on Destroy</h3>
<pre><code class="lang-typescript">it(<span class="hljs-string">'should disconnect MutationObserver on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
  component.mutationObserver = { disconnect: jest.fn() } <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
  component.ngOnDestroy();

expect(component.mutationObserver.disconnect).toHaveBeenCalled();
});
</code></pre>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I’d love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Speed Up Your Angular App Like a Pro: Lazy Load + Preload Strategy Explained]]></title><description><![CDATA[Angular is a powerful framework, but as your app grows, so does the size of your JavaScript bundles — and with that, your initial load time. Users today expect near-instant experiences, and waiting even a few extra seconds can lead to frustration and...]]></description><link>https://hashnode.rajatmalik.dev/speed-up-your-angular-app-like-a-pro-lazy-load-preload-strategy-explained</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/speed-up-your-angular-app-like-a-pro-lazy-load-preload-strategy-explained</guid><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 11 Nov 2025 13:30:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762595375472/178783cc-3052-4bec-8de5-1b68e451af28.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><em>Angular is a powerful framework, but as your app grows, so does the size of your JavaScript bundles — and with that, your initial load time. Users today expect near-instant experiences, and waiting even a few extra seconds can lead to frustration and bounce. That’s where Angular’s lazy loading and preloading strategies come into play. These two techniques can dramatically improve your app’s performance by smartly loading only what’s needed — exactly when it’s needed.</em></p>
</blockquote>
<h3 id="heading-start-with-a-hook-question">❓ Start With a Hook Question</h3>
<blockquote>
<p><em>Is your Angular app feeling slower than it should?</em></p>
</blockquote>
<p>In this article, I’ll walk you through how I use lazy loading and preloading strategies in Angular to optimize real-world projects.</p>
<p>You’ll get:</p>
<ul>
<li><p>🚀 Real code examples</p>
</li>
<li><p>📊 A deep dive into <em>when and why</em> to preload</p>
</li>
<li><p>🔁 An interactive way to toggle loading strategies</p>
</li>
<li><p>✅ Performance results and best practices</p>
</li>
</ul>
<p>By the end, you’ll be able to implement a hybrid strategy that keeps your initial load fast but prepares key routes ahead of time for instant transitions.</p>
<h3 id="heading-why-performance-optimization-matters">💡 Why Performance Optimization Matters</h3>
<blockquote>
<p><em>Page speed is not just about numbers — it directly impacts UX, SEO, and retention.</em></p>
</blockquote>
<p>Angular gives us incredible flexibility with route-based code splitting (aka lazy loading) and preload strategies, but many developers stop at just lazy loading. Let’s go beyond that!</p>
<h3 id="heading-basic-lazy-loading-setup-in-angular">📁 Basic Lazy Loading Setup in Angular</h3>
<p>Here’s a simple route-based lazy loading module setup:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// app-routing.module.ts</span>
<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'admin'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./admin/admin.module'</span>).then(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> m.AdminModule),
  },
];
</code></pre>
<p>✅ Explanation: This ensures the <code>AdminModule</code> is not part of the initial bundle. It’s loaded only when the user visits <code>/admin</code>.</p>
<h3 id="heading-whats-preloading-then">⚙️ What’s Preloading, Then?</h3>
<p>Preloading helps load lazily-loaded routes in the background after the app initializes.</p>
<p>Angular comes with a built-in strategy:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@NgModule</span>({
  imports: [
    RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
  ],
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppRoutingModule {}
</code></pre>
<p>🎯 This improves navigation <em>without slowing down the first paint</em>.</p>
<h3 id="heading-custom-preloading-strategy-smart-preload">🚀 Custom Preloading Strategy (Smart Preload)</h3>
<p>Sometimes you don’t want to preload <em>everything</em>. You can use route metadata:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'dashboard'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span>
      <span class="hljs-keyword">import</span>(<span class="hljs-string">'./dashboard/dashboard.module'</span>).then(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> m.DashboardModule),
    data: { preload: <span class="hljs-literal">true</span> },
  },
];
</code></pre>
<p>And create a custom strategy:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({ providedIn: <span class="hljs-string">'root'</span> })
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SelectivePreloadingStrategy <span class="hljs-keyword">implements</span> PreloadingStrategy {
  preload(route: Route, fn: <span class="hljs-function">() =&gt;</span> Observable&lt;<span class="hljs-built_in">any</span>&gt;): Observable&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-keyword">return</span> route.data?.[<span class="hljs-string">'preload'</span>] ? fn() : <span class="hljs-keyword">of</span>(<span class="hljs-literal">null</span>);
  }
}
</code></pre>
<p>Use it like this:</p>
<pre><code class="lang-typescript">RouterModule.forRoot(routes, {
  preloadingStrategy: SelectivePreloadingStrategy,
});
</code></pre>
<p>✅ Now you control what preloads, and what doesn’t.</p>
<h3 id="heading-pro-tips-for-real-world-projects">🧠 Pro Tips for Real-World Projects</h3>
<ul>
<li><p>🚫 Don’t lazy load core modules or auth-related module</p>
</li>
<li><p>✅ Always lazy load heavy feature modules (charts, tables)</p>
</li>
<li><p>🧠 Preload modules based on user behavior prediction</p>
</li>
<li><p>⏳ Consider quicklink-style preloading for anchor tag hover (advanced)</p>
</li>
</ul>
<h3 id="heading-real-performance-gains">📊 Real Performance Gains</h3>
<p>After using this strategy on a production Angular app:</p>
<ul>
<li><p>First load time dropped from 5.8s → 2.3s</p>
</li>
<li><p>Route navigation delays reduced from ~1.2s → ❤00ms</p>
</li>
<li><p>Google PageSpeed score improved by 18 points</p>
</li>
</ul>
<h3 id="heading-what-youll-walk-away-with">✅ What You’ll Walk Away With</h3>
<p>By now, you’ve learned:</p>
<ul>
<li><p>✅ How to lazy load Angular modules properly</p>
</li>
<li><p>✅ How to preload selected modules for a smooth experience</p>
</li>
<li><p>✅ How to write a custom preload strategy</p>
</li>
<li><p>✅ How to use route metadata to control behavior</p>
</li>
</ul>
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 Did this article spark new ideas or help solve a real problem?</p>
<p>💬 I’d love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p>Drop them in the comments below — let’s learn together!</p>
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 Share it with your team, tech friends, or community — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/">LinkedIn</a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik">Threads</a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik">X (Twitter)</a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/">BlueSky</a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat">GitHub Projects</a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/">Website</a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/">Medium Blog</a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat">Dev Blog</a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/">Substack</a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/">Portfolio</a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat">Hashnode</a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a 👏 Clap</p>
</li>
<li><p>Drop a 💬 Comment</p>
</li>
<li><p>Hit 🔔 Follow for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — together.</p>
<p>Stay tuned for more Angular tips, patterns, and performance tricks! 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Boost Your Git Productivity: A Simple Dev's Guide to Git Aliases with Real Examples]]></title><description><![CDATA[Ever feel like you’re typing the same long Git commands over and over again?
If you’re a developer (especially a frontend or Angular developer), Git is part of your daily life. But let me ask you: What if you could save time, avoid typos, and streaml...]]></description><link>https://hashnode.rajatmalik.dev/boost-your-git-productivity-a-simple-devs-guide-to-git-aliases-with-real-examples</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/boost-your-git-productivity-a-simple-devs-guide-to-git-aliases-with-real-examples</guid><category><![CDATA[Git]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[DEVCommunity]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 05 Nov 2025 13:30:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762078221513/19f46778-a4f9-4c2b-b6f8-c1792a05a650.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h4 id="heading-ever-feel-like-youre-typing-the-same-long-git-commands-over-and-over-again">Ever feel like you’re typing the same long Git commands over and over again?</h4>
<p>If you’re a developer (especially a frontend or Angular developer), Git is part of your daily life. But let me ask you: <strong>What if you could save time, avoid typos, and streamline your Git workflow — all by typing just a few letters?</strong></p>
<p>By the end of this article, you’ll know:</p>
<ul>
<li><p>What Git aliases are and why they matter</p>
</li>
<li><p>How to create your own Git aliases (with real examples)</p>
</li>
<li><p>Some <strong>must-have aliases</strong> for frontend developers</p>
</li>
<li><p>How to make them global and persist across projects</p>
</li>
<li><p>Bonus: How to alias complex commands and combine flags like a pro</p>
</li>
</ul>
<p>So grab a cup of chai (or coffee ☕) — let’s optimize your Git life.</p>
<hr />
<h3 id="heading-what-are-git-aliases">What Are Git Aliases?</h3>
<p>A <strong>Git alias</strong> is a custom shortcut you define to avoid typing long or repetitive commands.</p>
<p>Think of it like a speed dial for Git.</p>
<p>Instead of typing:</p>
<pre><code class="lang-bash">git status
</code></pre>
<p>You can just type:</p>
<pre><code class="lang-bash">git s
</code></pre>
<p>And it works the same. Magic? Nope. Just smart config.</p>
<hr />
<h3 id="heading-how-to-set-up-a-git-alias-step-by-step">How to Set Up a Git Alias (Step-by-Step)</h3>
<p>Git stores aliases in your <code>.gitconfig</code> file.</p>
<p>To add one, you can run this command:</p>
<pre><code class="lang-bash">git config --global alias.s status
</code></pre>
<p>Now whenever you type:</p>
<pre><code class="lang-bash">git s
</code></pre>
<p>It will run <code>git status</code>. Simple, right?</p>
<hr />
<h3 id="heading-must-have-git-aliases-for-developers">Must-Have Git Aliases for Developers</h3>
<p>Here are some popular and <strong>time-saving aliases</strong>:</p>
<pre><code class="lang-bash">git config --global alias.s status
git config --global alias.c commit
git config --global alias.ca <span class="hljs-string">'commit -a'</span>
git config --global alias.cm <span class="hljs-string">'commit -m'</span>
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.hist <span class="hljs-string">"log --oneline --graph --decorate --all"</span>
</code></pre>
<blockquote>
<p><em>Tip: You can open your ~/.gitconfig file and edit aliases manually too.</em></p>
</blockquote>
<hr />
<h3 id="heading-demo-my-favorite-git-alias-the-hist-log-view">Demo: My Favorite Git Alias — The “Hist” Log View</h3>
<p>This one is a game-changer for visualizing branches:</p>
<pre><code class="lang-bash">git config --global alias.hist <span class="hljs-string">"log --oneline --graph --decorate --all"</span>
</code></pre>
<p>Then just run:</p>
<pre><code class="lang-bash">git hist
</code></pre>
<p>And you’ll see:</p>
<pre><code class="lang-bash">* a3c9d22 (HEAD -&gt; main) Updated README
* 934a7cd (feature/login) Added login logic
* e1a8e5b Initial commit
</code></pre>
<p>It’s like Git turned visual!</p>
<hr />
<h3 id="heading-advanced-git-aliases-with-parameters">Advanced Git Aliases (with Parameters)</h3>
<p>Yes, you can alias even <strong>complex commands</strong> like this:</p>
<pre><code class="lang-bash">git config --global alias.undo <span class="hljs-string">"reset --soft HEAD~1"</span>
</code></pre>
<p>This gives you the power to “undo” your last commit, keeping changes in staging.</p>
<p>Usage:</p>
<pre><code class="lang-bash">git undo
</code></pre>
<p>Boom! Like Ctrl+Z for Git commits.</p>
<hr />
<h3 id="heading-pro-tip-use-functions-in-bash-or-zsh">Pro Tip: Use Functions in Bash or Zsh</h3>
<p>Want to create more interactive or multi-step Git shortcuts? Use shell functions in <code>.bashrc</code> or <code>.zshrc</code> like this:</p>
<pre><code class="lang-bash"><span class="hljs-function"><span class="hljs-title">gpush</span></span>() {
  git push origin $(git branch --show-current)
}
</code></pre>
<p>Now just type:</p>
<pre><code class="lang-bash">gpush
</code></pre>
<p>It’ll push your current branch automatically.</p>
<hr />
<h3 id="heading-why-devs-should-care">Why Devs Should Care</h3>
<p>As Angular/React developers, we switch branches, commit often, stash changes, and cherry-pick bug fixes. Git aliases let you do all this <strong>faster and error-free</strong>.</p>
<ul>
<li><p>Save time in daily workflows</p>
</li>
<li><p>Reduce mental fatigue</p>
</li>
<li><p>Look like a pro in front of your team 👨‍💻👩‍💻</p>
</li>
</ul>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 <strong>Did this article spark new ideas or help solve a real problem?</strong></p>
<p>💬 I’d love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p><strong>Drop them in the comments below — let’s learn together!</strong></p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 <strong>Share it with your team, tech friends, or community</strong> — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Managing Git Submodules in a Micro Frontend Architecture: A Step-by-Step Guide]]></title><description><![CDATA[In modern enterprise-scale frontend applications, micro frontends have become a popular architectural choice. Each micro frontend often exists as an independently deployable and maintainable module, and in many setups, teams choose to manage them usi...]]></description><link>https://hashnode.rajatmalik.dev/managing-git-submodules-in-a-micro-frontend-architecture-a-step-by-step-guide</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/managing-git-submodules-in-a-micro-frontend-architecture-a-step-by-step-guide</guid><category><![CDATA[Git]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 04 Nov 2025 13:30:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762076437479/fcec2a59-e898-43ec-8c80-3ad0939e3529.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In modern enterprise-scale frontend applications, micro frontends have become a popular architectural choice. Each micro frontend often exists as an independently deployable and maintainable module, and in many setups, teams choose to manage them using Git submodules.</p>
<p>In this article, I’ll walk you through the key concepts, common pitfalls, and best practices when using Git submodules — explained clearly and politely, with a special focus on working with 10 or more micro frontends.</p>
<h3 id="heading-what-are-git-submodules">What Are Git Submodules?</h3>
<p>Git submodules allow you to include one Git repository inside another as a subdirectory. This is especially useful when:</p>
<ul>
<li><p>Your team shares a common library across multiple applications.</p>
</li>
<li><p>You want to keep each micro frontend in a separate Git repo, but still orchestrate and manage them from a parent repo.</p>
</li>
</ul>
<h3 id="heading-cloning-a-repo-with-submodules">Cloning a Repo with Submodules</h3>
<p>When cloning a parent repository that contains submodules, the submodules are not downloaded by default.</p>
<h3 id="heading-recommended-way">Recommended way:</h3>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> --recursive &lt;parent-repo-url&gt;
</code></pre>
<h3 id="heading-if-you-cloned-without-recursive-do">If you cloned without -recursive, do:</h3>
<pre><code class="lang-bash">git submodule update --init --recursive
</code></pre>
<p>This will initialize and fetch all submodules and their history.</p>
<h3 id="heading-how-git-handles-submodule-references">How Git Handles Submodule References</h3>
<p>Each submodule is pinned to a specific commit. This information is not stored in the <code>.gitmodules</code> file, but rather inside the parent repo’s <code>.git</code> directory.</p>
<p>You can find what commit the submodule is pinned to using:</p>
<pre><code class="lang-bash">git submodule status
</code></pre>
<p>This ensures that the parent repo can keep track of exact versions of each micro frontend, leading to consistent builds and deployments.</p>
<h3 id="heading-making-changes-inside-a-submodule">Making Changes Inside a Submodule</h3>
<p>Submodules are treated as completely separate repositories. So if you:</p>
<ol>
<li><p>Navigate into a submodule:</p>
</li>
<li><p>Make changes and commit them:</p>
</li>
</ol>
<p>The parent repo still points to the old commit. To update the reference:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ../
git add microfrontend-one
git commit -m <span class="hljs-string">"Update microfrontend-one to latest commit"</span>
</code></pre>
<p>This step is crucial to notify the parent repo that it should now track the new commit of the submodule.</p>
<h3 id="heading-switching-branches-in-the-parent-repo">Switching Branches in the Parent Repo</h3>
<p>When switching branches in the parent repo, Git does not automatically update the submodules to match the new commit references.</p>
<h3 id="heading-to-sync-submodules-after-switching-branches">To sync submodules after switching branches:</h3>
<pre><code class="lang-bash">git submodule update --init --recursive
</code></pre>
<p>This ensures that each micro frontend is on the right commit for the selected parent repo branch.</p>
<h3 id="heading-automating-submodule-updates-recommended-for-teams">Automating Submodule Updates (Recommended for Teams)</h3>
<p>To avoid having to manually run the above command all the time, update your Git config globally:</p>
<pre><code class="lang-bash">git config --global submodule.recurse <span class="hljs-literal">true</span>
git config --global fetch.recurseSubmodules <span class="hljs-literal">true</span>
</code></pre>
<p>This enables automatic updates during common Git operations like <code>pull</code> and <code>checkout</code>.</p>
<h3 id="heading-adding-a-new-micro-frontend-as-a-submodule">Adding a New Micro Frontend as a Submodule</h3>
<p>To include a new micro frontend repository:</p>
<pre><code class="lang-bash">git submodule add &lt;repo-url&gt; microfrontend-new/
</code></pre>
<p>This will:</p>
<ul>
<li><p>Clone the repo</p>
</li>
<li><p>Create or update the <code>.gitmodules</code> file</p>
</li>
<li><p>Pin the submodule to the latest commit on its default branch</p>
</li>
</ul>
<p>You may then switch to a specific commit or branch in that submodule and commit the change in the parent repo.</p>
<h3 id="heading-removing-a-submodule">Removing a Submodule</h3>
<p>To safely remove a submodule:</p>
<pre><code class="lang-bash">git rm --cached &lt;path-to-submodule&gt;
rm -rf &lt;path-to-submodule&gt;
</code></pre>
<p>Then commit the changes in the parent repo. Modern Git versions handle submodule removal more gracefully than older ones.</p>
<hr />
<h3 id="heading-best-practices-for-using-submodules-in-micro-frontend-setups">🧠 Best Practices for Using Submodules in Micro Frontend Setups</h3>
<ol>
<li><p>Use the latest Git version — submodule UX is significantly improved.</p>
</li>
<li><p>Pin to specific commits for deterministic builds.</p>
</li>
<li><p>Keep submodules clean — avoid mixing parent and submodule changes in one commit.</p>
</li>
<li><p>Add aliases for common submodule commands to streamline workflows.</p>
</li>
<li><p>Ensure all team members are trained in submodule workflows to avoid accidental breakage.</p>
</li>
</ol>
<hr />
<h3 id="heading-alternatives-to-git-submodules">Alternatives to Git Submodules</h3>
<p>Before committing to submodules, consider these alternatives:</p>
<h3 id="heading-1-package-manager-approach">1. Package Manager Approach</h3>
<p>Publish each micro frontend as a package via npm (public or private). Then reference them via <code>package.json</code>.</p>
<p>✅ Easier for CI/CD and version management</p>
<p>⛔ Requires package management setup and publishing</p>
<h3 id="heading-2-monorepo-approach">2. Monorepo Approach</h3>
<p>Manage all micro frontends inside one repo using tools like Nx, Turborepo, or Lerna.</p>
<p>✅ Centralized development and tooling</p>
<p>⛔ Heavier for large codebases if not structured well</p>
<hr />
<h3 id="heading-final-thoughts">📢 Final Thoughts</h3>
<p>While Git submodules offer a clean and elegant mechanism to manage independent micro frontend repositories, they can introduce complexity. If your team is already using them, the tips above should help you manage them more effectively. Otherwise, evaluate whether alternatives like npm packages or a monorepo better suit your workflow.</p>
<p>If you’re working with a large team and find Git submodules confusing, it might be time to:</p>
<ul>
<li><p>Set up clear documentation</p>
</li>
<li><p>Provide Git training or workshops</p>
</li>
<li><p>Automate repetitive tasks with aliases or scripts</p>
</li>
</ul>
<hr />
<h3 id="heading-your-turn-devs">🎯 Your Turn, Devs!</h3>
<p>👀 Did this article spark new ideas or help solve a real problem?</p>
<p>💬 I’d love to hear about it!</p>
<p>✅ Are you already using this technique in your Angular or frontend project?</p>
<p>🧠 Got questions, doubts, or your own twist on the approach?</p>
<p>Drop them in the comments below — let’s learn together!</p>
<hr />
<h3 id="heading-lets-grow-together">🙌 Let’s Grow Together!</h3>
<p>If this article added value to your dev journey:</p>
<p>🔁 Share it with your team, tech friends, or community — you never know who might need it right now.</p>
<p>📌 Save it for later and revisit as a quick reference.</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/">LinkedIn</a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik">Threads</a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik">X (Twitter)</a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/">BlueSky</a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat">GitHub Projects</a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/">Website</a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/">Medium Blog</a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat">Dev Blog</a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/">Substack</a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/">Portfolio</a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat">Hashnode</a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a 👏 Clap</p>
</li>
<li><p>Drop a 💬 Comment</p>
</li>
<li><p>Hit 🔔 Follow for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — together.</p>
<p>Stay tuned for more Angular tips, patterns, and performance tricks! 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[LinkedSignal vs Computed: The Angular Signals Showdown You've Been Waiting For]]></title><description><![CDATA[Introduction
Ever felt like Angular's change detection was that one friend who shows up uninvited to every party? You know, constantly checking if anything changed, even when nothing did? 🤔
Well, Angular Signals just changed the game—and if you're n...]]></description><link>https://hashnode.rajatmalik.dev/linkedsignal-vs-computed-the-angular-signals-showdown-youve-been-waiting-for</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/linkedsignal-vs-computed-the-angular-signals-showdown-youve-been-waiting-for</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 29 Oct 2025 13:30:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760765525034/995567e0-e2ab-4332-8b72-3b109ee539c3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>Ever felt like Angular's change detection was that one friend who shows up uninvited to every party? You know, constantly checking if anything changed, even when nothing did? 🤔</p>
<p>Well, Angular Signals just changed the game—and if you're not using <code>computed</code> and <code>linkedSignal</code> yet, you're missing out on some serious performance gains and cleaner code.</p>
<p><strong>By the end of this article, you'll know:</strong></p>
<ul>
<li><p>When to reach for <code>computed</code> vs <code>linkedSignal</code> (and why it matters)</p>
</li>
<li><p>How to build reactive features without Zone.js breathing down your neck</p>
</li>
<li><p>Real-world patterns that'll make your senior devs do a double-take</p>
</li>
<li><p>The performance tricks that separate good Angular apps from <em>great</em> ones</p>
</li>
</ul>
<p>Let's dive in! But first, quick question: <strong>Are you still manually managing subscriptions in 2025?</strong> Drop a comment below—I'm genuinely curious! 👇</p>
<hr />
<h2 id="heading-why-angular-signals-are-your-new-best-friend">Why Angular Signals Are Your New Best Friend</h2>
<p>Remember the days of wrestling with RxJS subscriptions, memory leaks, and that one <code>ngOnDestroy</code> you forgot to implement? Angular Signals are here to save us from that chaos.</p>
<p>Signals bring <strong>fine-grained reactivity</strong> to Angular—meaning your app only updates what needs updating, when it needs updating. No more Zone.js checking everything under the sun.</p>
<pre><code class="lang-tsx">// The old way (we've all been there)
export class OldSchoolComponent {
  count$ = new BehaviorSubject(0);
  doubled$ = this.count$.pipe(map(n =&gt; n * 2));

  ngOnDestroy() {
    // Don't forget this! (But we always do...)
  }
}

// The signals way (clean and simple)
export class ModernComponent {
  count = signal(0);
  doubled = computed(() =&gt; this.count() * 2);
  // No cleanup needed! 🎉
}
</code></pre>
<hr />
<h2 id="heading-computed-signals-your-reactive-calculator">Computed Signals: Your Reactive Calculator 🧮</h2>
<h3 id="heading-what-are-computed-signals">What Are Computed Signals?</h3>
<p>Think of <code>computed</code> as that smart friend who always knows the answer because they're constantly doing the math in their head. Computed signals <strong>automatically derive values</strong> from other signals and update whenever their dependencies change.</p>
<h3 id="heading-the-magic-in-action">The Magic in Action</h3>
<pre><code class="lang-tsx">import { signal, computed } from '@angular/core';

export class PriceCalculatorComponent {
  // Base signals
  quantity = signal(1);
  pricePerUnit = signal(99.99);
  discountPercent = signal(0);

  // Computed magic happens here
  subtotal = computed(() =&gt;
    this.quantity() * this.pricePerUnit()
  );

  discountAmount = computed(() =&gt;
    this.subtotal() * (this.discountPercent() / 100)
  );

  total = computed(() =&gt;
    this.subtotal() - this.discountAmount()
  );

  // Update quantity, everything recalculates automatically!
  addToCart() {
    this.quantity.update(q =&gt; q + 1);
  }
}
</code></pre>
<p><strong>When should you use computed?</strong></p>
<ul>
<li><p>✅ Deriving values from other signals</p>
</li>
<li><p>✅ Calculations that depend on multiple signals</p>
</li>
<li><p>✅ Read-only derived state</p>
</li>
<li><p>✅ Performance-critical computations (they're memoized!)</p>
</li>
</ul>
<p>💡 <strong>Pro tip:</strong> Computed signals are <strong>lazy</strong> and <strong>cached</strong>. They only recalculate when accessed AND their dependencies changed. That's free performance right there!</p>
<hr />
<h2 id="heading-linkedsignal-the-two-way-street">LinkedSignal: The Two-Way Street 🔄</h2>
<h3 id="heading-enter-linkedsignal-computeds-flexible-cousin">Enter LinkedSignal: Computed's Flexible Cousin</h3>
<p>While <code>computed</code> is read-only, <code>linkedSignal</code> is that friend who listens but also has opinions. It can derive from other signals AND be manually updated—perfect for syncing with external sources or handling bi-directional data flow.</p>
<h3 id="heading-linkedsignal-in-the-wild">LinkedSignal in the Wild</h3>
<pre><code class="lang-tsx">import { signal, linkedSignal } from '@angular/core';

export class UserPreferencesComponent {
  // Source signal
  fahrenheit = signal(72);

  // LinkedSignal that syncs both ways
  celsius = linkedSignal(() =&gt;
    Math.round((this.fahrenheit() - 32) * 5/9)
  );

  // You can update it directly!
  updateCelsius(value: number) {
    this.celsius.set(value);
    // Now fahrenheit is out of sync, but that's okay!
    // LinkedSignal allows this flexibility
  }

  // Or sync it back
  syncFromCelsius() {
    const c = this.celsius();
    this.fahrenheit.set(Math.round(c * 9/5 + 32));
  }
}
</code></pre>
<h3 id="heading-real-world-example-form-input-sync">Real-World Example: Form Input Sync</h3>
<p>Here's where <code>linkedSignal</code> really shines—syncing form inputs with formatted displays:</p>
<pre><code class="lang-tsx">export class PaymentFormComponent {
  // Raw input value
  rawCardNumber = signal('');

  // Formatted display that can also be edited
  formattedCardNumber = linkedSignal(() =&gt; {
    const raw = this.rawCardNumber().replace(/\\s/g, '');
    return raw.match(/.{1,4}/g)?.join(' ') || '';
  });

  onFormattedInput(value: string) {
    // Update the formatted version
    this.formattedCardNumber.set(value);
    // Extract and update raw
    this.rawCardNumber.set(value.replace(/\\s/g, ''));
  }
}
</code></pre>
<p><strong>When to reach for linkedSignal?</strong></p>
<ul>
<li><p>✅ Two-way data binding scenarios</p>
</li>
<li><p>✅ Syncing with external APIs or localStorage</p>
</li>
<li><p>✅ Form inputs that need formatting</p>
</li>
<li><p>✅ Temporary overrides of computed values</p>
</li>
</ul>
<hr />
<h2 id="heading-the-ultimate-comparison-computed-vs-linkedsignal">The Ultimate Comparison: Computed vs LinkedSignal 🥊</h2>
<h3 id="heading-what-they-share">What They Share</h3>
<ul>
<li><p>🎯 Both derive from other signals</p>
</li>
<li><p>🎯 Both update reactively</p>
</li>
<li><p>🎯 Both are lazy (compute on-demand)</p>
</li>
<li><p>🎯 Both integrate seamlessly with Angular's template system</p>
</li>
</ul>
<h3 id="heading-the-key-differences">The Key Differences</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Computed</td><td>LinkedSignal</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Mutability</strong></td><td>Read-only</td><td>Read-write</td></tr>
<tr>
<td><strong>Use Case</strong></td><td>Pure derivations</td><td>Bi-directional sync</td></tr>
<tr>
<td><strong>Performance</strong></td><td>Highly optimized, cached</td><td>Flexible but less cached</td></tr>
<tr>
<td><strong>Best For</strong></td><td>Calculations, transformations</td><td>Forms, external sync</td></tr>
<tr>
<td><strong>Can be set()</strong></td><td>❌ Never</td><td>✅ Yes</td></tr>
<tr>
<td><strong>Stays in sync</strong></td><td>✅ Always</td><td>🔄 Until manually changed</td></tr>
</tbody>
</table>
</div><h3 id="heading-decision-tree-yes-i-made-one-for-you">Decision Tree (Yes, I Made One for You!)</h3>
<pre><code class="lang-tsx">// Ask yourself:
const shouldUseComputed = () =&gt; {
  if (needToManuallyUpdate) return false;
  if (pureCalculation) return true;
  if (externalDataSync) return false;
  return true; // When in doubt, computed is usually right
};
</code></pre>
<p><strong>Quick question:</strong> What's your most complex reactive state scenario? I'd love to hear how you'd solve it with signals! Drop it in the comments 💬</p>
<hr />
<h2 id="heading-real-world-example-dynamic-pricing-calculator">Real-World Example: Dynamic Pricing Calculator 💰</h2>
<p>Let's build something practical—a pricing calculator with tax, discounts, and currency conversion:</p>
<pre><code class="lang-tsx">@Component({
  selector: 'app-pricing',
  template: `
    &lt;div class="pricing-calculator"&gt;
      &lt;h3&gt;Product Pricing&lt;/h3&gt;

      &lt;label&gt;
        Quantity:
        &lt;input type="number"
               [value]="quantity()"
               (input)="quantity.set(+$event.target.value)"&gt;
      &lt;/label&gt;

      &lt;label&gt;
        Discount Code:
        &lt;input [value]="discountCode()"
               (input)="applyDiscount($event.target.value)"&gt;
      &lt;/label&gt;

      &lt;div class="results"&gt;
        &lt;p&gt;Subtotal: {{ subtotal() | currency }}&lt;/p&gt;
        &lt;p&gt;Discount: -{{ discountAmount() | currency }}&lt;/p&gt;
        &lt;p&gt;Tax: {{ taxAmount() | currency }}&lt;/p&gt;
        &lt;h4&gt;Total: {{ finalPrice() | currency }}&lt;/h4&gt;

        &lt;!-- LinkedSignal for currency display --&gt;
        &lt;p&gt;In EUR: € {{ priceInEur() }}&lt;/p&gt;
        &lt;button (click)="overrideEurPrice()"&gt;
          Set Custom EUR Price
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `
})
export class PricingCalculatorComponent {
  // Base signals
  quantity = signal(1);
  unitPrice = signal(49.99);
  discountCode = signal('');
  taxRate = signal(0.08); // 8% tax

  // Computed for calculations
  subtotal = computed(() =&gt;
    this.quantity() * this.unitPrice()
  );

  discountPercent = computed(() =&gt; {
    const code = this.discountCode();
    // Simple discount logic
    switch(code) {
      case 'SAVE10': return 0.10;
      case 'SAVE20': return 0.20;
      case 'HALFOFF': return 0.50;
      default: return 0;
    }
  });

  discountAmount = computed(() =&gt;
    this.subtotal() * this.discountPercent()
  );

  taxableAmount = computed(() =&gt;
    this.subtotal() - this.discountAmount()
  );

  taxAmount = computed(() =&gt;
    this.taxableAmount() * this.taxRate()
  );

  finalPrice = computed(() =&gt;
    this.taxableAmount() + this.taxAmount()
  );

  // LinkedSignal for currency conversion
  // Can be overridden by user or API
  priceInEur = linkedSignal(() =&gt;
    // Assuming 1 USD = 0.92 EUR
    Math.round(this.finalPrice() * 0.92 * 100) / 100
  );

  applyDiscount(code: string) {
    this.discountCode.set(code.toUpperCase());
  }

  overrideEurPrice() {
    // Maybe from an API or user input
    const customPrice = prompt('Enter custom EUR price:');
    if (customPrice) {
      this.priceInEur.set(parseFloat(customPrice));
    }
  }
}
</code></pre>
<hr />
<h2 id="heading-unit-testing-your-signals-because-were-professionals">Unit Testing Your Signals (Because We're Professionals) 🧪</h2>
<p>Nobody talks about testing signals, but here's how to do it right:</p>
<pre><code class="lang-tsx">describe('PricingCalculatorComponent', () =&gt; {
  let component: PricingCalculatorComponent;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [PricingCalculatorComponent]
    });

    const fixture = TestBed.createComponent(PricingCalculatorComponent);
    component = fixture.componentInstance;
  });

  it('should calculate subtotal correctly', () =&gt; {
    component.quantity.set(3);
    component.unitPrice.set(10);

    expect(component.subtotal()).toBe(30);
  });

  it('should apply discount code', () =&gt; {
    component.quantity.set(2);
    component.unitPrice.set(50);
    component.discountCode.set('SAVE20');

    expect(component.discountAmount()).toBe(20);
    expect(component.finalPrice()).toBe(86.4); // 80 + 8% tax
  });

  it('should allow EUR price override with linkedSignal', () =&gt; {
    component.finalPrice = signal(100); // Mock final price

    // Check computed value
    expect(component.priceInEur()).toBeCloseTo(92);

    // Override it
    component.priceInEur.set(95);
    expect(component.priceInEur()).toBe(95);

    // It's now disconnected from the source
    component.finalPrice.set(200);
    expect(component.priceInEur()).toBe(95); // Still 95!
  });
});
</code></pre>
<hr />
<h2 id="heading-best-practices-amp-performance-tips">Best Practices &amp; Performance Tips 🚀</h2>
<h3 id="heading-dos">Do's ✅</h3>
<ol>
<li><p><strong>Keep computed signals pure</strong></p>
<pre><code class="lang-tsx"> // Good
 total = computed(() =&gt; this.price() * this.quantity());

 // Bad - side effects!
 total = computed(() =&gt; {
   console.log('Computing...'); // Don't do this
   return this.price() * this.quantity();
 });
</code></pre>
</li>
<li><p><strong>Use linkedSignal for temporary overrides</strong></p>
<pre><code class="lang-tsx"> // Perfect use case
 autoSavedValue = linkedSignal(() =&gt; this.userInput());
 // User can override, but it resyncs on source change
</code></pre>
</li>
<li><p><strong>Batch signal updates</strong></p>
<pre><code class="lang-tsx"> updateMultiple() {
   // Angular batches these automatically!
   this.firstName.set('John');
   this.lastName.set('Doe');
   this.age.set(30);
   // Only one change detection cycle!
 }
</code></pre>
</li>
</ol>
<h3 id="heading-donts">Don'ts ❌</h3>
<ol>
<li><p><strong>Don't create circular dependencies</strong></p>
<pre><code class="lang-tsx"> // This will cause infinite loops!
 a = computed(() =&gt; this.b() + 1);
 b = computed(() =&gt; this.a() - 1);
</code></pre>
</li>
<li><p><strong>Don't overuse linkedSignal</strong></p>
<pre><code class="lang-tsx"> // If you never need to set(), use computed instead
 readonly = linkedSignal(() =&gt; this.source());
 // Better: readonly = computed(() =&gt; this.source());
</code></pre>
</li>
</ol>
<h3 id="heading-bonus-tip-the-signal-effect-pattern">💡 <strong>Bonus Tip: The Signal Effect Pattern</strong></h3>
<p>Here's a pattern I love for side effects with signals:</p>
<pre><code class="lang-tsx">export class NotificationComponent {
  message = signal('');

  constructor() {
    // React to signal changes with effects
    effect(() =&gt; {
      const msg = this.message();
      if (msg) {
        this.showToast(msg);
        // Auto-clear after 3 seconds
        setTimeout(() =&gt; this.message.set(''), 3000);
      }
    });
  }

  private showToast(msg: string) {
    // Your toast logic here
  }
}
</code></pre>
<hr />
<h2 id="heading-recap-your-signal-superpowers">Recap: Your Signal Superpowers 🦸‍♂️</h2>
<p>Let's wrap this up with what you've learned:</p>
<p>🎯 <strong>Computed signals</strong> are your go-to for:</p>
<ul>
<li><p>Derived values that are always in sync</p>
</li>
<li><p>Performance-critical calculations</p>
</li>
<li><p>Read-only reactive state</p>
</li>
</ul>
<p>🎯 <strong>LinkedSignals</strong> shine when you need:</p>
<ul>
<li><p>Bi-directional data flow</p>
</li>
<li><p>Manual overrides of computed values</p>
</li>
<li><p>Syncing with external sources</p>
</li>
</ul>
<p>🎯 <strong>Key takeaway:</strong> Start with <code>computed</code>. Only reach for <code>linkedSignal</code> when you actually need that write capability. Your future self (and your team) will thank you.</p>
<hr />
<h2 id="heading-lets-keep-this-conversation-going">Let's Keep This Conversation Going! 🚀</h2>
<h3 id="heading-what-did-you-think">💭 <strong>What did you think?</strong></h3>
<p>Did this clear up the computed vs linkedSignal confusion? What's your take on Angular's new reactive model? Drop a comment below—I read every single one and love the discussions that follow!</p>
<h3 id="heading-found-this-helpful">👏 <strong>Found this helpful?</strong></h3>
<p>If this saved you from a signals-induced headache (or taught you something new), smash that clap button! Seriously, even one clap makes my day—and helps other devs discover this content.</p>
<h3 id="heading-your-action-items">🎯 <strong>Your Action Items:</strong></h3>
<ol>
<li><p><strong>Try this out:</strong> Refactor one component to use signals this week</p>
</li>
<li><p><strong>Spread the knowledge:</strong> Share this with that one colleague still using RxJS for everything</p>
</li>
</ol>
<p><strong>One last question before you go:</strong></p>
<p>What Angular topic should I tackle next?</p>
<p>A) Signal-based state management patterns</p>
<p>B) Standalone components deep dive</p>
<p>C) Performance profiling in Angular 18+</p>
<p>Vote in the comments! 👇</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠🚀</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Angular Signals Not Updating? Here's Why and How to Fix It (With Real Solutions)]]></title><description><![CDATA[Ever stared at your Angular code for 20 minutes wondering why your Signal isn't updating? You're not alone. Last week, I spent an embarrassing amount of time debugging a Signal that refused to update — turns out I was mutating an array instead of cre...]]></description><link>https://hashnode.rajatmalik.dev/angular-signals-not-updating-heres-why-and-how-to-fix-it-with-real-solutions</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/angular-signals-not-updating-heres-why-and-how-to-fix-it-with-real-solutions</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 22 Oct 2025 13:30:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760764047129/fd0e58ae-4904-4978-86ea-c7ed42ef5972.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever stared at your Angular code for 20 minutes wondering why your Signal isn't updating? You're not alone. Last week, I spent an embarrassing amount of time debugging a Signal that refused to update — turns out I was mutating an array instead of creating a new one. Classic mistake, right?</p>
<p><strong>Here's the thing:</strong> Angular Signals are incredibly powerful for managing reactive state, but they come with gotchas that can drive even experienced developers crazy. The good news? Once you understand <em>why</em> Signals sometimes refuse to update, you'll never get stuck on these issues again.</p>
<p>By the end of this article, you'll:</p>
<ul>
<li><p>✅ Understand the 5 most common reasons Signals fail to update</p>
</li>
<li><p>✅ Know exactly how to debug Signal issues like a pro</p>
</li>
<li><p>✅ Have battle-tested patterns to avoid these problems entirely</p>
</li>
<li><p>✅ Get working code examples you can copy-paste right now</p>
</li>
</ul>
<p><em>Quick question before we dive in:</em> Have you ever had a Signal that just wouldn't update no matter what you tried? Drop a comment below with your weirdest Signal bug — I bet we've all been there! 💬</p>
<hr />
<h2 id="heading-the-big-problem-why-your-signals-arent-updating"><strong>The Big Problem: Why Your Signals Aren't Updating</strong></h2>
<p>Let's start with the uncomfortable truth: <strong>90% of Signal update issues come from treating them like regular variables</strong>. Signals need special care, and the Angular reactivity system has specific rules we need to follow.</p>
<p>Think of Signals like a notification system — they only notify subscribers when they detect a <em>real</em> change. And here's where things get tricky: what <em>you</em> consider a change might not be what Angular considers a change.</p>
<p>Let me show you exactly what I mean with real code...</p>
<hr />
<h2 id="heading-common-reason-1-mutating-objectsarrays-instead-of-replacing-them"><strong>🐛 Common Reason #1: Mutating Objects/Arrays Instead of Replacing Them</strong></h2>
<p>This is the number one killer of Signal updates. Let's look at a typical bug:</p>
<h3 id="heading-the-buggy-code"><strong>The Buggy Code:</strong></h3>
<pre><code class="lang-tsx">import { signal } from '@angular/core';

export class TodoComponent {
  todos = signal&lt;Todo[]&gt;([]);

  addTodo(newTodo: Todo) {
    // ❌ This WON'T trigger updates!
    this.todos().push(newTodo);
  }

  removeTodo(index: number) {
    // ❌ Also won't work!
    this.todos().splice(index, 1);
  }
}
</code></pre>
<p><strong>Why doesn't this work?</strong> You're mutating the existing array. The Signal still points to the same array reference, so Angular thinks nothing changed!</p>
<h3 id="heading-the-fix"><strong>The Fix:</strong></h3>
<pre><code class="lang-tsx">import { signal } from '@angular/core';

export class TodoComponent {
  todos = signal&lt;Todo[]&gt;([]);

  addTodo(newTodo: Todo) {
    // ✅ Create a new array reference
    this.todos.update(currentTodos =&gt; [...currentTodos, newTodo]);
  }

  removeTodo(index: number) {
    // ✅ Filter creates a new array
    this.todos.update(currentTodos =&gt;
      currentTodos.filter((_, i) =&gt; i !== index)
    );
  }

  // Alternative using set()
  addTodoAlt(newTodo: Todo) {
    // ✅ Also works!
    this.todos.set([...this.todos(), newTodo]);
  }
}
</code></pre>
<p><strong>Pro tip:</strong> Always think "immutable updates" when working with Signals. If you're using methods like <code>push()</code>, <code>pop()</code>, <code>splice()</code>, or directly modifying object properties, you're probably doing it wrong!</p>
<hr />
<h2 id="heading-common-reason-2-misusing-set-update"><strong>🐛 Common Reason #2: Misusing set(), update()</strong></h2>
<p>Angular gives us three ways to update Signals, and using the wrong one can cause issues:</p>
<h3 id="heading-understanding-the-methods"><strong>Understanding the Methods:</strong></h3>
<pre><code class="lang-tsx">import { signal } from '@angular/core';

export class UserProfileComponent {
  // For objects
  userProfile = signal({
    name: 'John',
    age: 30,
    preferences: {
      theme: 'dark',
      notifications: true
    }
  });

  // ❌ WRONG: This won't trigger updates for nested properties
  updateThemeWrong() {
    const profile = this.userProfile();
    profile.preferences.theme = 'light'; // Direct mutation!
  }

  // ✅ CORRECT: Using update() with spread operator
  updateThemeCorrect() {
    this.userProfile.update(profile =&gt; ({
      ...profile,
      preferences: {
        ...profile.preferences,
        theme: 'light'
      }
    }));
  }

  // ✅ CORRECT: Using set() with a new object
  updateThemeWithSet() {
    const currentProfile = this.userProfile();
    this.userProfile.set({
      ...currentProfile,
      preferences: {
        ...currentProfile.preferences,
        theme: 'light'
      }
    });
  }
}
</code></pre>
<p><strong>When to use each:</strong></p>
<ul>
<li><p><code>set()</code>: When you have the complete new value ready</p>
</li>
<li><p><code>update()</code>: When you need to compute the new value based on the current one</p>
</li>
</ul>
<hr />
<h2 id="heading-common-reason-3-computed-signals-with-side-effects"><strong>🐛 Common Reason #3: Computed Signals with Side Effects</strong></h2>
<p>Here's a sneaky one that catches many developers:</p>
<h3 id="heading-the-buggy-code-1"><strong>The Buggy Code:</strong></h3>
<pre><code class="lang-tsx">export class CartComponent {
  items = signal&lt;CartItem[]&gt;([]);

  // ❌ WRONG: Side effects in computed!
  totalPrice = computed(() =&gt; {
    const total = this.items().reduce((sum, item) =&gt;
      sum + item.price * item.quantity, 0
    );

    // This is a side effect - DON'T do this!
    console.log('Total calculated:', total);
    this.saveToLocalStorage(total); // Another side effect!

    return total;
  });
}
</code></pre>
<h3 id="heading-the-fix-1"><strong>The Fix:</strong></h3>
<pre><code class="lang-tsx">export class CartComponent {
  items = signal&lt;CartItem[]&gt;([]);

  // ✅ CORRECT: Pure computed signal
  totalPrice = computed(() =&gt; {
    return this.items().reduce((sum, item) =&gt;
      sum + item.price * item.quantity, 0
    );
  });

  // Use effect for side effects
  constructor() {
    effect(() =&gt; {
      const total = this.totalPrice();
      console.log('Total calculated:', total);
      this.saveToLocalStorage(total);
    });
  }
}
</code></pre>
<p><strong>Remember:</strong> Computed signals must be <strong>pure functions</strong> — no side effects allowed!</p>
<hr />
<h2 id="heading-common-reason-4-missing-dependencies-in-effects"><strong>🐛 Common Reason #4: Missing Dependencies in Effects</strong></h2>
<p>Effects can be tricky when you're not explicitly tracking all dependencies:</p>
<h3 id="heading-the-buggy-code-2"><strong>The Buggy Code:</strong></h3>
<pre><code class="lang-tsx">export class NotificationComponent {
  userId = signal&lt;number&gt;(1);
  notifications = signal&lt;Notification[]&gt;([]);

  constructor() {
    // ❌ This won't re-run when userId changes!
    const id = this.userId();
    effect(() =&gt; {
      // userId is read outside the effect
      this.loadNotifications(id);
    });
  }
}
</code></pre>
<h3 id="heading-the-fix-2"><strong>The Fix:</strong></h3>
<pre><code class="lang-tsx">export class NotificationComponent {
  userId = signal&lt;number&gt;(1);
  notifications = signal&lt;Notification[]&gt;([]);

  constructor() {
    // ✅ CORRECT: Read signals inside the effect
    effect(() =&gt; {
      const id = this.userId(); // Read inside effect!
      this.loadNotifications(id);
    });
  }
}
</code></pre>
<p><strong>Golden rule:</strong> Always read Signal values <strong>inside</strong> the effect function to ensure proper dependency tracking.</p>
<hr />
<h2 id="heading-common-reason-5-async-updates-not-handled-properly"><strong>🐛 Common Reason #5: Async Updates Not Handled Properly</strong></h2>
<p>This one's a classic — async operations need special handling:</p>
<h3 id="heading-the-buggy-code-3"><strong>The Buggy Code:</strong></h3>
<pre><code class="lang-tsx">export class DataComponent {
  data = signal&lt;any[]&gt;([]);

  async loadData() {
    // ❌ Signal might not update as expected
    this.data().push(...await this.apiService.getData());
  }
}
</code></pre>
<h3 id="heading-the-fix-3"><strong>The Fix:</strong></h3>
<pre><code class="lang-tsx">export class DataComponent {
  data = signal&lt;any[]&gt;([]);
  loading = signal&lt;boolean&gt;(false);

  async loadData() {
    this.loading.set(true);
    try {
      // ✅ CORRECT: Create new array with async data
      const newData = await this.apiService.getData();
      this.data.update(current =&gt; [...current, ...newData]);
    } finally {
      this.loading.set(false);
    }
  }

  // Even better with proper error handling
  async loadDataWithError() {
    this.loading.set(true);
    try {
      const response = await this.apiService.getData();
      this.data.set(response); // Replace entirely
    } catch (error) {
      console.error('Failed to load data:', error);
      // Handle error appropriately
    } finally {
      this.loading.set(false);
    }
  }
}
</code></pre>
<p><em>Quick check:</em> Which of these issues have you run into? I'm curious — drop a comment with the number (1-5) that's bitten you the most! 👇</p>
<hr />
<h2 id="heading-debugging-signals-like-a-pro"><strong>🔍 Debugging Signals Like a Pro</strong></h2>
<p>When things aren't working, here's your debugging toolkit:</p>
<h3 id="heading-1-effect-logging-pattern"><strong>1. Effect Logging Pattern</strong></h3>
<pre><code class="lang-tsx">export class DebugComponent {
  mySignal = signal({ count: 0, name: 'test' });

  constructor() {
    // Debug effect - remove in production!
    effect(() =&gt; {
      console.log('🔄 Signal updated:', {
        value: this.mySignal(),
        timestamp: new Date().toISOString()
      });
    });
  }
}
</code></pre>
<h3 id="heading-2-custom-debug-wrapper"><strong>2. Custom Debug Wrapper</strong></h3>
<pre><code class="lang-tsx">// Create a debug signal wrapper
function debugSignal&lt;T&gt;(initialValue: T, name: string) {
  const sig = signal(initialValue);

  // Wrap the update method
  const originalUpdate = sig.update.bind(sig);
  sig.update = (updateFn: (value: T) =&gt; T) =&gt; {
    const oldValue = sig();
    const result = originalUpdate(updateFn);
    const newValue = sig();
    console.log(`📊 ${name} updated:`, { oldValue, newValue });
    return result;
  };

  return sig;
}

// Usage
export class MyComponent {
  todos = debugSignal&lt;Todo[]&gt;([], 'todos');
}
</code></pre>
<h3 id="heading-3-angular-devtools-integration"><strong>3. Angular DevTools Integration</strong></h3>
<pre><code class="lang-tsx">export class DevToolsComponent {
  // Make signals visible in DevTools
  readonly debugState = computed(() =&gt; ({
    todos: this.todos(),
    filter: this.filter(),
    filtered: this.filteredTodos(),
    timestamp: Date.now()
  }));
}
</code></pre>
<hr />
<h2 id="heading-real-world-example-todo-list-with-common-pitfalls"><strong>📝 Real-World Example: Todo List with Common Pitfalls</strong></h2>
<p>Let's put it all together with a real example that shows both problems and solutions:</p>
<pre><code class="lang-tsx">import { Component, signal, computed, effect } from '@angular/core';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
  createdAt: Date;
}

@Component({
  selector: 'app-todo-list',
  template: `
    &lt;div class="todo-container"&gt;
      &lt;input #todoInput (keyup.enter)="addTodo(todoInput.value); todoInput.value=''" /&gt;

      &lt;div class="filters"&gt;
        &lt;button (click)="filter.set('all')"
                [class.active]="filter() === 'all'"&gt;All&lt;/button&gt;
        &lt;button (click)="filter.set('active')"
                [class.active]="filter() === 'active'"&gt;Active&lt;/button&gt;
        &lt;button (click)="filter.set('completed')"
                [class.active]="filter() === 'completed'"&gt;Completed&lt;/button&gt;
      &lt;/div&gt;

      &lt;ul&gt;
        @for (todo of filteredTodos(); track todo.id) {
          &lt;li [class.completed]="todo.completed"&gt;
            &lt;input type="checkbox"
                   [checked]="todo.completed"
                   (change)="toggleTodo(todo.id)"&gt;
            &lt;span&gt;{{ todo.text }}&lt;/span&gt;
            &lt;button (click)="removeTodo(todo.id)"&gt;❌&lt;/button&gt;
          &lt;/li&gt;
        }
      &lt;/ul&gt;

      &lt;div class="stats"&gt;
        Active: {{ stats().active }} |
        Completed: {{ stats().completed }} |
        Total: {{ stats().total }}
      &lt;/div&gt;
    &lt;/div&gt;
  `
})
export class TodoListComponent {
  // Signals for state
  todos = signal&lt;Todo[]&gt;([]);
  filter = signal&lt;'all' | 'active' | 'completed'&gt;('all');
  nextId = signal(1);

  // Computed signals (pure, no side effects!)
  filteredTodos = computed(() =&gt; {
    const currentFilter = this.filter();
    const allTodos = this.todos();

    switch (currentFilter) {
      case 'active':
        return allTodos.filter(t =&gt; !t.completed);
      case 'completed':
        return allTodos.filter(t =&gt; t.completed);
      default:
        return allTodos;
    }
  });

  stats = computed(() =&gt; {
    const allTodos = this.todos();
    return {
      total: allTodos.length,
      active: allTodos.filter(t =&gt; !t.completed).length,
      completed: allTodos.filter(t =&gt; t.completed).length
    };
  });

  constructor() {
    // Effect for side effects (localStorage persistence)
    effect(() =&gt; {
      const todosToSave = this.todos();
      if (todosToSave.length &gt; 0) {
        localStorage.setItem('todos', JSON.stringify(todosToSave));
      }
    });

    // Load initial data
    this.loadFromStorage();
  }

  addTodo(text: string) {
    if (!text.trim()) return;

    const newTodo: Todo = {
      id: this.nextId(),
      text: text.trim(),
      completed: false,
      createdAt: new Date()
    };

    // ✅ CORRECT: Using update to create new array
    this.todos.update(current =&gt; [...current, newTodo]);
    this.nextId.update(id =&gt; id + 1);
  }

  toggleTodo(id: number) {
    // ✅ CORRECT: Creating new array with updated object
    this.todos.update(todos =&gt;
      todos.map(todo =&gt;
        todo.id === id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    );
  }

  removeTodo(id: number) {
    // ✅ CORRECT: Filter creates new array
    this.todos.update(todos =&gt; todos.filter(t =&gt; t.id !== id));
  }

  private loadFromStorage() {
    const stored = localStorage.getItem('todos');
    if (stored) {
      try {
        const parsed = JSON.parse(stored);
        this.todos.set(parsed);
        const maxId = Math.max(...parsed.map((t: Todo) =&gt; t.id), 0);
        this.nextId.set(maxId + 1);
      } catch (e) {
        console.error('Failed to load todos:', e);
      }
    }
  }
}
</code></pre>
<hr />
<h2 id="heading-unit-testing-your-signals"><strong>🧪 Unit Testing Your Signals</strong></h2>
<p>Don't forget to test! Here's how to properly test Signal-based components:</p>
<pre><code class="lang-tsx">import { TestBed } from '@angular/core/testing';
import { signal, effect } from '@angular/core';

describe('TodoListComponent', () =&gt; {
  let component: TodoListComponent;

  beforeEach(() =&gt; {
    TestBed.configureTestingModule({
      imports: [TodoListComponent]
    });
    component = TestBed.createComponent(TodoListComponent).componentInstance;
  });

  it('should add a new todo', () =&gt; {
    const initialCount = component.todos().length;

    component.addTodo('Test todo');

    expect(component.todos().length).toBe(initialCount + 1);
    expect(component.todos()[initialCount].text).toBe('Test todo');
  });

  it('should filter todos correctly', () =&gt; {
    // Setup
    component.todos.set([
      { id: 1, text: 'Active', completed: false, createdAt: new Date() },
      { id: 2, text: 'Completed', completed: true, createdAt: new Date() }
    ]);

    // Test active filter
    component.filter.set('active');
    expect(component.filteredTodos().length).toBe(1);
    expect(component.filteredTodos()[0].text).toBe('Active');

    // Test completed filter
    component.filter.set('completed');
    expect(component.filteredTodos().length).toBe(1);
    expect(component.filteredTodos()[0].text).toBe('Completed');
  });

  it('should update stats when todos change', () =&gt; {
    component.todos.set([
      { id: 1, text: 'Task 1', completed: false, createdAt: new Date() },
      { id: 2, text: 'Task 2', completed: true, createdAt: new Date() },
      { id: 3, text: 'Task 3', completed: false, createdAt: new Date() }
    ]);

    const stats = component.stats();
    expect(stats.total).toBe(3);
    expect(stats.active).toBe(2);
    expect(stats.completed).toBe(1);
  });

  it('should handle async operations correctly', (done) =&gt; {
    // Mock async operation
    spyOn(component, 'loadFromStorage').and.callFake(async () =&gt; {
      await new Promise(resolve =&gt; setTimeout(resolve, 100));
      component.todos.set([
        { id: 1, text: 'Loaded', completed: false, createdAt: new Date() }
      ]);
    });

    component.loadFromStorage().then(() =&gt; {
      expect(component.todos().length).toBe(1);
      expect(component.todos()[0].text).toBe('Loaded');
      done();
    });
  });
});
</code></pre>
<hr />
<h2 id="heading-best-practices-to-avoid-signal-update-issues"><strong>✨ Best Practices to Avoid Signal Update Issues</strong></h2>
<p>After debugging countless Signal issues, here are my battle-tested best practices:</p>
<h3 id="heading-1-always-use-immutable-updates"><strong>1. Always Use Immutable Updates</strong></h3>
<pre><code class="lang-tsx">// ✅ DO THIS
signal.update(arr =&gt; [...arr, newItem]);
signal.update(obj =&gt; ({ ...obj, newProp: value }));

// ❌ NOT THIS
signal().push(newItem);
signal().newProp = value;
</code></pre>
<h3 id="heading-2-keep-computed-signals-pure"><strong>2. Keep Computed Signals Pure</strong></h3>
<pre><code class="lang-tsx">// ✅ DO THIS
totalPrice = computed(() =&gt;
  this.items().reduce((sum, item) =&gt; sum + item.price, 0)
);

// ❌ NOT THIS
totalPrice = computed(() =&gt; {
  const total = this.items().reduce((sum, item) =&gt; sum + item.price, 0);
  this.saveToDatabase(total); // Side effect!
  return total;
});
</code></pre>
<h3 id="heading-3-use-effects-wisely-for-debugging"><strong>3. Use Effects Wisely for Debugging</strong></h3>
<pre><code class="lang-tsx">// Development debugging
constructor() {
  if (!environment.production) {
    effect(() =&gt; {
      console.log('State changed:', {
        todos: this.todos(),
        filter: this.filter()
      });
    });
  }
}
</code></pre>
<h3 id="heading-4-create-helper-functions-for-complex-updates"><strong>4. Create Helper Functions for Complex Updates</strong></h3>
<pre><code class="lang-tsx">// Instead of complex inline updates
private updateNestedProperty&lt;T&gt;(
  signal: WritableSignal&lt;T&gt;,
  path: string[],
  value: any
): void {
  signal.update(current =&gt; {
    const updated = JSON.parse(JSON.stringify(current)); // Deep clone
    let ref = updated;
    for (let i = 0; i &lt; path.length - 1; i++) {
      ref = ref[path[i]];
    }
    ref[path[path.length - 1]] = value;
    return updated;
  });
}
</code></pre>
<h3 id="heading-5-document-your-signal-patterns"><strong>5. Document Your Signal Patterns</strong></h3>
<pre><code class="lang-tsx">/**
 * Cart state management using Signals
 * - items: Array of cart items (immutable updates only!)
 * - total: Computed from items (pure function)
 * - Effects: Persist to localStorage, sync with backend
 */
export class CartService {
  // ... your code
}
</code></pre>
<hr />
<h2 id="heading-bonus-tips"><strong>💡 Bonus Tips</strong></h2>
<p>Here are some extra nuggets of wisdom I've learned the hard way:</p>
<ol>
<li><p><strong>Use Signal Inputs for Components</strong> (Angular 17.1+):</p>
<pre><code class="lang-tsx"> export class ProductCard {
   // Instead of @Input()
   product = input.required&lt;Product&gt;();
   quantity = input(1);
 }
</code></pre>
</li>
<li><p><strong>Batch Updates for Performance</strong>:</p>
<pre><code class="lang-tsx"> // Multiple updates in one go
 batch(() =&gt; {
   this.todos.update(/* ... */);
   this.filter.set('all');
   this.loading.set(false);
 });
</code></pre>
</li>
<li><p><strong>Create Custom Signal Factories</strong>:</p>
<pre><code class="lang-tsx"> function createAsyncSignal&lt;T&gt;(initialValue: T) {
   const data = signal(initialValue);
   const loading = signal(false);
   const error = signal&lt;Error | null&gt;(null);

   return { data, loading, error };
 }
</code></pre>
</li>
</ol>
<hr />
<h2 id="heading-recap-your-signal-debugging-checklist"><strong>🎯 Recap: Your Signal Debugging Checklist</strong></h2>
<p>Let's wrap this up with a quick checklist you can bookmark:</p>
<p>✅ <strong>Not updating?</strong> Check if you're mutating instead of replacing</p>
<p>✅ <strong>Using update/set correctly?</strong> Remember the difference</p>
<p>✅ <strong>Computed not working?</strong> Remove side effects</p>
<p>✅ <strong>Effect not triggering?</strong> Read signals inside the effect</p>
<p>✅ <strong>Async issues?</strong> Handle promises properly with try/catch</p>
<p>✅ <strong>Still stuck?</strong> Add debug effects to trace changes</p>
<p>The truth is, 99% of Signal update issues come down to one thing: <strong>treating Signals like regular variables</strong>. Once you internalize that Signals need immutable updates and proper method usage, these problems disappear.</p>
<hr />
<h2 id="heading-your-turn"><strong>💬 Your Turn!</strong></h2>
<p>Alright, I've shared my Signal debugging war stories — now I want to hear yours!</p>
<p><strong>Here's my challenge for you:</strong></p>
<ol>
<li><p><strong>What's the weirdest Signal bug you've encountered?</strong> Drop it in the comments — I'll personally reply with a solution if you're still stuck! 👇</p>
</li>
<li><p><strong>Which of these 5 issues surprised you the most?</strong> Let me know — I'm genuinely curious which ones catch developers off guard.</p>
</li>
<li><p><strong>Got a different approach to handling Signals?</strong> Share it! The best part about our dev community is learning from each other.</p>
</li>
</ol>
<hr />
<h3 id="heading-want-to-level-up-your-angular-skills"><strong>🚀 Want to Level Up Your Angular Skills?</strong></h3>
<p>If this helped you squash that annoying Signal bug, here's how you can help me and get more content like this:</p>
<p>👏 <strong>Hit that clap button</strong> (you can clap up to 50 times — just saying 😉). It helps other devs discover these solutions!</p>
<p>📬 <strong>Follow me for more Angular deep dives</strong> — I publish practical tutorials every week, always with working code you can use immediately.</p>
<p>💌 <strong>Join my newsletter</strong> where I share exclusive tips, early access to articles, and answer reader questions directly. No spam, just solid dev content.</p>
<p>🔥 <strong>Bookmark this article</strong> — trust me, you'll need this reference when debugging Signals at 11 PM on a Friday (we've all been there).</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠🚀</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Angular Signals: 5+ Critical Mistakes That Could Break Your App (And How to Fix Them)]]></title><description><![CDATA[Angular Signals have revolutionized how we handle reactive state in our applications, but with great power comes great responsibility. As more developers adopt this powerful feature, I've noticed some patterns that could spell trouble for your app's ...]]></description><link>https://hashnode.rajatmalik.dev/angular-signals-5-critical-mistakes-that-could-break-your-app-and-how-to-fix-them</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/angular-signals-5-critical-mistakes-that-could-break-your-app-and-how-to-fix-them</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[Angular]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[signals]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 21 Oct 2025 13:30:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760763128668/81dd722a-7c7f-4184-add5-3ca00cb6f0f6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Angular Signals have revolutionized how we handle reactive state in our applications, but with great power comes great responsibility. As more developers adopt this powerful feature, I've noticed some patterns that could spell trouble for your app's performance and maintainability.</p>
<p><strong>Here's the thing:</strong> Even experienced Angular developers are making these mistakes. I've seen production apps crash, performance tank, and developers scratch their heads wondering why their "modern" code isn't working as expected.</p>
<p>In this article, you'll discover the <strong>5 most common (and dangerous) mistakes</strong> developers make with Angular Signals, plus 3 bonus pitfalls that could save you hours of debugging. By the end, you'll have actionable strategies to write cleaner, more performant Angular code.</p>
<p><em>💡 Quick win: Hit that 👏 if you've ever been confused about when to use Signals vs Observables - you're not alone!</em></p>
<hr />
<h2 id="heading-mistake-1-not-using-signals-for-dynamic-state-the-future-proofing-killer">Mistake #1: Not Using Signals for Dynamic State (The Future-Proofing Killer)</h2>
<p><strong>The Problem:</strong> You're still using traditional properties for state that changes during your app's lifecycle.</p>
<p>Here's what I see way too often:</p>
<pre><code class="lang-tsx">@Component({
  template: `
    &lt;div&gt;{{ userStatus }}&lt;/div&gt;
    &lt;button (click)="updateStatus('active')"&gt;Set Active&lt;/button&gt;
  `
})
export class UserComponent {
  userStatus = 'inactive'; // ❌ This will bite you later

  updateStatus(newStatus: string) {
    this.userStatus = newStatus; // ❌ No change detection optimization
  }
}
</code></pre>
<p><strong>Why this hurts:</strong> When Angular rolls out Signal Components (and they will), this code will break. Your UI won't update because Angular won't know the state changed.</p>
<p><strong>The Fix:</strong></p>
<pre><code class="lang-tsx">@Component({
  template: `
    &lt;div&gt;{{ userStatus() }}&lt;/div&gt;
    &lt;button (click)="updateStatus('active')"&gt;Set Active&lt;/button&gt;
  `
})
export class UserComponent {
  userStatus = signal('inactive'); // ✅ Future-proof

  updateStatus(newStatus: string) {
    this.userStatus.set(newStatus); // ✅ Angular knows what changed
  }
}
</code></pre>
<p><strong>Pro tip:</strong> Even if you're updating from a subscription, you won't need <code>markForCheck()</code> anymore! Angular's change detection becomes laser-focused on what actually changed.</p>
<p><em>👇 Have you been caught off-guard by change detection issues? Drop a comment below!</em></p>
<hr />
<h2 id="heading-mistake-2-signal-overuse-when-more-isnt-better">Mistake #2: Signal Overuse (When More Isn't Better)</h2>
<p><strong>The Problem:</strong> Using signals for everything, even when they're not rendered or don't need reactivity.</p>
<p>I've seen developers go signal-crazy:</p>
<pre><code class="lang-tsx">@Component({})
export class OverEnthusiasticComponent {
  private isFirstClick = signal(true); // ❌ Overkill
  private debugMode = signal(false); // ❌ Unnecessary overhead

  handleClick() {
    if (this.isFirstClick()) {
      this.isFirstClick.set(false);
      return;
    }
    // Handle subsequent clicks
  }
}
</code></pre>
<p><strong>Why this matters:</strong> You're adding unnecessary reactive overhead for values that never need to trigger UI updates.</p>
<p><strong>The Fix:</strong></p>
<pre><code class="lang-tsx">@Component({})
export class SmartComponent {
  #isFirstClick = true; // ✅ Simple and efficient
  #debugMode = false; // ✅ Private and lightweight

  handleClick() {
    if (this.#isFirstClick) {
      this.#isFirstClick = false;
      return;
    }
    // Handle subsequent clicks
  }
}
</code></pre>
<p><strong>Rule of thumb:</strong> If it's not rendered in the template and doesn't need reactivity, keep it simple.</p>
<p><em>📬 Want more performance tips? Subscribe to my newsletter for weekly Angular insights!</em></p>
<hr />
<h2 id="heading-mistake-3-unnecessary-signal-conversions-the-verbose-trap">Mistake #3: Unnecessary Signal Conversions (The Verbose Trap)</h2>
<p><strong>The Problem:</strong> Converting Observables to Signals just to use them in effects, when you could work with the Observable directly.</p>
<p>Here's the verbose approach I see too often:</p>
<pre><code class="lang-tsx">@Component({})
export class VerboseComponent {
  form = new FormGroup({
    email: new FormControl(''),
    password: new FormControl('')
  });

  constructor() {
    // ❌ Unnecessary conversion
    const formValue = toSignal(this.form.valueChanges);

    effect(() =&gt; {
      const value = formValue();
      // Handle form changes
      console.log('Form changed:', value);
    });
  }
}
</code></pre>
<p><strong>The cleaner approach:</strong></p>
<pre><code class="lang-tsx">@Component({})
export class CleanComponent {
  form = new FormGroup({
    email: new FormControl(''),
    password: new FormControl('')
  });

  constructor() {
    // ✅ Direct and predictable
    this.form.valueChanges.pipe(
      takeUntilDestroyed()
    ).subscribe(value =&gt; {
      // Handle form changes
      console.log('Form changed:', value);
    });
  }
}
</code></pre>
<p><strong>Key insight:</strong> Effects run during change detection, while Observables run immediately. Choose based on your timing needs.</p>
<hr />
<h2 id="heading-mistake-4-unanalyzed-effect-dependencies-the-hidden-subscription-bomb">Mistake #4: Unanalyzed Effect Dependencies (The Hidden Subscription Bomb)</h2>
<p><strong>The Problem:</strong> Writing effects without considering what signals they're actually tracking.</p>
<p>This innocent-looking code can cause performance nightmares:</p>
<pre><code class="lang-tsx">@Component({})
export class ProblematicComponent {
  #userStore = inject(UserStore);

  constructor() {
    effect(() =&gt; {
      const currentAccount = this.#userStore.currentAccount();
      const defaultAccount = this.#userStore.defaultAccount();
      const activeAccount = currentAccount || defaultAccount;

      if (!activeAccount) return;

      // ❌ This might run twice for the same account!
      this.fetchAccountData(activeAccount);
    });
  }
}
</code></pre>
<p><strong>What happens:</strong> If both <code>currentAccount</code> and <code>defaultAccount</code> change to the same value, your effect runs twice, making duplicate API calls.</p>
<p><strong>The optimized solution:</strong></p>
<pre><code class="lang-tsx">@Component({})
export class OptimizedComponent {
  #userStore = inject(UserStore);

  constructor() {
    // ✅ Single source of truth
    const activeAccount = computed(() =&gt;
      this.#userStore.currentAccount() || this.#userStore.defaultAccount()
    );

    effect(() =&gt; {
      const account = activeAccount();
      if (!account) return;

      // ✅ Runs only when the actual active account changes
      this.fetchAccountData(account);
    });
  }
}
</code></pre>
<p><strong>Pro technique:</strong> Use <code>explicitEffect</code> from ngxtension for maximum control:</p>
<pre><code class="lang-tsx">import { explicitEffect } from 'ngxtension/explicit-effect';

constructor() {
  const activeAccount = computed(() =&gt;
    this.#userStore.currentAccount() || this.#userStore.defaultAccount()
  );

  // ✅ Crystal clear dependencies
  explicitEffect([activeAccount], ([account]) =&gt; {
    if (!account) return;
    this.fetchAccountData(account);
  });
}
</code></pre>
<p><em>💬 Have you been bitten by unexpected effect runs? Share your story in the comments!</em></p>
<hr />
<h2 id="heading-mistake-5-mixing-async-pipes-with-signals-the-inconsistency-problem">Mistake #5: Mixing Async Pipes with Signals (The Inconsistency Problem)</h2>
<p><strong>The Problem:</strong> Using async pipes in a Signal-heavy codebase creates inconsistency and missed opportunities.</p>
<p>Mixed approach:</p>
<pre><code class="lang-tsx">@Component({
  template: `
    &lt;!-- ❌ Mixing paradigms --&gt;
    @for (item of items$ | async; track item.id) {
      &lt;div&gt;{{ item.name }}&lt;/div&gt;
    }

    &lt;!-- ✅ Consistent signal usage --&gt;
    @for (item of items(); track item.id) {
      &lt;div&gt;{{ item.name }}&lt;/div&gt;
    }
  `
})
export class InconsistentComponent {
  #service = inject(DataService);

  // ❌ Requires async pipe, harder to access in TS
  items$ = this.#service.getData();

  // ✅ Easy synchronous access, no async pipe needed
  items = toSignal(this.#service.getData(), { initialValue: [] });
}
</code></pre>
<p><strong>Benefits of the Signal approach:</strong></p>
<ul>
<li><p>Smaller bundle size (no async pipe)</p>
</li>
<li><p>Synchronous access to current state</p>
</li>
<li><p>Consistent reactive patterns</p>
</li>
<li><p>Better TypeScript integration</p>
</li>
</ul>
<hr />
<h2 id="heading-bonus-mistake-6-ignoring-signal-equality-the-unnecessary-render-trap">Bonus Mistake #6: Ignoring Signal Equality (The Unnecessary Render Trap)</h2>
<p><strong>The Problem:</strong> Not understanding how Signal equality works, leading to unnecessary re-renders.</p>
<pre><code class="lang-tsx">@Component({})
export class InefficientComponent {
  users = signal&lt;User[]&gt;([]);

  addUser(user: User) {
    // ❌ Always creates new array reference
    this.users.set([...this.users(), user]);
  }

  updateUser(id: string, updates: Partial&lt;User&gt;) {
    // ❌ Creates new array even if user wasn't found
    this.users.set(
      this.users().map(u =&gt; u.id === id ? { ...u, ...updates } : u)
    );
  }
}
</code></pre>
<p><strong>The optimized approach:</strong></p>
<pre><code class="lang-tsx">@Component({})
export class EfficientComponent {
  users = signal&lt;User[]&gt;([]);

  addUser(user: User) {
    // ✅ Only update if user is actually new
    if (this.users().find(u =&gt; u.id === user.id)) return;
    this.users.update(users =&gt; [...users, user]);
  }

  updateUser(id: string, updates: Partial&lt;User&gt;) {
    // ✅ Only update if user exists and changes
    this.users.update(users =&gt; {
      const index = users.findIndex(u =&gt; u.id === id);
      if (index === -1) return users; // No change

      const updatedUser = { ...users[index], ...updates };
      if (JSON.stringify(updatedUser) === JSON.stringify(users[index])) {
        return users; // No actual change
      }

      const newUsers = [...users];
      newUsers[index] = updatedUser;
      return newUsers;
    });
  }
}
</code></pre>
<hr />
<h2 id="heading-bonus-mistake-7-not-using-computed-signals-for-derived-state">Bonus Mistake #7: Not Using Computed Signals for Derived State</h2>
<p><strong>The Problem:</strong> Manually managing derived state instead of letting Angular handle it.</p>
<pre><code class="lang-tsx">@Component({})
export class ManualComponent {
  firstName = signal('');
  lastName = signal('');
  fullName = signal(''); // ❌ Manual management

  updateFirstName(name: string) {
    this.firstName.set(name);
    this.fullName.set(`${name} ${this.lastName()}`); // ❌ Manual sync
  }

  updateLastName(name: string) {
    this.lastName.set(name);
    this.fullName.set(`${this.firstName()} ${name}`); // ❌ Manual sync
  }
}
</code></pre>
<p><strong>The reactive approach:</strong></p>
<pre><code class="lang-tsx">@Component({})
export class ReactiveComponent {
  firstName = signal('');
  lastName = signal('');

  // ✅ Automatically updates when dependencies change
  fullName = computed(() =&gt; `${this.firstName()} ${this.lastName()}`);

  updateFirstName(name: string) {
    this.firstName.set(name);
    // ✅ fullName updates automatically
  }

  updateLastName(name: string) {
    this.lastName.set(name);
    // ✅ fullName updates automatically
  }
}
</code></pre>
<p><em>👏 If this helped you avoid a potential bug, give it a clap!</em></p>
<hr />
<h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>Angular Signals are powerful, but they require a shift in thinking. The key is knowing <strong>when</strong> to use them, <strong>how</strong> to use them efficiently, and <strong>what</strong> to avoid.</p>
<p><strong>Quick recap of the main pitfalls:</strong></p>
<ol>
<li><p><strong>Not using Signals for dynamic state</strong> - Future-proof your code</p>
</li>
<li><p><strong>Overusing Signals</strong> - Keep non-reactive state simple</p>
</li>
<li><p><strong>Unnecessary conversions</strong> - Don't convert just to convert</p>
</li>
<li><p><strong>Unanalyzed effect dependencies</strong> - Think about what you're tracking</p>
</li>
<li><p><strong>Mixing async pipes with Signals</strong> - Stay consistent</p>
</li>
<li><p><strong>Ignoring Signal equality</strong> - Optimize your updates</p>
</li>
<li><p><strong>Manual derived state</strong> - Let computed signals do the work</p>
</li>
</ol>
<hr />
<h2 id="heading-whats-next">What's Next?</h2>
<p><strong>What did you think?</strong> Which mistake surprised you the most? Have you encountered any of these in your own projects? Drop a comment below and let's discuss! 💬</p>
<p><strong>Found this helpful?</strong> Hit that 👏 button - it helps other developers discover these tips and avoid the same pitfalls.</p>
<p><strong>Want more Angular insights like this?</strong> Follow me for weekly deep-dives into Angular best practices, performance tips, and the latest framework updates. I also send out a newsletter with exclusive content and early access to new articles. 📬</p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠🚀</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Master Angular Performance: 10 Essential Lazy Loading, Route Guards & Resolvers Techniques Every Developer Must Know]]></title><description><![CDATA[Have you ever wondered why some Angular applications load lightning-fast while others feel sluggish and unresponsive?
The secret lies in mastering three fundamental Angular concepts that separate amateur developers from seasoned professionals: Lazy L...]]></description><link>https://hashnode.rajatmalik.dev/master-angular-performance-10-essential-lazy-loading-route-guards-and-resolvers-techniques-every-developer-must-know</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/master-angular-performance-10-essential-lazy-loading-route-guards-and-resolvers-techniques-every-developer-must-know</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Wed, 15 Oct 2025 13:30:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760173411961/be069c1c-c6eb-46e6-becd-62f449e205d6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h3 id="heading-have-you-ever-wondered-why-some-angular-applications-load-lightning-fast-while-others-feel-sluggish-and-unresponsive">Have you ever wondered why some Angular applications load lightning-fast while others feel sluggish and unresponsive?</h3>
<p>The secret lies in mastering three fundamental Angular concepts that separate amateur developers from seasoned professionals: <strong>Lazy Loading</strong>, <strong>Route Guards</strong>, and <strong>Resolvers</strong>. These aren’t just fancy terms thrown around in Angular documentation — they’re your weapons against slow load times, security vulnerabilities, and poor user experience.</p>
<p>Picture this: You’ve built an amazing Angular application with dozens of features, but users are abandoning it before it even loads. Sound familiar? You’re not alone. According to recent studies, 53% of users abandon mobile sites that take longer than 3 seconds to load. That’s where smart routing strategies come into play.</p>
<p><strong>By the end of this comprehensive guide, you’ll master:</strong></p>
<ul>
<li><p>Advanced lazy loading techniques that cut initial bundle size by up to 70%</p>
</li>
<li><p>Bulletproof route guards that secure your application like Fort Knox</p>
</li>
<li><p>Data resolvers that eliminate loading spinners and improve UX</p>
</li>
<li><p>Performance optimization strategies used by top-tier Angular developers</p>
</li>
<li><p>Real-world implementation examples you can use immediately</p>
</li>
</ul>
<p>Ready to transform your Angular skills from good to exceptional? Let’s dive deep into the Angular routing maze and emerge as navigation masters.</p>
<hr />
<h3 id="heading-1-smart-lazy-loading-load-only-what-you-need">1. Smart Lazy Loading: Load Only What You Need</h3>
<p>Lazy loading is like having a smart waiter who only brings you the course you’re ready to eat, not the entire menu at once.</p>
<h3 id="heading-the-problem-traditional-loading-creates">The Problem Traditional Loading Creates</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Traditional eager loading loads EVERYTHING upfront</span>
<span class="hljs-keyword">const</span> routes: Routes = [
  { path: <span class="hljs-string">'dashboard'</span>, component: DashboardComponent },
  { path: <span class="hljs-string">'users'</span>, component: UsersComponent },
  { path: <span class="hljs-string">'products'</span>, component: ProductsComponent },
  { path: <span class="hljs-string">'analytics'</span>, component: AnalyticsComponent },
  <span class="hljs-comment">// All components loaded immediately = SLOW</span>
];
</code></pre>
<h3 id="heading-the-lazy-loading-solution">The Lazy Loading Solution</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Smart lazy loading - Load modules on demand</span>
<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'dashboard'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./dashboard/dashboard.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.DashboardModule)
  },
  {
    path: <span class="hljs-string">'users'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./users/users.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.UsersModule)
  },
  {
    path: <span class="hljs-string">'products'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./products/products.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.ProductsModule)
  }
];
</code></pre>
<h3 id="heading-pro-tip-feature-module-structure">Pro Tip: Feature Module Structure</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// users-routing.module.ts</span>
<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">''</span>,
    component: UsersComponent,
    children: [
      {
        path: <span class="hljs-string">'profile/:id'</span>,
        loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./profile/profile.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.ProfileModule)
      },
      {
        path: <span class="hljs-string">'settings'</span>,
        loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./settings/settings.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.SettingsModule)
      }
    ]
  }
];

<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forChild(routes)],
  <span class="hljs-built_in">exports</span>: [RouterModule]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UsersRoutingModule { }
</code></pre>
<p><strong>Impact:</strong> This approach can reduce your initial bundle size by 50–70%, dramatically improving first-page load times.</p>
<hr />
<h3 id="heading-2-route-guards-your-applications-security-checkpoint">2. Route Guards: Your Application’s Security Checkpoint</h3>
<p>Think of route guards as bouncer at an exclusive club — they decide who gets in and who doesn’t.</p>
<h3 id="heading-canactivate-the-gatekeeper">CanActivate: The Gatekeeper</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// auth.guard.ts</span>
<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AuthGuard <span class="hljs-keyword">implements</span> CanActivate {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> authService: AuthService,
    <span class="hljs-keyword">private</span> router: Router,
    <span class="hljs-keyword">private</span> snackBar: MatSnackBar
  </span>) {}

  canActivate(
      route: ActivatedRouteSnapshot,
      state: RouterStateSnapshot
    ): Observable&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">boolean</span> {
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.authService.isAuthenticated()) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
      <span class="hljs-comment">// Store the attempted URL for redirecting after login</span>
      <span class="hljs-built_in">this</span>.authService.setRedirectUrl(state.url);
      <span class="hljs-built_in">this</span>.snackBar.open(<span class="hljs-string">'Please log in to access this page'</span>, <span class="hljs-string">'Close'</span>, {
        duration: <span class="hljs-number">3000</span>,
        panelClass: [<span class="hljs-string">'warning-snackbar'</span>]
      });
      <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/login'</span>]);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
  }
</code></pre>
<h3 id="heading-canactivatechild-protecting-child-routes">CanActivateChild: Protecting Child Routes</h3>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AdminGuard <span class="hljs-keyword">implements</span> CanActivateChild {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> userService: UserService</span>) {}
  canActivateChild(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">const</span> userRole = <span class="hljs-built_in">this</span>.userService.getCurrentUserRole();
    <span class="hljs-keyword">const</span> requiredRole = childRoute.data[<span class="hljs-string">'requiredRole'</span>];
    <span class="hljs-keyword">if</span> (userRole === <span class="hljs-string">'admin'</span> || userRole === requiredRole) {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`Access denied. Required role: <span class="hljs-subst">${requiredRole}</span>, User role: <span class="hljs-subst">${userRole}</span>`</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
}
</code></pre>
<h3 id="heading-candeactivate-preventing-data-loss">CanDeactivate: Preventing Data Loss</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// unsaved-changes.guard.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CanComponentDeactivate {
  canDeactivate: <span class="hljs-function">() =&gt;</span> Observable&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UnsavedChangesGuard <span class="hljs-keyword">implements</span> CanDeactivate&lt;CanComponentDeactivate&gt; {
  canDeactivate(
    component: CanComponentDeactivate,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState?: RouterStateSnapshot
  ): Observable&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">return</span> component.canDeactivate ? component.canDeactivate() : <span class="hljs-literal">true</span>;
  }
}
<span class="hljs-comment">// Implementation in component</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UserFormComponent <span class="hljs-keyword">implements</span> CanComponentDeactivate {
  hasUnsavedChanges = <span class="hljs-literal">false</span>;
  canDeactivate(): Observable&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">boolean</span>&gt; | <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.hasUnsavedChanges) {
      <span class="hljs-keyword">return</span> confirm(<span class="hljs-string">'You have unsaved changes. Are you sure you want to leave?'</span>);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  }
}
</code></pre>
<hr />
<h3 id="heading-3-resolvers-pre-load-data-like-a-pro">3. Resolvers: Pre-load Data Like a Pro</h3>
<p>Resolvers are like personal assistants who fetch everything you need before you even ask.</p>
<h3 id="heading-basic-data-resolver">Basic Data Resolver</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// user-resolver.service.ts</span>
<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> UserResolver <span class="hljs-keyword">implements</span> Resolve&lt;User&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> userService: UserService,
    <span class="hljs-keyword">private</span> router: Router
  </span>) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable&lt;User&gt; | <span class="hljs-built_in">Promise</span>&lt;User&gt; | User {
    <span class="hljs-keyword">const</span> userId = route.paramMap.get(<span class="hljs-string">'id'</span>);
    <span class="hljs-keyword">if</span> (!userId) {
      <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/users'</span>]);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.userService.getUserById(userId).pipe(
      catchError(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error loading user:'</span>, error);
        <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/users'</span>]);
        <span class="hljs-keyword">return</span> EMPTY;
      })
    );
  }
}
</code></pre>
<h3 id="heading-advanced-multi-data-resolver">Advanced Multi-Data Resolver</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// dashboard-resolver.service.ts</span>
<span class="hljs-keyword">interface</span> DashboardData {
  user: User;
  stats: DashboardStats;
  notifications: Notification[];
}

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DashboardResolver <span class="hljs-keyword">implements</span> Resolve&lt;DashboardData&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> userService: UserService,
    <span class="hljs-keyword">private</span> statsService: StatsService,
    <span class="hljs-keyword">private</span> notificationService: NotificationService
  </span>) {}
  resolve(): Observable&lt;DashboardData&gt; {
    <span class="hljs-keyword">return</span> forkJoin({
      user: <span class="hljs-built_in">this</span>.userService.getCurrentUser(),
      stats: <span class="hljs-built_in">this</span>.statsService.getDashboardStats(),
      notifications: <span class="hljs-built_in">this</span>.notificationService.getRecentNotifications()
    }).pipe(
      catchError(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error loading dashboard data:'</span>, error);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">of</span>({
          user: <span class="hljs-literal">null</span>,
          stats: <span class="hljs-literal">null</span>,
          notifications: []
        });
      })
    );
  }
}
</code></pre>
<hr />
<h3 id="heading-4-advanced-route-configuration-patterns">4. Advanced Route Configuration Patterns</h3>
<h3 id="heading-combining-guards-and-resolvers">Combining Guards and Resolvers</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'admin'</span>,
    canActivate: [AuthGuard, AdminGuard],
    canActivateChild: [AdminGuard],
    children: [
      {
        path: <span class="hljs-string">'dashboard'</span>,
        component: AdminDashboardComponent,
        resolve: {
          dashboardData: DashboardResolver
        },
        data: { requiredRole: <span class="hljs-string">'admin'</span> }
      },
      {
        path: <span class="hljs-string">'users/:id'</span>,
        component: UserDetailComponent,
        canDeactivate: [UnsavedChangesGuard],
        resolve: {
          user: UserResolver
        }
      }
    ]
  }
];
</code></pre>
<h3 id="heading-dynamic-route-parameters-with-resolvers">Dynamic Route Parameters with Resolvers</h3>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProductResolver <span class="hljs-keyword">implements</span> Resolve&lt;Product&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> productService: ProductService</span>) {}

  resolve(route: ActivatedRouteSnapshot): Observable&lt;Product&gt; {
    <span class="hljs-keyword">const</span> productId = route.paramMap.get(<span class="hljs-string">'id'</span>);
    <span class="hljs-keyword">const</span> category = route.paramMap.get(<span class="hljs-string">'category'</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.productService.getProduct(productId, category).pipe(
      map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> {
        <span class="hljs-comment">// Add dynamic data based on route params</span>
        <span class="hljs-keyword">return</span> {
          ...product,
          breadcrumb: <span class="hljs-built_in">this</span>.generateBreadcrumb(category, product.name)
        };
      })
    );
  }
  <span class="hljs-keyword">private</span> generateBreadcrumb(category: <span class="hljs-built_in">string</span>, productName: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">string</span>[] {
    <span class="hljs-keyword">return</span> [<span class="hljs-string">'Home'</span>, <span class="hljs-string">'Products'</span>, category, productName];
  }
}
</code></pre>
<hr />
<h3 id="heading-5-performance-first-loading-strategies">5. Performance-First Loading Strategies</h3>
<h3 id="heading-preloading-strategies">Preloading Strategies</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// custom-preloading.strategy.ts</span>
<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CustomPreloadingStrategy <span class="hljs-keyword">implements</span> PreloadingStrategy {
  preload(route: Route, load: <span class="hljs-function">() =&gt;</span> Observable&lt;<span class="hljs-built_in">any</span>&gt;): Observable&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-comment">// Only preload routes marked with preload: true</span>
    <span class="hljs-keyword">if</span> (route.data &amp;&amp; route.data[<span class="hljs-string">'preload'</span>]) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Preloading: '</span> + route.path);
      <span class="hljs-keyword">return</span> load();
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">of</span>(<span class="hljs-literal">null</span>);
  }
}

<span class="hljs-comment">// app-routing.module.ts</span>
<span class="hljs-keyword">const</span> routes: Routes = [
  {
    path: <span class="hljs-string">'dashboard'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./dashboard/dashboard.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.DashboardModule),
    data: { preload: <span class="hljs-literal">true</span> } <span class="hljs-comment">// This will be preloaded</span>
  },
  {
    path: <span class="hljs-string">'archive'</span>,
    loadChildren: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./archive/archive.module'</span>).then(<span class="hljs-function"><span class="hljs-params">m</span> =&gt;</span> m.ArchiveModule)
    <span class="hljs-comment">// This won't be preloaded</span>
  }
];
<span class="hljs-meta">@NgModule</span>({
  imports: [RouterModule.forRoot(routes, {
    preloadingStrategy: CustomPreloadingStrategy
  })],
  <span class="hljs-built_in">exports</span>: [RouterModule]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AppRoutingModule { }
</code></pre>
<hr />
<h3 id="heading-6-advanced-security-patterns">6. Advanced Security Patterns</h3>
<h3 id="heading-role-based-access-control">Role-Based Access Control</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// rbac.guard.ts</span>
<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> RbacGuard <span class="hljs-keyword">implements</span> CanActivate {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> authService: AuthService</span>) {}

  canActivate(route: ActivatedRouteSnapshot): <span class="hljs-built_in">boolean</span> {
    <span class="hljs-keyword">const</span> requiredPermissions = route.data[<span class="hljs-string">'permissions'</span>] <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>[];
    <span class="hljs-keyword">const</span> userPermissions = <span class="hljs-built_in">this</span>.authService.getUserPermissions();
    <span class="hljs-keyword">return</span> requiredPermissions.every(<span class="hljs-function"><span class="hljs-params">permission</span> =&gt;</span>
      userPermissions.includes(permission)
    );
  }
}
<span class="hljs-comment">// Usage in routes</span>
{
  path: <span class="hljs-string">'sensitive-data'</span>,
  component: SensitiveDataComponent,
  canActivate: [AuthGuard, RbacGuard],
  data: {
    permissions: [<span class="hljs-string">'read:sensitive_data'</span>, <span class="hljs-string">'access:admin_panel'</span>]
  }
}
</code></pre>
<h3 id="heading-jwt-token-validation-guard">JWT Token Validation Guard</h3>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> TokenValidationGuard <span class="hljs-keyword">implements</span> CanActivate {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> authService: AuthService,
    <span class="hljs-keyword">private</span> router: Router
  </span>) {}

    canActivate(): Observable&lt;<span class="hljs-built_in">boolean</span>&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.authService.validateToken().pipe(
      map(<span class="hljs-function"><span class="hljs-params">isValid</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (!isValid) {
          <span class="hljs-built_in">this</span>.authService.logout();
          <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/login'</span>]);
          <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }),
      catchError(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">this</span>.router.navigate([<span class="hljs-string">'/login'</span>]);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">of</span>(<span class="hljs-literal">false</span>);
      })
    );
  }
}
</code></pre>
<hr />
<h3 id="heading-7-error-handling-and-user-experience">7. Error Handling and User Experience</h3>
<h3 id="heading-graceful-error-handling-in-resolvers">Graceful Error Handling in Resolvers</h3>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ErrorHandlingResolver <span class="hljs-keyword">implements</span> Resolve&lt;<span class="hljs-built_in">any</span>&gt; {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> dataService: DataService,
    <span class="hljs-keyword">private</span> errorHandler: ErrorHandlerService,
    <span class="hljs-keyword">private</span> loadingService: LoadingService
  </span>) {}

  resolve(route: ActivatedRouteSnapshot): Observable&lt;<span class="hljs-built_in">any</span>&gt; {
    <span class="hljs-built_in">this</span>.loadingService.show();
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.dataService.getData(route.params[<span class="hljs-string">'id'</span>]).pipe(
      finalize(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">this</span>.loadingService.hide()),
      catchError(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
        <span class="hljs-built_in">this</span>.errorHandler.handleError(error);
        <span class="hljs-comment">// Return fallback data instead of breaking the route</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">of</span>({
          id: route.params[<span class="hljs-string">'id'</span>],
          name: <span class="hljs-string">'Data Unavailable'</span>,
          error: <span class="hljs-literal">true</span>
        });
      })
    );
  }
}
</code></pre>
<h3 id="heading-loading-states-with-resolvers">Loading States with Resolvers</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// Component implementation</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DataComponent <span class="hljs-keyword">implements</span> OnInit {
  data: <span class="hljs-built_in">any</span>;
  isLoading = <span class="hljs-literal">true</span>;
  hasError = <span class="hljs-literal">false</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> route: ActivatedRoute</span>) {}
  ngOnInit() {
    <span class="hljs-built_in">this</span>.route.data.subscribe(<span class="hljs-function">(<span class="hljs-params">{ resolvedData }</span>) =&gt;</span> {
      <span class="hljs-built_in">this</span>.data = resolvedData;
      <span class="hljs-built_in">this</span>.hasError = resolvedData?.error || <span class="hljs-literal">false</span>;
      <span class="hljs-built_in">this</span>.isLoading = <span class="hljs-literal">false</span>;
    });
  }
}
</code></pre>
<hr />
<h3 id="heading-8-user-experience-enhancements">8. User Experience Enhancements</h3>
<h3 id="heading-route-transition-animations">Route Transition Animations</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// route-animations.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> routeAnimations = trigger(<span class="hljs-string">'routeAnimations'</span>, [
  transition(<span class="hljs-string">'* &lt;=&gt; *'</span>, [
    style({ position: <span class="hljs-string">'relative'</span> }),
    query(<span class="hljs-string">':enter, :leave'</span>, [
      style({
        position: <span class="hljs-string">'absolute'</span>,
        top: <span class="hljs-number">0</span>,
        left: <span class="hljs-number">0</span>,
        width: <span class="hljs-string">'100%'</span>
      })
    ], { optional: <span class="hljs-literal">true</span> }),
    query(<span class="hljs-string">':enter'</span>, [
      style({ transform: <span class="hljs-string">'translateX(100%)'</span> })
    ], { optional: <span class="hljs-literal">true</span> }),
    query(<span class="hljs-string">':leave'</span>, animateChild(), { optional: <span class="hljs-literal">true</span> }),
    group([
      query(<span class="hljs-string">':leave'</span>, [
        animate(<span class="hljs-string">'200ms ease-out'</span>, style({ transform: <span class="hljs-string">'translateX(-100%)'</span> }))
      ], { optional: <span class="hljs-literal">true</span> }),
      query(<span class="hljs-string">':enter'</span>, [
        animate(<span class="hljs-string">'200ms ease-out'</span>, style({ transform: <span class="hljs-string">'translateX(0%)'</span> }))
      ], { optional: <span class="hljs-literal">true</span> })
    ]),
    query(<span class="hljs-string">':enter'</span>, animateChild(), { optional: <span class="hljs-literal">true</span> }),
  ])
]);
</code></pre>
<h3 id="heading-smart-progress-indicators">Smart Progress Indicators</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// progress-interceptor.service.ts</span>
<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProgressInterceptor <span class="hljs-keyword">implements</span> HttpInterceptor {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> progressService: ProgressService</span>) {}

  intercept(req: HttpRequest&lt;<span class="hljs-built_in">any</span>&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;<span class="hljs-built_in">any</span>&gt;&gt; {
    <span class="hljs-comment">// Show progress only for resolver requests</span>
    <span class="hljs-keyword">if</span> (req.context.get(IS_RESOLVER_REQUEST)) {
      <span class="hljs-built_in">this</span>.progressService.show();
    }
    <span class="hljs-keyword">return</span> next.handle(req).pipe(
      finalize(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (req.context.get(IS_RESOLVER_REQUEST)) {
          <span class="hljs-built_in">this</span>.progressService.hide();
        }
      })
    );
  }
}
</code></pre>
<hr />
<h3 id="heading-9-testing-your-navigation-logic">9. Testing Your Navigation Logic</h3>
<h3 id="heading-testing-route-guards">Testing Route Guards</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// auth.guard.spec.ts</span>
describe(<span class="hljs-string">'AuthGuard'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> guard: AuthGuard;
  <span class="hljs-keyword">let</span> authService: jasmine.SpyObj&lt;AuthService&gt;;
  <span class="hljs-keyword">let</span> router: jasmine.SpyObj&lt;Router&gt;;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> authSpy = jasmine.createSpyObj(<span class="hljs-string">'AuthService'</span>, [<span class="hljs-string">'isAuthenticated'</span>]);
    <span class="hljs-keyword">const</span> routerSpy = jasmine.createSpyObj(<span class="hljs-string">'Router'</span>, [<span class="hljs-string">'navigate'</span>]);
    TestBed.configureTestingModule({
      providers: [
        { provide: AuthService, useValue: authSpy },
        { provide: Router, useValue: routerSpy }
      ]
    });
    guard = TestBed.inject(AuthGuard);
    authService = TestBed.inject(AuthService) <span class="hljs-keyword">as</span> jasmine.SpyObj&lt;AuthService&gt;;
    router = TestBed.inject(Router) <span class="hljs-keyword">as</span> jasmine.SpyObj&lt;Router&gt;;
  });
  it(<span class="hljs-string">'should allow access when user is authenticated'</span>, <span class="hljs-function">() =&gt;</span> {
    authService.isAuthenticated.and.returnValue(<span class="hljs-literal">true</span>);
    expect(guard.canActivate({} <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>, {} <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>)).toBe(<span class="hljs-literal">true</span>);
  });
  it(<span class="hljs-string">'should redirect to login when user is not authenticated'</span>, <span class="hljs-function">() =&gt;</span> {
    authService.isAuthenticated.and.returnValue(<span class="hljs-literal">false</span>);
    expect(guard.canActivate({} <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>, { url: <span class="hljs-string">'/dashboard'</span> } <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>)).toBe(<span class="hljs-literal">false</span>);
    expect(router.navigate).toHaveBeenCalledWith([<span class="hljs-string">'/login'</span>]);
  });
});
</code></pre>
<h3 id="heading-testing-resolvers">Testing Resolvers</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// user-resolver.spec.ts</span>
describe(<span class="hljs-string">'UserResolver'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> resolver: UserResolver;
  <span class="hljs-keyword">let</span> userService: jasmine.SpyObj&lt;UserService&gt;;

  beforeEach(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> userSpy = jasmine.createSpyObj(<span class="hljs-string">'UserService'</span>, [<span class="hljs-string">'getUserById'</span>]);
    TestBed.configureTestingModule({
      providers: [
        { provide: UserService, useValue: userSpy }
      ]
    });
    resolver = TestBed.inject(UserResolver);
    userService = TestBed.inject(UserService) <span class="hljs-keyword">as</span> jasmine.SpyObj&lt;UserService&gt;;
  });
  it(<span class="hljs-string">'should resolve user data'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> mockUser = { id: <span class="hljs-string">'1'</span>, name: <span class="hljs-string">'John Doe'</span> };
    userService.getUserById.and.returnValue(<span class="hljs-keyword">of</span>(mockUser));
    <span class="hljs-keyword">const</span> route = { paramMap: { get: <span class="hljs-function">() =&gt;</span> <span class="hljs-string">'1'</span> } } <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>;
    resolver.resolve(route, {} <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).subscribe(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> {
      expect(user).toEqual(mockUser);
    });
  });
});
</code></pre>
<hr />
<h3 id="heading-10-production-ready-optimization-tips">10. Production-Ready Optimization Tips</h3>
<h3 id="heading-bundle-analysis-and-code-splitting">Bundle Analysis and Code Splitting</h3>
<pre><code class="lang-typescript"># Analyze your bundle size
ng build --stats-json
npx webpack-bundle-analyzer dist/stats.json

# Optimize <span class="hljs-keyword">for</span> production
ng build --prod --build-optimizer --vendor-chunk --common-chunk
</code></pre>
<h3 id="heading-performance-monitoring">Performance Monitoring</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// performance-monitor.service.ts</span>
<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PerformanceMonitorService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> analytics: AnalyticsService</span>) {}

  measureRouteLoad(routeName: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> startTime = performance.now();
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> endTime = performance.now();
      <span class="hljs-keyword">const</span> loadTime = endTime - startTime;
      <span class="hljs-built_in">this</span>.analytics.track(<span class="hljs-string">'route_load_time'</span>, {
        route: routeName,
        loadTime: loadTime,
        timestamp: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString()
      });
      <span class="hljs-keyword">if</span> (loadTime &gt; <span class="hljs-number">2000</span>) {
        <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`Slow route detected: <span class="hljs-subst">${routeName}</span> took <span class="hljs-subst">${loadTime}</span>ms`</span>);
      }
    };
  }
}
</code></pre>
<hr />
<h3 id="heading-key-takeaways">🎯 Key Takeaways</h3>
<p>Mastering Angular’s lazy loading, route guards, and resolvers isn’t just about writing code — it’s about crafting exceptional user experiences. Here’s what separates the pros from the beginners:</p>
<ol>
<li><p><strong>Lazy Loading</strong> reduces initial bundle size by 50–70%</p>
</li>
<li><p><strong>Route Guards</strong> provide bulletproof security and user flow control</p>
</li>
<li><p><strong>Resolvers</strong> eliminate loading spinners and improve perceived performance</p>
</li>
<li><p><strong>Smart preloading</strong> strategies balance performance with user experience</p>
</li>
<li><p><strong>Proper error handling</strong> prevents broken user journeys</p>
</li>
<li><p><strong>Testing</strong> ensures your navigation logic works flawlessly</p>
</li>
<li><p><strong>Performance monitoring</strong> helps you optimize continuously</p>
</li>
</ol>
<p>The developers who implement these patterns see dramatic improvements in Core Web Vitals, user engagement, and overall application performance.</p>
<hr />
<h3 id="heading-whats-your-experience">💡 What’s Your Experience?</h3>
<p>Have you implemented any of these patterns in your Angular applications? What challenges did you face, and how did you overcome them?</p>
<p><strong>Drop a comment below and share:</strong></p>
<ul>
<li><p>Your favorite optimization technique</p>
</li>
<li><p>Any unique use cases you’ve encountered</p>
</li>
<li><p>Questions about implementing these patterns</p>
</li>
</ul>
<p>Your insights could help fellow developers navigate their own Angular challenges!</p>
<hr />
<h3 id="heading-ready-for-more-angular-mastery">🔥 Ready for More Angular Mastery?</h3>
<p>If this deep dive into Angular routing helped level up your skills, you’ll love my upcoming content on:</p>
<ul>
<li><p>Advanced RxJS patterns for Angular developers</p>
</li>
<li><p>State management strategies that scale</p>
</li>
<li><p>Performance optimization secrets from production apps</p>
</li>
<li><p>Angular testing strategies that actually work</p>
</li>
</ul>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> </p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item><item><title><![CDATA[Angular’s Game-Changing Dynamic Component Features: inputBinding(), outputBinding(), and twoWayBinding()]]></title><description><![CDATA[Ever struggled with creating dynamic components in Angular and wished there was a cleaner, more intuitive way? 
If you’ve been working with Angular’s dynamic components using ComponentFactory or ViewContainerRef, you know the pain of verbose code, co...]]></description><link>https://hashnode.rajatmalik.dev/angulars-game-changing-dynamic-component-features-inputbinding-outputbinding-and-twowaybinding</link><guid isPermaLink="true">https://hashnode.rajatmalik.dev/angulars-game-changing-dynamic-component-features-inputbinding-outputbinding-and-twowaybinding</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Rajat Malik]]></dc:creator><pubDate>Tue, 14 Oct 2025 13:30:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760168238521/2759d7ae-64b3-46a8-b112-67d3b07d4063.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Ever struggled with creating dynamic components in Angular and wished there was a cleaner, more intuitive way?</strong> </p>
<p>If you’ve been working with Angular’s dynamic components using ComponentFactory or ViewContainerRef, you know the pain of verbose code, complex event handling, and the constant worry about memory leaks. Well, Angular 20 just dropped some absolute game-changers that will make you rethink how you approach dynamic components entirely.</p>
<p><strong>What you’ll learn by the end of this article:</strong></p>
<ul>
<li><p>How to use Angular 20’s new <code>inputBinding()</code>, <code>outputBinding()</code>, and <code>twoWayBinding()</code> APIs</p>
</li>
<li><p>Best practices for memory management and cleanup</p>
</li>
<li><p>Complete unit testing strategies</p>
</li>
<li><p>Performance optimization techniques</p>
</li>
<li><p>Real-world implementation examples with clean, maintainable code</p>
</li>
</ul>
<p><em>Ready to level up your Angular game? Let’s dive in!</em> </p>
<hr />
<h3 id="heading-the-old-way-vs-the-new-way-a-quick-reality-check">The Old Way vs. The New Way: A Quick Reality Check</h3>
<p>Before we jump into the good stuff, let’s be honest about what we used to deal with:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// The old, painful way </span>
<span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(MyComponent);
componentRef.instance.someInput = <span class="hljs-string">'value'</span>;
componentRef.instance.someOutput.subscribe(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
  <span class="hljs-comment">// Handle output</span>
});

<span class="hljs-comment">// Don't forget cleanup... if you remember </span>
ngOnDestroy() {
  <span class="hljs-comment">// Manual subscription cleanup</span>
}
</code></pre>
<p><strong>Sound familiar?</strong> We’ve all been there — wrestling with subscriptions, forgetting cleanup, and ending up with memory leaks that haunt our apps.</p>
<p>💬 <strong>Quick question for you:</strong> How many times have you forgotten to unsubscribe from dynamic component outputs? Be honest in the comments!</p>
<hr />
<h3 id="heading-meet-the-new-heroes-inputbinding-outputbinding-and-twowaybinding">Meet the New Heroes: inputBinding(), outputBinding(), and twoWayBinding()</h3>
<p>Angular 20 introduces three powerful methods that transform how we work with dynamic components. Let’s break them down:</p>
<h3 id="heading-1-inputbinding-clean-input-handling">1. inputBinding() — Clean Input Handling</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Component, ViewContainerRef, inject } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { inputBinding } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dynamic-host'</span>,
  template: <span class="hljs-string">`&lt;div #dynamicContainer&gt;&lt;/div&gt;`</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DynamicHostComponent {
  <span class="hljs-keyword">private</span> viewContainer = inject(ViewContainerRef);
  addDynamicComponent() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(UserCardComponent);
    <span class="hljs-comment">// New way - Clean and type-safe!</span>
    inputBinding(componentRef, <span class="hljs-string">'userName'</span>, <span class="hljs-string">'John Doe'</span>);
    inputBinding(componentRef, <span class="hljs-string">'isActive'</span>, <span class="hljs-literal">true</span>);
    inputBinding(componentRef, <span class="hljs-string">'userData'</span>, { id: <span class="hljs-number">1</span>, email: <span class="hljs-string">'john@example.com'</span> });
  }
}
</code></pre>
<h3 id="heading-2-outputbinding-effortless-event-handling">2. outputBinding() — Effortless Event Handling</h3>
<pre><code class="lang-typescript">addDynamicComponentWithEvents() {
  <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(UserCardComponent);

<span class="hljs-comment">// Set inputs</span>
  inputBinding(componentRef, <span class="hljs-string">'userName'</span>, <span class="hljs-string">'Jane Smith'</span>);
  <span class="hljs-comment">// Handle outputs the new way</span>
  outputBinding(componentRef, <span class="hljs-string">'userClicked'</span>, <span class="hljs-function">(<span class="hljs-params">userData</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User clicked:'</span>, userData);
    <span class="hljs-built_in">this</span>.handleUserSelection(userData);
  });
  outputBinding(componentRef, <span class="hljs-string">'deleteRequested'</span>, <span class="hljs-function">(<span class="hljs-params">userId</span>) =&gt;</span> {
    <span class="hljs-built_in">this</span>.confirmDelete(userId);
  });
}
</code></pre>
<h3 id="heading-3-twowaybinding-the-holy-grail">3. twoWayBinding() — The Holy Grail</h3>
<p>This is where it gets really exciting. Two-way binding with dynamic components used to be a nightmare. Not anymore:</p>
<pre><code class="lang-typescript">setupTwoWayBinding() {
  <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(EditableCardComponent);
  <span class="hljs-comment">// Two-way binding made simple</span>
  twoWayBinding(componentRef, <span class="hljs-string">'value'</span>,
    <span class="hljs-comment">// Initial value</span>
    <span class="hljs-built_in">this</span>.currentValue,
    <span class="hljs-comment">// Callback when value changes</span>
    (newValue) =&gt; {
      <span class="hljs-built_in">this</span>.currentValue = newValue;
      <span class="hljs-built_in">this</span>.onValueChanged(newValue);
    }
  );
}
</code></pre>
<p><strong>Pretty clean, right?</strong> </p>
<hr />
<h3 id="heading-real-world-example-dynamic-dashboard-widgets">Real-World Example: Dynamic Dashboard Widgets</h3>
<p>Let’s build something practical — a dashboard that can dynamically add different widget types:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-dashboard'</span>,
  template: <span class="hljs-string">`
    &lt;div class="dashboard-header"&gt;
      &lt;h2&gt;My Dashboard&lt;/h2&gt;
      &lt;button (click)="addWidget('chart')" class="btn-primary"&gt;Add Chart&lt;/button&gt;
      &lt;button (click)="addWidget('table')" class="btn-primary"&gt;Add Table&lt;/button&gt;
      &lt;button (click)="addWidget('counter')" class="btn-primary"&gt;Add Counter&lt;/button&gt;
    &lt;/div&gt;
    &lt;div class="widgets-container" #widgetContainer&gt;&lt;/div&gt;
  `</span>,
  styles: [<span class="hljs-string">`
    .widgets-container {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 1rem;
      padding: 1rem;
    }
  `</span>]
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DashboardComponent <span class="hljs-keyword">implements</span> OnDestroy {
  <span class="hljs-keyword">private</span> viewContainer = inject(ViewContainerRef);
  <span class="hljs-keyword">private</span> activeComponents: ComponentRef&lt;<span class="hljs-built_in">any</span>&gt;[] = [];
  <span class="hljs-keyword">private</span> subscriptions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>&lt;<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>&gt;();
  addWidget(<span class="hljs-keyword">type</span>: <span class="hljs-string">'chart'</span> | <span class="hljs-string">'table'</span> | <span class="hljs-string">'counter'</span>) {
    <span class="hljs-keyword">let</span> componentRef: ComponentRef&lt;<span class="hljs-built_in">any</span>&gt;;
    <span class="hljs-keyword">switch</span> (<span class="hljs-keyword">type</span>) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'chart'</span>:
        componentRef = <span class="hljs-built_in">this</span>.createChartWidget();
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'table'</span>:
        componentRef = <span class="hljs-built_in">this</span>.createTableWidget();
        <span class="hljs-keyword">break</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">'counter'</span>:
        componentRef = <span class="hljs-built_in">this</span>.createCounterWidget();
        <span class="hljs-keyword">break</span>;
    }
    <span class="hljs-built_in">this</span>.activeComponents.push(componentRef);
  }
  <span class="hljs-keyword">private</span> createChartWidget() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(ChartWidgetComponent);
    <span class="hljs-comment">// Set initial data</span>
    inputBinding(componentRef, <span class="hljs-string">'title'</span>, <span class="hljs-string">'Sales Chart'</span>);
    inputBinding(componentRef, <span class="hljs-string">'data'</span>, <span class="hljs-built_in">this</span>.generateChartData());
    inputBinding(componentRef, <span class="hljs-string">'chartType'</span>, <span class="hljs-string">'line'</span>);
    <span class="hljs-comment">// Handle widget events</span>
    <span class="hljs-keyword">const</span> unsubscribe1 = outputBinding(componentRef, <span class="hljs-string">'refreshRequested'</span>, <span class="hljs-function">() =&gt;</span> {
      inputBinding(componentRef, <span class="hljs-string">'data'</span>, <span class="hljs-built_in">this</span>.generateChartData());
    });
    <span class="hljs-keyword">const</span> unsubscribe2 = outputBinding(componentRef, <span class="hljs-string">'deleteRequested'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.removeWidget(componentRef);
    });
    <span class="hljs-comment">// Store cleanup functions</span>
    <span class="hljs-built_in">this</span>.subscriptions.add(unsubscribe1);
    <span class="hljs-built_in">this</span>.subscriptions.add(unsubscribe2);
    <span class="hljs-keyword">return</span> componentRef;
  }
  <span class="hljs-keyword">private</span> createCounterWidget() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(CounterWidgetComponent);
    <span class="hljs-comment">// Two-way binding for counter value</span>
    <span class="hljs-keyword">const</span> unsubscribe = twoWayBinding(componentRef, <span class="hljs-string">'count'</span>,
      <span class="hljs-number">0</span>, <span class="hljs-comment">// initial value</span>
      (newCount) =&gt; {
        <span class="hljs-comment">// Sync with parent state or save to backend</span>
        <span class="hljs-built_in">this</span>.saveWidgetState(componentRef.instance.id, { count: newCount });
      }
    );
    <span class="hljs-built_in">this</span>.subscriptions.add(unsubscribe);
    <span class="hljs-keyword">return</span> componentRef;
  }
  <span class="hljs-keyword">private</span> removeWidget(componentRef: ComponentRef&lt;<span class="hljs-built_in">any</span>&gt;) {
    <span class="hljs-keyword">const</span> index = <span class="hljs-built_in">this</span>.activeComponents.indexOf(componentRef);
    <span class="hljs-keyword">if</span> (index &gt; <span class="hljs-number">-1</span>) {
      <span class="hljs-built_in">this</span>.activeComponents.splice(index, <span class="hljs-number">1</span>);
      componentRef.destroy();
    }
  }
  ngOnDestroy() {
    <span class="hljs-comment">// Clean shutdown - no memory leaks!</span>
    <span class="hljs-built_in">this</span>.subscriptions.forEach(<span class="hljs-function"><span class="hljs-params">unsubscribe</span> =&gt;</span> unsubscribe());
    <span class="hljs-built_in">this</span>.subscriptions.clear();
    <span class="hljs-built_in">this</span>.activeComponents.forEach(<span class="hljs-function"><span class="hljs-params">ref</span> =&gt;</span> ref.destroy());
    <span class="hljs-built_in">this</span>.activeComponents.length = <span class="hljs-number">0</span>;
  }
}
</code></pre>
<p><strong>Pro tip:</strong> Notice how we’re storing unsubscribe functions and cleaning them up properly? This is crucial for preventing memory leaks!</p>
<hr />
<h3 id="heading-unit-testing-your-dynamic-components">Unit Testing Your Dynamic Components</h3>
<p>Testing dynamic components can be tricky, but Angular 20’s new APIs make it much more straightforward:</p>
<pre><code class="lang-typescript">describe(<span class="hljs-string">'DashboardComponent'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> component: DashboardComponent;
  <span class="hljs-keyword">let</span> fixture: ComponentFixture&lt;DashboardComponent&gt;;
  beforeEach(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">await</span> TestBed.configureTestingModule({
      declarations: [DashboardComponent, ChartWidgetComponent, CounterWidgetComponent],
      imports: [CommonModule]
    }).compileComponents();
    fixture = TestBed.createComponent(DashboardComponent);
    component = fixture.componentInstance;
  });
  describe(<span class="hljs-string">'Dynamic Widget Creation'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should create chart widget with correct inputs'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Act</span>
      component.addWidget(<span class="hljs-string">'chart'</span>);
      fixture.detectChanges();
      <span class="hljs-comment">// Assert</span>
      <span class="hljs-keyword">const</span> chartComponent = fixture.debugElement.query(
        By.directive(ChartWidgetComponent)
      );
      expect(chartComponent).toBeTruthy();
      expect(chartComponent.componentInstance.title).toBe(<span class="hljs-string">'Sales Chart'</span>);
      expect(chartComponent.componentInstance.chartType).toBe(<span class="hljs-string">'line'</span>);
    });
    it(<span class="hljs-string">'should handle widget events correctly'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      spyOn(component, <span class="hljs-string">'removeWidget'</span>);
      <span class="hljs-comment">// Act</span>
      component.addWidget(<span class="hljs-string">'chart'</span>);
      fixture.detectChanges();
      <span class="hljs-keyword">const</span> chartComponent = fixture.debugElement.query(
        By.directive(ChartWidgetComponent)
      );
      <span class="hljs-comment">// Simulate delete event</span>
      chartComponent.componentInstance.deleteRequested.emit();
      tick();
      <span class="hljs-comment">// Assert</span>
      expect(component.removeWidget).toHaveBeenCalled();
    }));
    it(<span class="hljs-string">'should handle two-way binding for counter widget'</span>, fakeAsync(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      spyOn(component, <span class="hljs-string">'saveWidgetState'</span>);
      <span class="hljs-comment">// Act</span>
      component.addWidget(<span class="hljs-string">'counter'</span>);
      fixture.detectChanges();
      <span class="hljs-keyword">const</span> counterComponent = fixture.debugElement.query(
        By.directive(CounterWidgetComponent)
      );
      <span class="hljs-comment">// Simulate count change</span>
      counterComponent.componentInstance.count = <span class="hljs-number">5</span>;
      counterComponent.componentInstance.countChange.emit(<span class="hljs-number">5</span>);
      tick();
      <span class="hljs-comment">// Assert</span>
      expect(component.saveWidgetState).toHaveBeenCalledWith(
        jasmine.any(<span class="hljs-built_in">String</span>),
        { count: <span class="hljs-number">5</span> }
      );
    }));
  });
  describe(<span class="hljs-string">'Memory Management'</span>, <span class="hljs-function">() =&gt;</span> {
    it(<span class="hljs-string">'should clean up subscriptions on destroy'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Arrange</span>
      component.addWidget(<span class="hljs-string">'chart'</span>);
      component.addWidget(<span class="hljs-string">'counter'</span>);
      <span class="hljs-keyword">const</span> subscriptionCount = (component <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).subscriptions.size;
      expect(subscriptionCount).toBeGreaterThan(<span class="hljs-number">0</span>);
      <span class="hljs-comment">// Act</span>
      component.ngOnDestroy();
      <span class="hljs-comment">// Assert</span>
      expect((component <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).subscriptions.size).toBe(<span class="hljs-number">0</span>);
      expect((component <span class="hljs-keyword">as</span> <span class="hljs-built_in">any</span>).activeComponents.length).toBe(<span class="hljs-number">0</span>);
    });
  });
});
</code></pre>
<p><strong>Testing tip:</strong> Always test both the happy path and the cleanup! Memory leaks in tests can be just as problematic as in production. </p>
<hr />
<h3 id="heading-best-practices-amp-memory-management">Best Practices &amp; Memory Management</h3>
<h3 id="heading-1-always-clean-up-your-subscriptions">1. Always Clean Up Your Subscriptions</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DynamicComponentManager <span class="hljs-keyword">implements</span> OnDestroy {
  <span class="hljs-keyword">private</span> cleanup: (<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>)[] = [];
  addComponent() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.createComponent();
    <span class="hljs-comment">// Store cleanup function</span>
    <span class="hljs-keyword">const</span> unsubscribe = outputBinding(componentRef, <span class="hljs-string">'someEvent'</span>, <span class="hljs-built_in">this</span>.handleEvent);
    <span class="hljs-built_in">this</span>.cleanup.push(unsubscribe);
  }
  ngOnDestroy() {
    <span class="hljs-comment">// Execute all cleanup functions</span>
    <span class="hljs-built_in">this</span>.cleanup.forEach(<span class="hljs-function"><span class="hljs-params">fn</span> =&gt;</span> fn());
    <span class="hljs-built_in">this</span>.cleanup.length = <span class="hljs-number">0</span>;
  }
}
</code></pre>
<h3 id="heading-2-use-weakmap-for-component-metadata">2. Use WeakMap for Component Metadata</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> AdvancedDynamicManager {
  <span class="hljs-keyword">private</span> componentMetadata = <span class="hljs-keyword">new</span> <span class="hljs-built_in">WeakMap</span>&lt;ComponentRef&lt;<span class="hljs-built_in">any</span>&gt;, ComponentMetaData&gt;();
  addComponent() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.createComponent();
    <span class="hljs-comment">// Store metadata that gets garbage collected with the component</span>
    <span class="hljs-built_in">this</span>.componentMetadata.set(componentRef, {
      createdAt: <span class="hljs-built_in">Date</span>.now(),
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'chart'</span>,
      subscriptions: []
    });
  }
}
</code></pre>
<h3 id="heading-3-implement-change-detection-optimization">3. Implement Change Detection Optimization</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> OptimizedDynamicHost {
  addComponent() {
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(MyComponent);
    <span class="hljs-comment">// Optimize change detection</span>
    componentRef.changeDetectorRef.detach();
    inputBinding(componentRef, <span class="hljs-string">'data'</span>, <span class="hljs-built_in">this</span>.data);
    <span class="hljs-comment">// Manually trigger when needed</span>
    componentRef.changeDetectorRef.detectChanges();
  }
}
</code></pre>
<h3 id="heading-4-monitor-performance-with-angular-devtools">4. Monitor Performance with Angular DevTools</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> PerformanceAwareDynamic {
  <span class="hljs-keyword">private</span> performanceMetrics = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">number</span>&gt;();
  addComponent(<span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">const</span> startTime = performance.now();
    <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.createComponent(<span class="hljs-keyword">type</span>);
    <span class="hljs-keyword">const</span> endTime = performance.now();
    <span class="hljs-built_in">this</span>.performanceMetrics.set(<span class="hljs-keyword">type</span>, endTime - startTime);
    <span class="hljs-comment">// Log slow component creation</span>
    <span class="hljs-keyword">if</span> (endTime - startTime &gt; <span class="hljs-number">100</span>) {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`Slow component creation for <span class="hljs-subst">${<span class="hljs-keyword">type</span>}</span>: <span class="hljs-subst">${endTime - startTime}</span>ms`</span>);
    }
  }
}
</code></pre>
<p><strong>Quick question:</strong> Are you currently monitoring your dynamic component performance? Drop a comment and let me know what tools you use! 💬</p>
<hr />
<h3 id="heading-bonus-tips">Bonus Tips</h3>
<h3 id="heading-1-type-safety-with-generics">1. Type Safety with Generics</h3>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createTypedComponent</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">
  viewContainer: ViewContainerRef,
  componentType: Type&lt;T&gt;,
  inputs: Partial&lt;T&gt; = {}
</span>): <span class="hljs-title">ComponentRef</span>&lt;<span class="hljs-title">T</span>&gt; </span>{
  <span class="hljs-keyword">const</span> componentRef = viewContainer.createComponent(componentType);

<span class="hljs-built_in">Object</span>.entries(inputs).forEach(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =&gt;</span> {
    inputBinding(componentRef, key <span class="hljs-keyword">as</span> keyof T, value);
  });
  <span class="hljs-keyword">return</span> componentRef;
}
<span class="hljs-comment">// Usage with full type safety</span>
<span class="hljs-keyword">const</span> chartRef = createTypedComponent(<span class="hljs-built_in">this</span>.viewContainer, ChartComponent, {
  title: <span class="hljs-string">'My Chart'</span>,
  data: chartData,
  showLegend: <span class="hljs-literal">true</span>
});
</code></pre>
<h3 id="heading-2-component-factory-service">2. Component Factory Service</h3>
<pre><code class="lang-typescript"><span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> DynamicComponentFactory {
  <span class="hljs-keyword">private</span> registry = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">string</span>, Type&lt;<span class="hljs-built_in">any</span>&gt;&gt;();
  registerComponent(name: <span class="hljs-built_in">string</span>, component: Type&lt;<span class="hljs-built_in">any</span>&gt;) {
    <span class="hljs-built_in">this</span>.registry.set(name, component);
  }
  createComponent(name: <span class="hljs-built_in">string</span>, viewContainer: ViewContainerRef) {
    <span class="hljs-keyword">const</span> componentType = <span class="hljs-built_in">this</span>.registry.get(name);
    <span class="hljs-keyword">if</span> (!componentType) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Component '<span class="hljs-subst">${name}</span>' not registered`</span>);
    }
    <span class="hljs-keyword">return</span> viewContainer.createComponent(componentType);
  }
}
</code></pre>
<h3 id="heading-3-error-boundary-for-dynamic-components">3. Error Boundary for Dynamic Components</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> SafeDynamicHost {
  addComponentSafely(componentType: Type&lt;<span class="hljs-built_in">any</span>&gt;) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> componentRef = <span class="hljs-built_in">this</span>.viewContainer.createComponent(componentType);
      <span class="hljs-comment">// Wrap in error handling</span>
      <span class="hljs-keyword">const</span> originalNgOnInit = componentRef.instance.ngOnInit;
      componentRef.instance.ngOnInit = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">try</span> {
          originalNgOnInit?.call(componentRef.instance);
        } <span class="hljs-keyword">catch</span> (error) {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Dynamic component initialization failed:'</span>, error);
          <span class="hljs-built_in">this</span>.handleComponentError(componentRef, error);
        }
      };
      <span class="hljs-keyword">return</span> componentRef;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to create dynamic component:'</span>, error);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }
  }
}
</code></pre>
<hr />
<h3 id="heading-recap-your-dynamic-component-toolkit">Recap: Your Dynamic Component Toolkit</h3>
<p>Let’s wrap up what we’ve covered:</p>
<p><strong>Key Angular 20 Features:</strong></p>
<ul>
<li><p><code>inputBinding()</code> - Clean, type-safe input handling</p>
</li>
<li><p><code>outputBinding()</code> - Simplified event subscription with automatic cleanup options</p>
</li>
<li><p><code>twoWayBinding()</code> - Effortless two-way data binding for dynamic components</p>
</li>
</ul>
<p><strong>Best Practices Checklist:</strong></p>
<ul>
<li><p>✅ Always clean up subscriptions in <code>ngOnDestroy</code></p>
</li>
<li><p>✅ Use WeakMap for component metadata</p>
</li>
<li><p>✅ Monitor performance of dynamic component creation</p>
</li>
<li><p>✅ Implement error boundaries for robust apps</p>
</li>
<li><p>✅ Leverage TypeScript generics for type safety</p>
</li>
<li><p>✅ Test both functionality and memory management</p>
</li>
</ul>
<p><strong>Memory Management Essentials:</strong></p>
<ul>
<li><p>Store cleanup functions and execute them on destroy</p>
</li>
<li><p>Use <code>ChangeDetectorRef.detach()</code> for performance optimization</p>
</li>
<li><p>Monitor component lifecycle with Angular DevTools</p>
</li>
<li><p>Implement proper error handling to prevent memory leaks</p>
</li>
</ul>
<hr />
<h3 id="heading-your-turn">Your Turn!</h3>
<p><strong>💬 What did you think?</strong> Have you already started experimenting with Angular 20’s dynamic component features? I’d love to hear about your experience! Drop a comment below and share:</p>
<ul>
<li><p>What’s your biggest pain point with dynamic components?</p>
</li>
<li><p>Have you tried these new APIs yet?</p>
</li>
<li><p>What other Angular 20 features are you excited about?</p>
</li>
</ul>
<p> <strong>Found this helpful?</strong> If this article saved you some debugging time or sparked new ideas, smash that clap button! It helps other developers discover these game-changing features too.</p>
<p> <strong>Want more Angular insights like this?</strong> I share practical Angular tips, advanced techniques, and the latest framework updates every week. Follow me here on Medium and never miss out on the good stuff!</p>
<p> <strong>Take Action:</strong></p>
<ol>
<li><p>Try implementing one of these examples in your current project</p>
</li>
<li><p>Refactor an existing dynamic component using the new APIs</p>
</li>
<li><p>Share this article with your team — they’ll thank you later!</p>
</li>
<li><p>Star the GitHub repository with the complete examples (link in my bio)</p>
</li>
</ol>
<p>Keep coding, keep learning, and remember — the best way to master these features is to get your hands dirty with code! </p>
<hr />
<h3 id="heading-follow-me-for-more-angular-amp-frontend-goodness">🚀 Follow Me for More Angular &amp; Frontend Goodness:</h3>
<p>I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.</p>
<ul>
<li><p>💼 <a target="_blank" href="https://www.linkedin.com/in/errajatmalik/"><strong>LinkedIn</strong></a> — Let’s connect professionally</p>
</li>
<li><p>🎥 <a target="_blank" href="https://www.threads.net/@er.rajatmalik"><strong>Threads</strong></a> — Short-form frontend insights</p>
</li>
<li><p>🐦 <a target="_blank" href="https://twitter.com/er_rajatmalik"><strong>X (Twitter)</strong></a> — Developer banter + code snippets</p>
</li>
<li><p>👥 <a target="_blank" href="http://devrajat.bsky.social/"><strong>BlueSky</strong></a> — Stay up to date on frontend trends</p>
</li>
<li><p>🌟 <a target="_blank" href="https://github.com/malikrajat"><strong>GitHub Projects</strong></a> — Explore code in action</p>
</li>
<li><p>🌐 <a target="_blank" href="https://malikrajat.github.io/"><strong>Website</strong></a> — Everything in one place</p>
</li>
<li><p>📚 <a target="_blank" href="https://medium.com/@codewithrajat/"><strong>Medium Blog</strong></a> — Long-form content and deep-dives</p>
</li>
<li><p>💬 <a target="_blank" href="https://dev.to/codewithrajat"><strong>Dev Blog</strong></a> — Free Long-form content and deep-dives</p>
</li>
<li><p>✉️ <a target="_blank" href="https://codewithrajat.substack.com/"><strong>Substack</strong></a> — Weekly frontend stories &amp; curated resources</p>
</li>
<li><p>🧩 <a target="_blank" href="https://rajatmalik.dev/"><strong>Portfolio</strong></a> — Projects, talks, and recognitions</p>
</li>
<li><p>✍️ <a target="_blank" href="https://hashnode.com/@codeswithrajat"><strong>Hashnode</strong></a> — Developer blog posts &amp; tech discussions</p>
</li>
</ul>
<hr />
<h3 id="heading-if-you-found-this-article-valuable">🎉 If you found this article valuable:</h3>
<ul>
<li><p>Leave a <strong>👏 Clap</strong></p>
</li>
<li><p>Drop a <strong>💬 Comment</strong></p>
</li>
<li><p>Hit <strong>🔔 Follow</strong> for more weekly frontend insights</p>
</li>
</ul>
<p>Let’s build cleaner, faster, and smarter web apps — <strong>together</strong>.</p>
<p><strong>Stay tuned for more Angular tips, patterns, and performance tricks!</strong> 🧪🧠</p>
<p><a target="_blank" href="https://forms.gle/mdfXEVh3AxYwAsKw5">✨ Share Your Thoughts To 📣 Set Your Notification Preference</a></p>
]]></content:encoded></item></channel></rss>