Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Use Supabase with Angular

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.

What you'll learn

  • How to create a reusable Supabase service in Angular
  • How to handle auth state changes with observables
  • How to perform CRUD operations from Angular components
  • How to configure environment variables for Supabase in Angular
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner8 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+, Angular 16+March 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

typescript
1npm 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.

typescript
1// src/environments/environment.ts
2export const environment = {
3 production: false,
4 supabaseUrl: 'https://your-project-ref.supabase.co',
5 supabaseAnonKey: 'your-anon-key-here'
6};
7
8// src/environments/environment.prod.ts
9export const environment = {
10 production: true,
11 supabaseUrl: 'https://your-project-ref.supabase.co',
12 supabaseAnonKey: 'your-anon-key-here'
13};

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.

typescript
1// src/app/services/supabase.service.ts
2import { Injectable } from '@angular/core';
3import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
4import { BehaviorSubject } from 'rxjs';
5import { environment } from '../../environments/environment';
6
7@Injectable({ providedIn: 'root' })
8export class SupabaseService {
9 private supabase: SupabaseClient;
10 private currentUser = new BehaviorSubject<User | null>(null);
11 currentUser$ = this.currentUser.asObservable();
12
13 constructor() {
14 this.supabase = createClient(
15 environment.supabaseUrl,
16 environment.supabaseAnonKey
17 );
18
19 this.supabase.auth.onAuthStateChange((event, session) => {
20 this.currentUser.next(session?.user ?? null);
21 });
22 }
23
24 get client(): SupabaseClient {
25 return this.supabase;
26 }
27}

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.

typescript
1// Add to SupabaseService class
2async signUp(email: string, password: string) {
3 const { data, error } = await this.supabase.auth.signUp({
4 email,
5 password
6 });
7 if (error) throw error;
8 return data;
9}
10
11async signIn(email: string, password: string) {
12 const { data, error } = await this.supabase.auth.signInWithPassword({
13 email,
14 password
15 });
16 if (error) throw error;
17 return data;
18}
19
20async signOut() {
21 const { error } = await this.supabase.auth.signOut();
22 if (error) throw error;
23}

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.

typescript
1// Add interface and methods to SupabaseService
2interface Todo {
3 id: number;
4 title: string;
5 is_complete: boolean;
6 user_id: string;
7}
8
9async getTodos(): Promise<Todo[]> {
10 const { data, error } = await this.supabase
11 .from('todos')
12 .select('*')
13 .order('id', { ascending: true });
14 if (error) throw error;
15 return data as Todo[];
16}
17
18async addTodo(title: string): Promise<Todo> {
19 const { data, error } = await this.supabase
20 .from('todos')
21 .insert({ title, is_complete: false })
22 .select()
23 .single();
24 if (error) throw error;
25 return data as Todo;
26}
27
28async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {
29 const { data, error } = await this.supabase
30 .from('todos')
31 .update(updates)
32 .eq('id', id)
33 .select()
34 .single();
35 if (error) throw error;
36 return data as Todo;
37}

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.

typescript
1// src/app/components/todo-list/todo-list.component.ts
2import { Component, OnInit } from '@angular/core';
3import { SupabaseService } from '../../services/supabase.service';
4
5@Component({
6 selector: 'app-todo-list',
7 template: `
8 <div *ngIf="(supabaseService.currentUser$ | async) as user; else login">
9 <h2>Todos for {{ user.email }}</h2>
10 <ul>
11 <li *ngFor="let todo of todos">
12 {{ todo.title }} - {{ todo.is_complete ? 'Done' : 'Pending' }}
13 </li>
14 </ul>
15 </div>
16 <ng-template #login>
17 <p>Please sign in to see your todos.</p>
18 </ng-template>
19 `
20})
21export class TodoListComponent implements OnInit {
22 todos: any[] = [];
23
24 constructor(public supabaseService: SupabaseService) {}
25
26 async ngOnInit() {
27 this.todos = await this.supabaseService.getTodos();
28 }
29}

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

Complete working example

supabase.service.ts
1import { Injectable } from '@angular/core';
2import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
3import { BehaviorSubject } from 'rxjs';
4import { environment } from '../../environments/environment';
5
6export interface Todo {
7 id: number;
8 title: string;
9 is_complete: boolean;
10 user_id: string;
11}
12
13@Injectable({ providedIn: 'root' })
14export class SupabaseService {
15 private supabase: SupabaseClient;
16 private currentUser = new BehaviorSubject<User | null>(null);
17 currentUser$ = this.currentUser.asObservable();
18
19 constructor() {
20 this.supabase = createClient(
21 environment.supabaseUrl,
22 environment.supabaseAnonKey
23 );
24
25 this.supabase.auth.onAuthStateChange((event, session) => {
26 this.currentUser.next(session?.user ?? null);
27 });
28 }
29
30 get client(): SupabaseClient {
31 return this.supabase;
32 }
33
34 async signUp(email: string, password: string) {
35 const { data, error } = await this.supabase.auth.signUp({ email, password });
36 if (error) throw error;
37 return data;
38 }
39
40 async signIn(email: string, password: string) {
41 const { data, error } = await this.supabase.auth.signInWithPassword({ email, password });
42 if (error) throw error;
43 return data;
44 }
45
46 async signOut() {
47 const { error } = await this.supabase.auth.signOut();
48 if (error) throw error;
49 }
50
51 async getTodos(): Promise<Todo[]> {
52 const { data, error } = await this.supabase
53 .from('todos')
54 .select('*')
55 .order('id', { ascending: true });
56 if (error) throw error;
57 return data as Todo[];
58 }
59
60 async addTodo(title: string): Promise<Todo> {
61 const { data, error } = await this.supabase
62 .from('todos')
63 .insert({ title, is_complete: false })
64 .select()
65 .single();
66 if (error) throw error;
67 return data as Todo;
68 }
69
70 async updateTodo(id: number, updates: Partial<Todo>): Promise<Todo> {
71 const { data, error } = await this.supabase
72 .from('todos')
73 .update(updates)
74 .eq('id', id)
75 .select()
76 .single();
77 if (error) throw error;
78 return data as Todo;
79 }
80
81 async deleteTodo(id: number) {
82 const { error } = await this.supabase
83 .from('todos')
84 .delete()
85 .eq('id', id);
86 if (error) throw error;
87 }
88}

Common mistakes when using Supabase with Angular

Why it's a problem: Creating multiple Supabase client instances by calling createClient in each component instead of using a shared service

How to avoid: Always create the Supabase client once in a singleton service (providedIn: 'root') and inject it into components. Multiple instances can cause auth state mismatches.

Why it's a problem: Putting the Supabase service role key in Angular environment files, exposing it in the browser bundle

How to avoid: 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.

Why it's a problem: Not subscribing to auth state changes, causing the UI to not react when users sign in or out

How to avoid: Use the BehaviorSubject pattern and subscribe to currentUser$ in your components using the async pipe or manual subscriptions.

Why it's a problem: Forgetting to enable RLS on tables, leaving data publicly accessible through the API

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I want to build an Angular 17 app with Supabase as the backend. Show me how to create a SupabaseService that handles auth and CRUD operations on a 'todos' table, with proper TypeScript types and observable auth state.

Supabase Prompt

Help me set up Supabase in my Angular project. I need a service that initializes the client, provides sign in and sign out methods, and lets me query a todos table. Show me the environment configuration and a component that uses the service.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.