{"id":4061,"date":"2025-10-07T11:47:01","date_gmt":"2025-10-07T09:47:01","guid":{"rendered":"https:\/\/nguenkam.com\/blog\/?p=4061"},"modified":"2025-10-07T11:47:01","modified_gmt":"2025-10-07T09:47:01","slug":"zone-js-in-angular-from-foundation-to-the-zoneless-future","status":"publish","type":"post","link":"https:\/\/nguenkam.com\/blog\/index.php\/2025\/10\/07\/zone-js-in-angular-from-foundation-to-the-zoneless-future\/","title":{"rendered":"Zone.js in Angular: From Foundation to the Zoneless Future"},"content":{"rendered":"\n<p>With Angular 20.2, an important milestone has been reached: <strong>Zoneless Change Detection<\/strong> is now production-ready. But what does this mean for Angular developers? In this article, we&#8217;ll explain Zone.js from the ground up and show how the transition to zoneless architecture works.<\/p>\n\n\n\n<h4>What is Zone.js?<\/h4>\n\n\n\n<p>Zone.js is a JavaScript library that <strong>monitors and manages asynchronous operations<\/strong> in web applications. Think of Zone.js as a &#8220;detective&#8221; that observes all asynchronous activities in your application.<\/p>\n\n\n\n<h4>How Zone.js Works<\/h4>\n\n\n\n<p>Zone.js <strong>&#8220;patches&#8221;<\/strong> (overrides) native browser APIs such as:<\/p>\n\n\n\n<ul><li><code>setTimeout<\/code>&nbsp;and&nbsp;<code>setInterval<\/code><\/li><li><code>Promise<\/code><\/li><li><code>XMLHttpRequest<\/code>&nbsp;and&nbsp;<code>fetch<\/code><\/li><li>DOM Events<\/li><li><code>requestAnimationFrame<\/code><\/li><\/ul>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image.png\" alt=\"\" class=\"wp-image-4062\" width=\"439\" height=\"498\" srcset=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image.png 550w, https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-264x300.png 264w\" sizes=\"(max-width: 439px) 100vw, 439px\" \/><\/figure><\/div>\n\n\n\n<h4>Zone.js  VS Zoneless : Comparison <\/h4>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-1.png\" alt=\"\" class=\"wp-image-4063\" width=\"555\" height=\"334\" srcset=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-1.png 743w, https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-1-300x180.png 300w\" sizes=\"(max-width: 555px) 100vw, 555px\" \/><\/figure><\/div>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Detailed Code Examples<\/span><\/h4>\n\n\n\n<h4>1. Traditional Angular Component with Zone.js<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>import { Component } from '@angular\/core';\r\nimport { HttpClient } from '@angular\/common\/http';\r\n\r\n@Component({\r\n  selector: 'app-user-list',\r\n  template: `\r\n    &lt;div>\r\n      &lt;h2>Users ({{ users.length }})&lt;\/h2>\r\n      &lt;button (click)=\"loadUsers()\">Load Users&lt;\/button>\r\n      &lt;ul>\r\n        &lt;li *ngFor=\"let user of users\">{{ user.name }}&lt;\/li>\r\n      &lt;\/ul>\r\n    &lt;\/div>\r\n  `\r\n})\r\nexport class UserListComponent {\r\n  users: any&#91;] = &#91;];\r\n\r\n  constructor(private http: HttpClient) {}\r\n\r\n  loadUsers() {\r\n    \/\/ Zone.js automatically detects the HTTP request\r\n    \/\/ and triggers Change Detection after the response\r\n    this.http.get&lt;any&#91;]>('\/api\/users').subscribe(users => {\r\n      this.users = users; \/\/ Change Detection runs automatically\r\n    });\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<p><strong>What happens here with Zone.js:<\/strong><\/p>\n\n\n\n<ol><li>Button click ? Zone.js detects DOM event<\/li><li>HTTP request ? Zone.js patches&nbsp;<code>XMLHttpRequest<\/code><\/li><li>Response arrives ? Zone.js triggers Change Detection<\/li><li><code>users<\/code>&nbsp;array is updated ? DOM is re-rendered<\/li><\/ol>\n\n\n\n<h4>2. Migration to OnPush (Preparation for Zoneless)<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>import { Component, ChangeDetectionStrategy, inject, signal } from '@angular\/core';\r\nimport { HttpClient } from '@angular\/common\/http';\r\n\r\n@Component({\r\n  selector: 'app-user-list',\r\n  changeDetection: ChangeDetectionStrategy.OnPush,\r\n  template: `\r\n    &lt;div>\r\n      &lt;h2>Users ({{ users().length }})&lt;\/h2>\r\n      &lt;button (click)=\"loadUsers()\">Load Users&lt;\/button>\r\n      &lt;ul>\r\n        &lt;li *ngFor=\"let user of users()\">{{ user.name }}&lt;\/li>\r\n      &lt;\/ul>\r\n      &lt;div *ngIf=\"loading()\">Loading...&lt;\/div>\r\n    &lt;\/div>\r\n  `\r\n})\r\nexport class UserListComponent {\r\n  private http = inject(HttpClient);\r\n  \r\n  \/\/ Signals for reactive state management\r\n  users = signal&lt;any&#91;]>(&#91;]);\r\n  loading = signal(false);\r\n\r\n  loadUsers() {\r\n    this.loading.set(true);\r\n    \r\n    this.http.get&lt;any&#91;]>('\/api\/users').subscribe(users => {\r\n      this.users.set(users); \/\/ Signal update automatically triggers Change Detection\r\n      this.loading.set(false);\r\n    });\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<h4>3. Fully Zoneless with provideZonelessChangeDetection<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ main.ts\r\nimport { bootstrapApplication } from '@angular\/platform-browser';\r\nimport { provideZonelessChangeDetection } from '@angular\/core';\r\nimport { AppComponent } from '.\/app\/app.component';\r\n\r\nbootstrapApplication(AppComponent, {\r\n  providers: &#91;\r\n    provideZonelessChangeDetection(), \/\/ Enable zoneless\r\n    \/\/ other providers...\r\n  ]\r\n});\r<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ user-list.component.ts\r\nimport { Component, ChangeDetectionStrategy, inject, signal, effect } from '@angular\/core';\r\nimport { HttpClient } from '@angular\/common\/http';\r\n\r\n@Component({\r\n  selector: 'app-user-list',\r\n  changeDetection: ChangeDetectionStrategy.OnPush,\r\n  template: `\r\n    &lt;div>\r\n      &lt;h2>Users ({{ users().length }})&lt;\/h2>\r\n      &lt;button (click)=\"loadUsers()\">Load Users&lt;\/button>\r\n      \r\n      &lt;!-- Async pipe also works zoneless -->\r\n      &lt;div *ngIf=\"users$ | async as userList\">\r\n        &lt;ul>\r\n          &lt;li *ngFor=\"let user of userList\">{{ user.name }}&lt;\/li>\r\n        &lt;\/ul>\r\n      &lt;\/div>\r\n      \r\n      &lt;div *ngIf=\"loading()\">Loading...&lt;\/div>\r\n    &lt;\/div>\r\n  `\r\n})\r\nexport class UserListComponent {\r\n  private http = inject(HttpClient);\r\n  \r\n  users = signal&lt;any&#91;]>(&#91;]);\r\n  loading = signal(false);\r\n  users$ = this.http.get&lt;any&#91;]>('\/api\/users'); \/\/ Observable for async pipe\r\n\r\n  constructor() {\r\n    \/\/ Effect for side effects\r\n    effect(() => {\r\n      console.log(`Number of users: ${this.users().length}`);\r\n    });\r\n  }\r\n\r\n  loadUsers() {\r\n    this.loading.set(true);\r\n    \r\n    this.http.get&lt;any&#91;]>('\/api\/users').subscribe({\r\n      next: users => {\r\n        this.users.set(users);\r\n        this.loading.set(false);\r\n      },\r\n      error: () => {\r\n        this.loading.set(false);\r\n      }\r\n    });\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Migration Step-by-Step<\/span><\/h4>\n\n\n\n<h4>Step 1: Convert Components to OnPush<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Before: Default Change Detection\r\n@Component({\r\n  selector: 'app-example',\r\n  template: `&lt;div>{{ data }}&lt;\/div>`\r\n})\r\nexport class ExampleComponent {\r\n  data = 'initial';\r\n  \r\n  updateData() {\r\n    setTimeout(() => {\r\n      this.data = 'updated'; \/\/ Works with Zone.js\r\n    }, 1000);\r\n  }\r\n}\r\n\r\n\/\/ After: OnPush with Signals\r\n@Component({\r\n  selector: 'app-example',\r\n  changeDetection: ChangeDetectionStrategy.OnPush,\r\n  template: `&lt;div>{{ data() }}&lt;\/div>`\r\n})\r\nexport class ExampleComponent {\r\n  data = signal('initial');\r\n  \r\n  updateData() {\r\n    setTimeout(() => {\r\n      this.data.set('updated'); \/\/ Signal triggers Change Detection\r\n    }, 1000);\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<h4>Step 2: Remove Zone.js<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ angular.json - Remove Zone.js\r\n{\r\n  \"projects\": {\r\n    \"my-app\": {\r\n      \"architect\": {\r\n        \"build\": {\r\n          \"options\": {\r\n            \"polyfills\": &#91;\r\n              \/\/ \"zone.js\" &lt;- Remove this line\r\n            ]\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<h4>Step 3: Enable Zoneless Change Detection<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ main.ts\r\nimport { bootstrapApplication } from '@angular\/platform-browser';\r\nimport { provideZonelessChangeDetection } from '@angular\/core';\r\n\r\nbootstrapApplication(AppComponent, {\r\n  providers: &#91;\r\n    provideZonelessChangeDetection(),\r\n    \/\/ other providers\r\n  ]\r\n});\r<\/code><\/pre>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Common Pitfalls and Solutions<\/span><\/h4>\n\n\n\n<h4>Problem: Third-party Libraries<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Problematic: Library uses setTimeout without Signal update\r\nsomeLibrary.doAsyncWork().then(result => {\r\n  this.result = result; \/\/ No Change Detection!\r\n});\r\n\r\n\/\/ Solution: Manually trigger Change Detection\r\nimport { ChangeDetectorRef, inject } from '@angular\/core';\r\n\r\nexport class MyComponent {\r\n  private cdr = inject(ChangeDetectorRef);\r\n  \r\n  async doWork() {\r\n    const result = await someLibrary.doAsyncWork();\r\n    this.result = result;\r\n    this.cdr.markForCheck(); \/\/ Manually trigger Change Detection\r\n  }\r\n}\r\n\r\n\/\/ Better solution: Use Signals\r\nexport class MyComponent {\r\n  result = signal(null);\r\n  \r\n  async doWork() {\r\n    const result = await someLibrary.doAsyncWork();\r\n    this.result.set(result); \/\/ Automatic Change Detection\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<h4>When Should we Switch to Zoneless?<\/h4>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-5.png\" alt=\"\" class=\"wp-image-4067\" width=\"357\" height=\"33\" srcset=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-5.png 368w, https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-5-300x28.png 300w\" sizes=\"(max-width: 357px) 100vw, 357px\" \/><\/figure>\n\n\n\n<ul><li><strong>New projects<\/strong>&nbsp;with Angular 18+<\/li><li><strong>Performance-critical applications<\/strong><\/li><li><strong>Mobile apps<\/strong>&nbsp;(smaller bundle size)<\/li><li><strong>Projects with modern Angular patterns<\/strong>&nbsp;(Signals, OnPush)<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" src=\"https:\/\/nguenkam.com\/blog\/wp-content\/uploads\/2025\/10\/image-6.png\" alt=\"\" class=\"wp-image-4068\" width=\"218\" height=\"34\"\/><\/figure>\n\n\n\n<ul><li><strong>Legacy applications<\/strong>&nbsp;with many Default Change Detection components<\/li><li><strong>Projects with many third-party libraries<\/strong>&nbsp;of unknown compatibility<\/li><li><strong>Teams without experience<\/strong>&nbsp;with OnPush and Signals<\/li><\/ul>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">New Angular CLI Option<\/span><\/h4>\n\n\n\n<p>With Angular 20.2, you can create a zoneless project directly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># New zoneless project\r\nng new my-zoneless-app --zoneless\r\n\r\n# Or for existing projects\r\nng add @angular\/core --zoneless\r<\/code><\/pre>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Advanced Zoneless Patterns<\/span><\/h4>\n\n\n\n<h4>Using Signals with Computed Values<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>import { Component, signal, computed } from '@angular\/core';\r\n\r\n@Component({\r\n  selector: 'app-shopping-cart',\r\n  template: `\r\n    &lt;div>\r\n      &lt;h2>Shopping Cart&lt;\/h2>\r\n      &lt;p>Items: {{ itemCount() }}&lt;\/p>\r\n      &lt;p>Total: ${{ totalPrice() }}&lt;\/p>\r\n      &lt;button (click)=\"addItem()\">Add Item&lt;\/button>\r\n    &lt;\/div>\r\n  `\r\n})\r\nexport class ShoppingCartComponent {\r\n  private items = signal(&#91;\r\n    { name: 'Apple', price: 1.50 },\r\n    { name: 'Banana', price: 0.75 }\r\n  ]);\r\n\r\n  \/\/ Computed signals automatically update when dependencies change\r\n  itemCount = computed(() => this.items().length);\r\n  totalPrice = computed(() => \r\n    this.items().reduce((sum, item) => sum + item.price, 0)\r\n  );\r\n\r\n  addItem() {\r\n    this.items.update(items => &#91;\r\n      ...items,\r\n      { name: 'Orange', price: 2.00 }\r\n    ]);\r\n    \/\/ Both itemCount() and totalPrice() will automatically update\r\n  }\r\n}\r<\/code><\/pre>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Migration Checklist<\/span><\/h4>\n\n\n\n<h4>Pre-Migration Assessment<\/h4>\n\n\n\n<ul><li>&nbsp;<strong>Audit Components<\/strong>: Identify components using Default Change Detection<\/li><li>&nbsp;<strong>Library Compatibility<\/strong>: Check if third-party libraries support zoneless<\/li><li>&nbsp;<strong>Test Coverage<\/strong>: Ensure comprehensive tests before migration<\/li><li>&nbsp;<strong>Team Training<\/strong>: Educate team on Signals and OnPush patterns<\/li><\/ul>\n\n\n\n<h4>Migration Steps<\/h4>\n\n\n\n<ul><li>&nbsp;<strong>Step 1<\/strong>: Convert components to OnPush strategy<\/li><li>&nbsp;<strong>Step 2<\/strong>: Replace direct property assignments with Signals<\/li><li>&nbsp;<strong>Step 3<\/strong>: Update event handlers to use Signal updates<\/li><li>&nbsp;<strong>Step 4<\/strong>: Test thoroughly with Zone.js still enabled<\/li><li>&nbsp;<strong>Step 5<\/strong>: Remove Zone.js from polyfills<\/li><li>&nbsp;<strong>Step 6<\/strong>: Enable&nbsp;<code>provideZonelessChangeDetection()<\/code><\/li><li>&nbsp;<strong>Step 7<\/strong>: Final testing and performance validation<\/li><\/ul>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h4><span class=\"has-inline-color has-vivid-cyan-blue-color\">Conclusion<\/span><\/h4>\n\n\n\n<p>Zone.js has been a crucial building block for Angular&#8217;s success by automating Change Detection. With the introduction of <strong>Signals<\/strong> and <strong>Zoneless Change Detection<\/strong> in Angular 20.2, developers now have a choice between:<\/p>\n\n\n\n<ul><li><strong>Simplicity<\/strong>&nbsp;(Zone.js) &#8211; Automatic, but with performance overhead<\/li><li><strong>Performance<\/strong>&nbsp;(Zoneless) &#8211; Optimized, but requires more conscious programming<\/li><\/ul>\n\n\n\n<p>The future of Angular is clearly moving towards Zoneless, but the transition is gradual and developer-friendly. Zone.js will continue to be supported, so existing applications don&#8217;t need to migrate immediately.<\/p>\n\n\n\n<p><strong>Recommendation:<\/strong> Start new projects with Zoneless and migrate existing applications gradually when the benefits justify the effort. The improved performance, smaller bundle size, and cleaner debugging experience make Zoneless an attractive option for modern Angular applications.<\/p>\n\n\n\n<p>The transition to Zoneless represents Angular&#8217;s evolution towards more explicit, performant, and maintainable code patterns. While it requires developers to be more intentional about state management, the benefits in terms of performance and debugging make it a worthwhile investment for the future.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With Angular 20.2, an important milestone has been reached: Zoneless Change Detection is now production-ready. But what does this mean for Angular developers? In this article, we&#8217;ll explain Zone.js from the ground up and show how the transition to zoneless architecture works. What is Zone.js? Zone.js is a JavaScript library that monitors and manages asynchronous [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1965,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[37],"tags":[1063,1070,1071,1068,1062,1066,1064,128,803,1065,1067,1060,1061],"_links":{"self":[{"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/4061"}],"collection":[{"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=4061"}],"version-history":[{"count":1,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/4061\/revisions"}],"predecessor-version":[{"id":4069,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/posts\/4061\/revisions\/4069"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/media\/1965"}],"wp:attachment":[{"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=4061"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=4061"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nguenkam.com\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=4061"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}