# How to Use Supabase with Angular

- Tool: Supabase
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Supabase (all plans), @supabase/supabase-js v2+, Angular 16+
- Last updated: March 2026

## TL;DR

To use Supabase with Angular, install @supabase/supabase-js, create an Angular service that initializes the Supabase client, and inject it into your components. Use environment files for the Supabase URL and anon key. The service wraps auth, database, and storage operations as observables, giving you reactive data flows that integrate naturally with Angular's change detection.

## Building an Angular App with Supabase as the Backend

This tutorial shows you how to integrate Supabase into an Angular application. You will create a dedicated Angular service that initializes the Supabase client and exposes methods for authentication and database operations. By wrapping Supabase in a service, you get dependency injection, testability, and a clean separation between your UI components and backend logic.

## Before you start

- Angular CLI installed (ng version 16+)
- A Supabase project with your URL and anon key
- Node.js 18+ installed
- Basic familiarity with Angular services and dependency injection

## Step-by-step guide

### 1. Install the Supabase JavaScript client

Add the @supabase/supabase-js package to your Angular project. This is the official Supabase client library that works in any JavaScript environment, including Angular. It provides methods for auth, database queries, storage, and realtime subscriptions.

```
npm install @supabase/supabase-js
```

**Expected result:** The @supabase/supabase-js package is added to your package.json dependencies.

### 2. Add Supabase credentials to Angular environment files

Angular uses environment files to store configuration that varies between development and production. Add your Supabase URL and anon key to src/environments/environment.ts. The anon key is safe for client-side use because it respects Row Level Security. Never put the service role key in Angular code — it bypasses all security.

```
// src/environments/environment.ts
export const environment = {
  production: false,
  supabaseUrl: 'https://your-project-ref.supabase.co',
  supabaseAnonKey: 'your-anon-key-here'
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  supabaseUrl: 'https://your-project-ref.supabase.co',
  supabaseAnonKey: 'your-anon-key-here'
};
```

**Expected result:** Your Supabase credentials are stored in the environment files and accessible throughout the app.

### 3. Create an Angular Supabase service

Generate a service using the Angular CLI and initialize the Supabase client inside it. The service uses providedIn: 'root' so it is a singleton available throughout the app. Expose the client directly for simple use cases, or wrap common operations in typed methods for better developer experience. The auth state listener converts Supabase's callback-based onAuthStateChange into an observable.

```
// src/app/services/supabase.service.ts
import { Injectable } from '@angular/core';
import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
import { BehaviorSubject } from 'rxjs';
import { environment } from '../../environments/environment';

@Injectable({ providedIn: 'root' })
export class SupabaseService {
  private supabase: SupabaseClient;
  private currentUser = new BehaviorSubject<User | null>(null);
  currentUser$ = this.currentUser.asObservable();

  constructor() {
    this.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseAnonKey
    );

    this.supabase.auth.onAuthStateChange((event, session) => {
      this.currentUser.next(session?.user ?? null);
    });
  }

  get client(): SupabaseClient {
    return this.supabase;
  }
}
```

**Expected result:** An injectable SupabaseService that initializes the client and emits auth state changes as an observable.

### 4. Add authentication methods to the service

Extend the service with methods for sign up, sign in, and sign out. These wrap the Supabase auth methods and return promises that your components can await. The signUp method passes email and password to Supabase and returns the user data or an error. The signOut method clears the session and the BehaviorSubject automatically emits null via the auth state listener.

```
// Add to SupabaseService class
async signUp(email: string, password: string) {
  const { data, error } = await this.supabase.auth.signUp({
    email,
    password
  });
  if (error) throw error;
  return data;
}

async signIn(email: string, password: string) {
  const { data, error } = await this.supabase.auth.signInWithPassword({
    email,
    password
  });
  if (error) throw error;
  return data;
}

async signOut() {
  const { error } = await this.supabase.auth.signOut();
  if (error) throw error;
}
```

**Expected result:** The service exposes signUp, signIn, and signOut methods that components can call directly.

### 5. Add CRUD methods for database operations

Add typed methods that perform common database operations. These methods call the Supabase client internally and return the data or throw on error. You can define TypeScript interfaces for your table rows to get full type safety. Remember that all operations go through RLS — if your table has RLS enabled, only rows permitted by your policies will be returned or modified.

```
// Add interface and methods to SupabaseService
interface Todo {
  id: number;
  title: string;
  is_complete: boolean;
  user_id: string;
}

async getTodos(): Promise<Todo[]> {
  const { data, error } = await this.supabase
    .from('todos')
    .select('*')
    .order('id', { ascending: true });
  if (error) throw error;
  return data as Todo[];
}

async addTodo(title: string): Promise<Todo> {
  const { data, error } = await this.supabase
    .from('todos')
    .insert({ title, is_complete: false })
    .select()
    .single();
  if (error) throw error;
  return data as Todo;
}

async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {
  const { data, error } = await this.supabase
    .from('todos')
    .update(updates)
    .eq('id', id)
    .select()
    .single();
  if (error) throw error;
  return data as Todo;
}
```

**Expected result:** The service has typed methods for reading, creating, and updating todos that components can call.

### 6. Use the service in an Angular component

Inject the SupabaseService into a component and use its methods to load and display data. Subscribe to currentUser$ to reactively show or hide content based on auth state. Call the CRUD methods from event handlers. Angular's async pipe works well with the observable-based auth state, while the database methods return promises you can await in component methods.

