{"id":77908,"date":"2026-03-03T12:16:41","date_gmt":"2026-03-03T06:46:41","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=77908"},"modified":"2026-05-12T10:16:06","modified_gmt":"2026-05-12T04:46:06","slug":"angular-mistakes-i-stopped-making-and-you-should-too","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/angular-mistakes-i-stopped-making-and-you-should-too\/","title":{"rendered":"Angular Architecture Mistakes That Slow Down Large Applications"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>There is no doubt that Angular is powerful. But after using Angular\u00a0 from v1 to v20 in real production systems, I found that most performance problems and messy codebases come from a small number of mistakes that keep happening.<\/p>\n<p>In this article, I&#8217;ll talk about what I&#8217;ve learned from my own projects that helped me make Angular apps that are cleaner, more scalable, and faster.<\/p>\n<p>Some mistakes I made in the past that you should try to avoid as much as possible:<\/p>\n<ol>\n<li>Misusing the Default Change Detection Strategy<\/li>\n<li>Poor Subscription Management in Reactive Angular Apps<\/li>\n<li>Not Using trackBy in ngFor<\/li>\n<li>Not Using Lazy Loading and Angular&#8217;s Feature Modules<\/li>\n<li>Not Using Angular HTTP Interceptors for Centralized Error Handling<\/li>\n<li>Ignoring Angular-Specific Performance Tools<\/li>\n<\/ol>\n<h2>Let&#8217;s explore them in\u00a0 more detail.<\/h2>\n<h3>Misusing the Default Change Detection Strategy<\/h3>\n<div id=\"attachment_77909\" style=\"width: 454px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-77909\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-77909\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection-300x200.webp\" alt=\"change detect\" width=\"444\" height=\"296\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection-300x200.webp 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection-1024x682.webp 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection-768x512.webp 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection-624x416.webp 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/change-detection.webp 1100w\" sizes=\"(max-width: 444px) 100vw, 444px\" \/><p id=\"caption-attachment-77909\" class=\"wp-caption-text\">change detection<\/p><\/div>\n<p>At first, I used the default change detection strategy everywhere in my work.<\/p>\n<p>For small apps, the default strategy works very well. But as the component tree gets bigger, it causes change detection to happen in big parts of the application, which slows it down and makes it check things that don&#8217;t need to be checked. Using OnPush only checks for changes when inputs change, which makes our app more efficient and predictable.<\/p>\n<p><strong>Default Strategy<\/strong><\/p>\n<pre>@Component({\r\n   selector: 'app-user-card',\r\n   templateUrl: '.\/user-card.component.html'\r\n})\r\nexport class UserCardComponent {\r\n  @Input() user!: User;\r\n}<\/pre>\n<p><strong>Optimized with OnPush<\/strong><\/p>\n<pre>import { ChangeDetectionStrategy } from '@angular\/core';\r\n\r\n@Component({\r\n\u00a0 \u00a0 \u00a0selector: 'app-user-card',\r\n\u00a0 \u00a0 \u00a0templateUrl: '.\/user-card.component.html',\r\n\u00a0 \u00a0 \u00a0changeDetection: ChangeDetectionStrategy.OnPush\r\n})\r\nexport class UserCardComponent {\r\n\u00a0 \u00a0 \u00a0@Input() user!: User;\r\n}<\/pre>\n<p>When to use it:<br \/>\nUse OnPush for components that only show things, components with inputs that can&#8217;t be changed, big lists, or parts of your app that need to be fast and where you want more control over change detection.<\/p>\n<p>Switching to OnPush on one of our enterprise dashboards with thousands of rows cut down on unnecessary re-renders and made the dashboard much more responsive.<\/p>\n<h2>Poor Subscription Management in Reactive Angular Apps<\/h2>\n<div id=\"attachment_77921\" style=\"width: 417px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-77921\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-77921\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/02\/unsubscribe-300x171.webp\" alt=\"Subscribe Unsubscribe\" width=\"407\" height=\"232\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/02\/unsubscribe-300x171.webp 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/unsubscribe-624x356.webp 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/unsubscribe.webp 640w\" sizes=\"(max-width: 407px) 100vw, 407px\" \/><p id=\"caption-attachment-77921\" class=\"wp-caption-text\">Subscribing Without Unsubscribing<\/p><\/div>\n<p>If you subscribe to Observables without unsubscribing, your Angular app may have memory leaks that you don&#8217;t know about. If a component is destroyed but the subscription is still active, it keeps listening and running logic that isn&#8217;t needed. This can slow down your app over time and make it act in ways you didn&#8217;t expect.<\/p>\n<p>Bad Example:<\/p>\n<pre>ngOnInit() {\r\n  this.userService.getUsers().subscribe(users =&gt; {\r\n    this.users = users;\r\n  });\r\n}<\/pre>\n<p>If the component is destroyed, the subscription may still remain active.<\/p>\n<p>A better approach:<br \/>\nUse takeUntil, AsyncPipe, or manually unsubscribe in ngOnDestroy.<\/p>\n<pre>private destroy$ = new Subject&lt;void&gt;();\r\n\r\nngOnInit() {\r\nthis.userService.getUsers()\r\n.pipe(takeUntil(this.destroy$))\r\n.subscribe(users =&gt; {\r\n\u00a0 \u00a0 this.users = users;\r\n});\r\n}\r\n\r\nngOnDestroy() {\r\n\u00a0 \u00a0 this.destroy$.next();\r\n\u00a0 \u00a0 this.destroy$.complete();\r\n}\r\n\r\n<\/pre>\n<p>Using AsyncPipe example<\/p>\n<pre><strong>HTML<\/strong>\r\n&lt;div *ngFor=\"let user of users$ | async\"&gt;\r\n  {{ user.name }}\r\n&lt;\/div&gt;\r\n\r\n<strong>Typescript<\/strong>\r\n \r\nusers$ = this.userService.getUsers();<\/pre>\n<p>Managing subscriptions properly keeps your app clean, efficient, and memory-safe.<\/p>\n<h2>Not Using trackBy in ngFor<\/h2>\n<div id=\"attachment_77920\" style=\"width: 470px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-77920\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-77920\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by-300x169.webp\" alt=\"TrackBy\" width=\"460\" height=\"259\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by-300x169.webp 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by-1024x576.webp 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by-768x432.webp 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by-624x351.webp 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/track-by.webp 1100w\" sizes=\"(max-width: 460px) 100vw, 460px\" \/><p id=\"caption-attachment-77920\" class=\"wp-caption-text\">TrackBy<\/p><\/div>\n<p>Angular tracks list items by object reference when you don&#8217;t use trackBy in *ngFor. Angular might re-render the whole list instead of just what changed if the array changes even a little bit. This can cause a lot of DOM re-renders and slow down performance in applications that use a lot of data.<\/p>\n<p>Bad approach:<\/p>\n<p>If the users array is replaced (even with the same data), Angular recreates all DOM elements.<\/p>\n<pre>&lt;li *ngFor=\"let user of users\"&gt;\r\n  {{ user.name }}\r\n&lt;\/li&gt;<\/pre>\n<p>Good approach:<\/p>\n<pre>\/\/ HTML\r\n&lt;li *ngFor=\"let item of items; trackBy: trackById\"&gt;\r\n  {{ item.name }}\r\n&lt;\/li&gt;\r\n\r\n\/\/ Typescript\r\ntrackById(index: number, item: any) {\r\n   return item.id;\r\n}<\/pre>\n<p>Using trackBy helps Angular figure out which items really changed, which stops unnecessary DOM re-renders and speeds things up, especially in big lists or lists that change often.<\/p>\n<p>This is especially critical for:<\/p>\n<ul>\n<li>Tables<\/li>\n<li>Virtual scroll<\/li>\n<li>Data-heavy dashboards<\/li>\n<\/ul>\n<p>When you didn&#8217;t use trackBy in big data tables, the whole table would re-render every time you refreshed with a small data or even if only one row changed.<\/p>\n<h2>Not Using Lazy Loading and Angular&#8217;s Feature Modules<\/h2>\n<p>Combining everything into a single Angular module may work initially, but as the application expands and grow. It becomes disorganised, challenging to scale, and difficult to maintain. Large modules make lazy loading impossible, increase coupling, and slow down compilation.<\/p>\n<p>Bad approach: All features live in one place with no clear separation.<\/p>\n<pre>@NgModule({\r\n  declarations: [\r\n     AppComponent,\r\n     HeaderComponent,\r\n     DashboardComponent,\r\n     UserListComponent,\r\n     AdminComponent,\r\n     ReportsComponent\r\n  ],\r\nimports: [\r\n   BrowserModule,\r\n   FormsModule,\r\n   ReactiveFormsModule,\r\n   HttpClientModule\r\n  ]\r\n})\r\nexport class AppModule {}<\/pre>\n<p>Better approach: Feature modules. Now your app is modular, organized, and ready for lazy loading.<\/p>\n<pre>@NgModule({\r\n  declarations: [UserListComponent],\r\n  imports: [CommonModule],\r\n})\r\nexport class UsersModule {}\r\n\r\n@NgModule({\r\n  declarations: [AdminComponent],\r\n  imports: [CommonModule],\r\n})\r\nexport class AdminModule {}<\/pre>\n<p>When to split modules:<\/p>\n<ul>\n<li>When a feature has multiple related components<\/li>\n<li>When you want lazy loading<\/li>\n<li>When different teams work on separate features<\/li>\n<li>When the module starts becoming too large<\/li>\n<\/ul>\n<p>Small, focused modules make your Angular app cleaner, scalable, and easier to maintain.<\/p>\n<h2>Not Using Angular HTTP Interceptors for Centralized Error Handling<\/h2>\n<div id=\"attachment_77922\" style=\"width: 335px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-77922\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-77922\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-300x300.png\" alt=\"Error handling\" width=\"325\" height=\"325\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-300x300.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-150x150.png 150w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-768x768.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-624x624.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-120x120.png 120w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-24x24.png 24w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-48x48.png 48w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2-96x96.png 96w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/error2.png 1024w\" sizes=\"(max-width: 325px) 100vw, 325px\" \/><p id=\"caption-attachment-77922\" class=\"wp-caption-text\">Error handling<\/p><\/div>\n<p>When Angular HTTP Interceptors are not used for centralised error handling, each component or service handles errors independently, resulting in repetitive code and inconsistent user experience. Uncaught errors can cause your app to crash or confuse users.<\/p>\n<p>Create\u00a0 an HTTP interceptor<\/p>\n<pre>@Injectable()\r\nexport class ErrorInterceptor implements HttpInterceptor {\r\n   intercept(req: HttpRequest&lt;any&gt;, next: HttpHandler) {\r\n     return next.handle(req).pipe(\r\n       catchError((error: HttpErrorResponse) =&gt; {\r\n         console.error('Global Error:', error.message);\r\n       return throwError(() =&gt; error);\r\n    })\r\n   );\r\n  }\r\n}<\/pre>\n<p>Using a global handler:<\/p>\n<pre>@Injectable()\r\nexport class GlobalErrorHandler implements ErrorHandler {\r\n   handleError(error: any) {\r\n    console.error('Global error:', error);\r\n   \/\/ show notification or log to server\r\n  }\r\n}\r\n\/\/ Add it to the AppModule\r\n@NgModule({\r\n   providers: [{ provide: ErrorHandler, useClass: GlobalErrorHandler }]\r\n})\r\nexport class AppModule {}<\/pre>\n<p><strong>Why it helps:<\/strong><\/p>\n<ul>\n<li>Centralized error management<\/li>\n<li>Consistent user notifications<\/li>\n<li>Cleaner, maintainable code<\/li>\n<li>Easier logging and monitoring<\/li>\n<\/ul>\n<p>Global error handling makes your app more robust, predictable and professional.<\/p>\n<h2>Ignoring Angular-Specific Performance Tools<\/h2>\n<div id=\"attachment_77919\" style=\"width: 306px\" class=\"wp-caption alignnone\"><img aria-describedby=\"caption-attachment-77919\" decoding=\"async\" loading=\"lazy\" class=\" wp-image-77919\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/02\/speed-247x300.png\" alt=\"Performance Boost\" width=\"296\" height=\"360\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/02\/speed-247x300.png 247w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/speed-768x932.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/speed-624x757.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/02\/speed.png 842w\" sizes=\"(max-width: 296px) 100vw, 296px\" \/><p id=\"caption-attachment-77919\" class=\"wp-caption-text\">Performance Boost<\/p><\/div>\n<p>Small Angular apps may become sluggish and unresponsive as they grow if performance is not considered early on. Longer load times and a slow user interface result from ignoring optimisations like OnPush, lazy loading, or effective change detection.<\/p>\n<p>In large Angular applications, I always evaluate:<\/p>\n<p>\u2022 Lazy loading at the route level with Angular Router<br \/>\n\u2022 Tailored preloading techniques<br \/>\n\u2022 Using Angular CLI build statistics to determine bundle size<br \/>\n\u2022 Choosing a change detection strategy<br \/>\n\u2022 RxJS operators such as debounceIt&#8217;s time for events driven by users<\/p>\n<p>Example:<\/p>\n<pre>searchControl.valueChanges\r\n.pipe(debounceTime(400))\r\n.subscribe(value =&gt; {\r\n    this.search(value);\r\n});<\/pre>\n<h2>Conclusion<\/h2>\n<p>Even though Angular is a powerful tool, writing code alone is not enough to create scalable and maintainable applications. Performance and long-term maintainability can be greatly enhanced by implementing techniques like OnPush change detection, trackBy in ngFor, structured feature modules, appropriate subscription management, and centralised error handling.<\/p>\n<p>The framework does not cause large Angular applications to fail. Architectural choices are the reason they fail.<\/p>\n<p>You can create systems that are quick, dependable, and maintainable even as they grow if you comprehend how Angular&#8217;s change detection, dependency injection, routing, and reactive patterns operate internally.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction There is no doubt that Angular is powerful. But after using Angular\u00a0 from v1 to v20 in real production systems, I found that most performance problems and messy codebases come from a small number of mistakes that keep happening. In this article, I&#8217;ll talk about what I&#8217;ve learned from my own projects that helped [&hellip;]<\/p>\n","protected":false},"author":1764,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0},"categories":[5876],"tags":[955,4117],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/77908"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1764"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=77908"}],"version-history":[{"count":7,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/77908\/revisions"}],"predecessor-version":[{"id":78082,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/77908\/revisions\/78082"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=77908"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=77908"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=77908"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}