> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# Zoneless Change Detection

> Build faster Angular applications without Zone.js using zoneless change detection

## Overview

Zoneless change detection eliminates the need for Zone.js, Angular's traditional mechanism for automatic change detection. This results in smaller bundle sizes, better performance, and more predictable behavior.

## Why Go Zoneless?

<CardGroup cols={2}>
  <Card title="Smaller Bundle Size" icon="file-zipper">
    Removing Zone.js can reduce your bundle size by \~30-50KB gzipped.
  </Card>

  <Card title="Better Performance" icon="rocket">
    More efficient change detection with reduced overhead from zone tracking.
  </Card>

  <Card title="Improved Debugging" icon="bug">
    Easier debugging without Zone.js patching async operations.
  </Card>

  <Card title="Modern Primitives" icon="atom">
    Works seamlessly with Signals and reactive patterns.
  </Card>
</CardGroup>

## Enabling Zoneless Mode

Use `provideZonelessChangeDetection()` to enable zoneless mode in your application.

### Standalone Application

```typescript theme={null}
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection()
  ]
}).catch(err => console.error(err));
```

### NgModule Application

```typescript theme={null}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [
    provideZonelessChangeDetection()
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
```

<Note>
  Once you enable zoneless mode, Zone.js is completely removed from your application. Make sure your code is compatible before making the switch.
</Note>

## How It Works

Instead of automatically detecting changes after every async operation, zoneless mode relies on explicit change detection triggers:

1. **Signals**: When signal values change
2. **Events**: DOM event handlers
3. **Async Pipe**: Observable emissions in templates
4. **Manual**: Explicit `ChangeDetectorRef` calls

```typescript theme={null}
import { Component, signal, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-zoneless-demo',
  template: `
    <div>
      <!-- ✅ Automatically updates (signal) -->
      <p>Count: {{ count() }}</p>
      <button (click)="increment()">Increment</button>

      <!-- ✅ Automatically updates (async pipe) -->
      <p>Time: {{ time$ | async }}</p>

      <!-- ❌ Won't update automatically -->
      <p>Manual: {{ manualValue }}</p>
      <button (click)="updateManual()">Update Manual</button>
    </div>
  `
})
export class ZonelessDemoComponent {
  // Signal-based value: updates automatically
  count = signal(0);
  
  // Observable: updates through async pipe
  time$ = interval(1000).pipe(
    map(() => new Date().toLocaleTimeString())
  );

  // Regular property: needs manual change detection
  manualValue = 0;

  constructor(private cdr: ChangeDetectorRef) {}

  increment(): void {
    // Signal update triggers change detection
    this.count.update(n => n + 1);
  }

  updateManual(): void {
    this.manualValue++;
    // Must manually trigger change detection
    this.cdr.markForCheck();
  }
}
```

## Migration Guide

### Step 1: Use Signals for State

Replace component properties with signals where possible.

```typescript theme={null}
// Before (Zone.js)
import { Component } from '@angular/core';

@Component({
  selector: 'app-user',
  template: `<p>{{ userName }}</p>`
})
export class UserComponent {
  userName = 'John';

  updateName(name: string): void {
    this.userName = name; // Automatically detected by Zone.js
  }
}

// After (Zoneless)
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-user',
  template: `<p>{{ userName() }}</p>`
})
export class UserComponent {
  userName = signal('John');

  updateName(name: string): void {
    this.userName.set(name); // Triggers change detection
  }
}
```

### Step 2: Use Async Pipe for Observables

Always use the async pipe in templates for observables.

```typescript theme={null}
// Before (manual subscription)
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: `<p>{{ data }}</p>`
})
export class DataComponent implements OnInit, OnDestroy {
  data: string = '';
  private subscription?: Subscription;

  constructor(private dataService: DataService) {}

  ngOnInit(): void {
    this.subscription = this.dataService.getData().subscribe(
      value => this.data = value
    );
  }

  ngOnDestroy(): void {
    this.subscription?.unsubscribe();
  }
}

// After (async pipe)
import { Component } from '@angular/core';
import { DataService } from './data.service';

@Component({
  selector: 'app-data',
  template: `<p>{{ data$ | async }}</p>`
})
export class DataComponent {
  data$ = this.dataService.getData();

  constructor(private dataService: DataService) {}
}
```

### Step 3: Handle Async Operations

For callbacks and third-party integrations, manually trigger change detection.

```typescript theme={null}
import { Component, signal, ChangeDetectorRef, OnInit } from '@angular/core';

@Component({
  selector: 'app-websocket',
  template: `
    <div>
      <p>Status: {{ status() }}</p>
      <p>Message: {{ lastMessage() }}</p>
    </div>
  `
})
export class WebSocketComponent implements OnInit {
  status = signal<'connected' | 'disconnected'>('disconnected');
  lastMessage = signal('');
  private ws?: WebSocket;

  constructor(private cdr: ChangeDetectorRef) {}

  ngOnInit(): void {
    this.ws = new WebSocket('wss://example.com/socket');

    this.ws.onopen = () => {
      // Using signals - no manual CD needed
      this.status.set('connected');
    };

    this.ws.onmessage = (event) => {
      // Using signals - no manual CD needed
      this.lastMessage.set(event.data);
    };
  }
}
```

### Step 4: Update Event Handlers

