Working with Zoneless Change Detection in Angular

June 24, 2025

When adding Supabase to my WebGPU game, I ran into this annoying error: "Acquiring an exclusive Navigator LockManager immediately failed".

After a quick google search, the solution turns out to be to disable change detection using zone.js. See here for more details.

For better or worse, this changes the way that components are normally written in Angular.

Zoneless Change Detection (ZCD) grants more control over when components are updated, but it also requires the developer to explicitly invoke change detection where it is required, resulting in much more verbose code.

Fortunately, once you get used to this, it's relatively painless, just expect to be calling this.changeRef.detectChanges() a lot.

Angular App Configuration

export const appConfig: ApplicationConfig = {
  providers: [provideExperimentalZonelessChangeDetection(), provideRouter(routes)]
};

Example Simple Authentication Component using ZCD

import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'
import { SupabaseService } from '../supabase.service'
import { Router } from '@angular/router';

@Component({
  selector: 'app-auth',
  templateUrl: './auth.component.html',
  styleUrls: ['./auth.component.css'],
  imports: [ReactiveFormsModule],
  changeDetection: ChangeDetectionStrategy.Default
})
export class AuthComponent {
  loading = false
  loginForm: FormGroup = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  constructor(
    private readonly supabase: SupabaseService,
    private readonly changeRef: ChangeDetectorRef,
    private readonly router: Router
  ) {
  }

  async onSubmit(): Promise<void> {
    try {
      this.loading = true
      const email = this.loginForm.value.email as string
      const password = this.loginForm.value.password;

      const { data, error } = await this.supabase.signUpNewUser(email, password)
      if (data.user?.identities?.length == 0) //kinda hack to check if already signed up
        await this.supabase.signIn(email, password);
      else
        alert("CHECK YOUR EMAIL FOR CONFIRMATION")

      if (error) throw error
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.loginForm.reset()
      this.loading = false

      this.changeRef.detectChanges();
    }
  }

  ngOnInit() {
    this.supabase.authChanges((event, session) => {
      console.log(event, session);
      if (event == 'SIGNED_IN')
        this.router.navigate(['/account']);
      
      this.changeRef.detectChanges();
    })
  }
}