1

I want to use Supabase authentication for my App and simply built this auth service:

import { Injectable } from '@angular/core';
import { AuthResponse, createClient } from '@supabase/supabase-js';
import { environment } from '../../environments/environment';
import { from, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  supabase = createClient(environment.supabaseUrl, environment.supabaseKey);

  register(email: string, password: string): Observable<AuthResponse> {
    const promise = this.supabase.auth.signUp({
      email,
      password
    })

    return from(promise);
  }

  login(email: string, password: string): Observable<AuthResponse> {
    const promise = this.supabase.auth.signInWithPassword({
      email,
      password
    });

    return from(promise);
  }
}

in the login component I call the login method from the auth service:

import { Component, inject } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth/auth.service';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './login.component.html',
  styleUrl: './login.component.css'
})
export class LoginComponent {
  authService = inject(AuthService);
  router = inject(Router);

  errorMessage: string | null = null;

  contactForm = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  handleSubmit() {
    const rawForm = this.contactForm.getRawValue();
    if (rawForm.email && rawForm.password) {
      this.authService
        .login(rawForm.email, rawForm.password)
        .subscribe((result) => {
          if (result.error) {
            this.errorMessage = result.error.message;
          } else {
            this.router.navigateByUrl('/');
          }
        });
    }
  }
}

I also enabled withFetch() for the HttpClient to improve possible SSR:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    provideHttpClient(withFetch()),
    importProvidersFrom(HttpClientModule)]
};

but when I call http://localhost:4200/login, I get this error:

[vite] Internal server error: Page /login did not render in 30 seconds.
      at Timeout.<anonymous> (C:\Users\kursp\Desktop\deluxe-detail\node_modules\@angular\build\src\utils\server-rendering\render-page.js:90:90)
      at Timeout.timer (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:2249:37)
      at _ZoneDelegate.invokeTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:398:33)
      at _ZoneImpl.runTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:158:47)
      at invokeTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:479:34)
      at Timeout.ZoneTask.invoke (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:468:48)
      at Timeout.data.args.<computed> (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:2231:32)
      at listOnTimeout (node:internal/timers:573:17)
      at process.processTimers (node:internal/timers:514:7)

I tried and added the withFetch() option for the providers in the config. I thought that might have some sort of interference with the SSR eventhough I use supabase in order to not need an explicit backend for authentication.

The actual result: Still the timeout error after 30 seconds, I assume Zone.js has some tasks/routines to finish up the connection but never gets to actually executing them --> being stuck on that

1
  • have you shared the full code for LoginComponent, do you have any code on ngOnInit or ngAfterViewInit Commented Jul 9 at 11:17

1 Answer 1

0

Probably the createSupabase initialization method creates a persistent connection to the given environment.supabaseUrl server given usage of realtime-js under the hood (it's mentioned in initialization docs)

When Angular is initializing the app, it will create the AuthService. And as part of that, it will therefore call createSupabase method to create the supabase attribute in AuthService. It will perform that initialization and create that persistent connection.

But that connection won't ever disconnect. Unless the proper API is called to do so. This means Angular's Zone.js will permanently have pending tasks to perform related to the open connection and therefore won't ever consider the app to be stable. And Angular needs the app to be stable (without any pending tasks) to be considered initialized.

To solve that, you can start the connection once Angular has finished initializing the application. To do so, you can create / initialize the Supabase client the app is stable by using APP_INITIALIZER DI token

This way, the app will init as usual. Then, when stable, the connection will be created with a permanent connection that will keep the app non stable. But that's fine at that point that everything's booted. It makes sense that the app is unstable at that point in time, given data is permanently incoming from the Supabase client until you close it.

For instance, this could be the updated AuthService code:

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  supabase!: ReturnType<typeof createClient>

  constructor() {
    inject(ApplicationRef)
      .isStable.pipe(first((isStable) => isStable))
      .subscribe(() => {
        this.supabase = createClient(
          environment.supabaseUrl,
          environment.supabaseKey,
        )
      })
  }
}

More info about this kind of unstable initialization issue in docs: https://angular.dev/errors/NG0506

1
  • Thanks alot! That did the trick. You saved me a day or two. Commented Jul 10 at 10:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.