Event handlers in templates automatically trigger change detection.

```typescript theme={null}
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-form',
  template: `
    <div>
      <input 
        [value]="searchTerm()"
        (input)="onSearchChange($event)"
      >
      <button (click)="search()">Search</button>
      <p>Results: {{ resultsCount() }}</p>
    </div>
  `
})
export class FormComponent {
  searchTerm = signal('');
  resultsCount = signal(0);

  onSearchChange(event: Event): void {
    // Event handlers trigger change detection
    const value = (event.target as HTMLInputElement).value;
    this.searchTerm.set(value);
  }

  async search(): Promise<void> {
    const results = await this.performSearch(this.searchTerm());
    // Signal update triggers change detection
    this.resultsCount.set(results.length);
  }

  private async performSearch(term: string): Promise<any[]> {
    // Simulated async search
    return [];
  }
}
```

## Advanced Patterns

### Custom Change Detection Strategy

```typescript theme={null}
import { 
  Injectable, 
  ChangeDetectorRef, 
  ApplicationRef 
} from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CustomScheduler {
  private pending = new Set<ChangeDetectorRef>();
  private scheduled = false;

  constructor(private appRef: ApplicationRef) {}

  schedule(cdr: ChangeDetectorRef): void {
    this.pending.add(cdr);
    
    if (!this.scheduled) {
      this.scheduled = true;
      queueMicrotask(() => this.flush());
    }
  }

  private flush(): void {
    this.scheduled = false;
    const cdrs = Array.from(this.pending);
    this.pending.clear();
    
    cdrs.forEach(cdr => cdr.markForCheck());
    this.appRef.tick();
  }
}
```

### Zoneless Service with Signals

```typescript theme={null}
import { Injectable, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

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

@Injectable({ providedIn: 'root' })
export class UserService {
  private users = signal<User[]>([]);
  private loading = signal(false);
  private error = signal<string | null>(null);

  // Computed selectors
  readonly allUsers = this.users.asReadonly();
  readonly isLoading = this.loading.asReadonly();
  readonly errorMessage = this.error.asReadonly();
  
  readonly userCount = computed(() => this.users().length);
  readonly hasUsers = computed(() => this.users().length > 0);

  constructor(private http: HttpClient) {}

  async loadUsers(): Promise<void> {
    this.loading.set(true);
    this.error.set(null);

    try {
      const users = await this.http
        .get<User[]>('/api/users')
        .toPromise();
      
      this.users.set(users || []);
    } catch (err) {
      this.error.set('Failed to load users');
      console.error(err);
    } finally {
      this.loading.set(false);
    }
  }

  addUser(user: User): void {
    this.users.update(users => [...users, user]);
  }

  removeUser(id: number): void {
    this.users.update(users => users.filter(u => u.id !== id));
  }
}
```

## Testing Zoneless Applications

```typescript theme={null}
import { TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core';
import { CounterComponent } from './counter.component';

describe('CounterComponent (Zoneless)', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent],
      providers: [
        provideZonelessChangeDetection()
      ]
    }).compileComponents();
  });

  it('should increment counter', async () => {
    const fixture = TestBed.createComponent(CounterComponent);
    const component = fixture.componentInstance;
    
    // Initial value
    expect(component.count()).toBe(0);

    // Increment
    component.increment();
    
    // Wait for stability
    await fixture.whenStable();
    
    // Verify update
    expect(component.count()).toBe(1);
    expect(fixture.nativeElement.textContent).toContain('Count: 1');
  });
});
```

<Note>
  In zoneless mode, `fixture.detectChanges()` is deprecated. Use `await fixture.whenStable()` instead to wait for async updates.
</Note>

## Common Pitfalls

<Warning>
  Avoid modifying component state in third-party callbacks without using signals or manual change detection.
</Warning>

```typescript theme={null}
// ❌ Bad: Direct property mutation
export class BadComponent {
  data = '';

  ngOnInit(): void {
    someThirdPartyLib.onData((value) => {
      this.data = value; // Won't trigger change detection!
    });
  }
}

// ✅ Good: Use signals
export class GoodComponent {
  data = signal('');

  ngOnInit(): void {
    someThirdPartyLib.onData((value) => {
      this.data.set(value); // Triggers change detection
    });
  }
}
```

## Performance Optimization

Zoneless mode provides several performance benefits:

* **Reduced overhead**: No Zone.js patches on async operations
* **Predictable timing**: Change detection only runs when needed
* **Better tree-shaking**: Smaller final bundle without Zone.js
* **Efficient updates**: Fine-grained reactivity with signals

## Best Practices

<CardGroup cols={2}>
  <Card title="Prefer Signals" icon="signal">
    Use signals for all reactive state to get automatic change detection.
  </Card>

  <Card title="Use Async Pipe" icon="pipe">
    Always use async pipe for observables in templates.
  </Card>

  <Card title="Avoid SetTimeout" icon="clock">
    Replace setTimeout/setInterval with observables or signals.
  </Card>

  <Card title="Test Thoroughly" icon="vial">
    Test your application extensively when migrating to zoneless.
  </Card>
</CardGroup>

## Additional Resources

* [Signals Documentation](/advanced/signals)
* [Change Detection](/advanced/change-detection)
* [Angular Zoneless Guide](https://angular.dev/guide/experimental/zoneless)