```
// src/app/components/todo-list/todo-list.component.ts
import { Component, OnInit } from '@angular/core';
import { SupabaseService } from '../../services/supabase.service';

@Component({
  selector: 'app-todo-list',
  template: `
    <div *ngIf="(supabaseService.currentUser$ | async) as user; else login">
      <h2>Todos for {{ user.email }}</h2>
      <ul>
        <li *ngFor="let todo of todos">
          {{ todo.title }} - {{ todo.is_complete ? 'Done' : 'Pending' }}
        </li>
      </ul>
    </div>
    <ng-template #login>
      <p>Please sign in to see your todos.</p>
    </ng-template>
  `
})
export class TodoListComponent implements OnInit {
  todos: any[] = [];

  constructor(public supabaseService: SupabaseService) {}

  async ngOnInit() {
    this.todos = await this.supabaseService.getTodos();
  }
}
```

**Expected result:** The component displays the authenticated user's todos, fetched from Supabase through the injected service.

## Complete code example

File: `supabase.service.ts`

```typescript
import { Injectable } from '@angular/core';
import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
import { BehaviorSubject } from 'rxjs';
import { environment } from '../../environments/environment';

export interface Todo {
  id: number;
  title: string;
  is_complete: boolean;
  user_id: string;
}

@Injectable({ providedIn: 'root' })
export class SupabaseService {
  private supabase: SupabaseClient;
  private currentUser = new BehaviorSubject<User | null>(null);
  currentUser$ = this.currentUser.asObservable();

  constructor() {
    this.supabase = createClient(
      environment.supabaseUrl,
      environment.supabaseAnonKey
    );

    this.supabase.auth.onAuthStateChange((event, session) => {
      this.currentUser.next(session?.user ?? null);
    });
  }

  get client(): SupabaseClient {
    return this.supabase;
  }

  async signUp(email: string, password: string) {
    const { data, error } = await this.supabase.auth.signUp({ email, password });
    if (error) throw error;
    return data;
  }

  async signIn(email: string, password: string) {
    const { data, error } = await this.supabase.auth.signInWithPassword({ email, password });
    if (error) throw error;
    return data;
  }

  async signOut() {
    const { error } = await this.supabase.auth.signOut();
    if (error) throw error;
  }

  async getTodos(): Promise<Todo[]> {
    const { data, error } = await this.supabase
      .from('todos')
      .select('*')
      .order('id', { ascending: true });
    if (error) throw error;
    return data as Todo[];
  }

  async addTodo(title: string): Promise<Todo> {
    const { data, error } = await this.supabase
      .from('todos')
      .insert({ title, is_complete: false })
      .select()
      .single();
    if (error) throw error;
    return data as Todo;
  }

  async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {
    const { data, error } = await this.supabase
      .from('todos')
      .update(updates)
      .eq('id', id)
      .select()
      .single();
    if (error) throw error;
    return data as Todo;
  }

  async deleteTodo(id: number) {
    const { error } = await this.supabase
      .from('todos')
      .delete()
      .eq('id', id);
    if (error) throw error;
  }
}
```

## Common mistakes

- **Creating multiple Supabase client instances by calling createClient in each component instead of using a shared service** — undefined Fix: Always create the Supabase client once in a singleton service (providedIn: 'root') and inject it into components. Multiple instances can cause auth state mismatches.
- **Putting the Supabase service role key in Angular environment files, exposing it in the browser bundle** — undefined Fix: Only use the anon key in client-side Angular code. The service role key bypasses RLS and must only be used in server-side environments like Edge Functions.
- **Not subscribing to auth state changes, causing the UI to not react when users sign in or out** — undefined Fix: Use the BehaviorSubject pattern and subscribe to currentUser$ in your components using the async pipe or manual subscriptions.
- **Forgetting to enable RLS on tables, leaving data publicly accessible through the API** — undefined Fix: Always enable RLS on every table and write explicit policies. Without RLS, any user with the anon key can read and modify all data.

## Best practices

- Create a single SupabaseService with providedIn: 'root' to ensure one client instance across the app
- Store Supabase credentials in Angular environment files and swap them at build time for production
- Use BehaviorSubject to expose auth state as an observable that works with Angular's async pipe
- Generate TypeScript types from your Supabase schema using supabase gen types for full type safety
- Always enable RLS on tables and write policies before exposing data through the client
- Handle errors in service methods by throwing — let components decide how to display errors to users
- Use Angular route guards with the auth state observable to protect routes from unauthenticated access
- Unsubscribe from observables in ngOnDestroy to prevent memory leaks

## Frequently asked questions

### Can I use Supabase with Angular standalone components?

Yes. The SupabaseService with providedIn: 'root' works with both NgModule-based and standalone component architectures. Inject it directly into standalone components the same way.

### How do I handle Supabase auth redirects in Angular?

For OAuth flows, configure the redirectTo parameter to point to a route in your Angular app. In that route's component, call supabase.auth.getSession() to retrieve the session from the URL hash fragment.

### Should I use Supabase realtime subscriptions in Angular?

Yes. You can subscribe to realtime changes in the SupabaseService and emit updates through a Subject or BehaviorSubject that components observe. Remember to unsubscribe from the Supabase channel in the service's ngOnDestroy.

### How do I protect Angular routes based on Supabase auth?

Create an Angular route guard that checks the currentUser$ observable. If the user is null, redirect to the login page. Use canActivate in your route configuration.

### Does Supabase support Angular Universal (SSR)?

Yes, but you need to handle the client differently on the server. Use @supabase/ssr for server-side rendering and ensure you do not access browser APIs like localStorage during server-side execution.

### Can RapidDev help build a full Angular app with Supabase?

Yes. RapidDev can help you architect and build production Angular applications with Supabase, including auth flows, database design, RLS policies, and deployment configuration.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-angular
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-use-supabase-with-angular
