# Use SupaHost with Angular

> Connect an Angular app to a SupaHost project with supabase-js. Environment files, an injectable Supabase service with a session signal, a component that queries a table, sign-in and a route guard.

Source: https://supahost.app/docs/getting-started/angular · Updated 2026-11-11

An Angular app runs in the browser and talks to your SupaHost project with supabase-js and the public `anon` key. Wrapping the client in an injectable service keeps one instance for the whole app and gives components a simple way to read the current session.

The examples use standalone components, signals and the `recipes` table from the [quickstart](/docs/getting-started/quickstart).

## Install

```bash
npx @angular/cli@latest new recipe-box
cd recipe-box
npm install @supabase/supabase-js
npx ng generate environments
```

## Add your project keys

Put the Project URL and `anon` key from the **Connect** tab (subtab **App**) in your environment files:

```ts
// src/environments/environment.development.ts
export const environment = {
  supabaseUrl: 'https://app-one.acme.supahost.space',
  supabaseAnonKey: 'eyJhbGciOiJIUzI1NiIs...',
}
```

Add the production values to `src/environments/environment.ts`. Both files are compiled into the browser bundle, which is fine for the `anon` key and never for `service_role`.

## Create a Supabase service

```ts
// src/app/supabase.service.ts
import { Injectable, signal } from '@angular/core'
import { createClient, type Session, type SupabaseClient } from '@supabase/supabase-js'
import { environment } from '../environments/environment'

@Injectable({ providedIn: 'root' })
export class SupabaseService {
  readonly client: SupabaseClient = createClient(
    environment.supabaseUrl,
    environment.supabaseAnonKey
  )
  readonly session = signal<Session | null>(null)

  constructor() {
    this.client.auth.getSession().then(({ data }) => this.session.set(data.session))
    this.client.auth.onAuthStateChange((_event, session) => this.session.set(session))
  }

  signIn(email: string, password: string) {
    return this.client.auth.signInWithPassword({ email, password })
  }

  signUp(email: string, password: string) {
    return this.client.auth.signUp({
      email,
      password,
      options: { emailRedirectTo: window.location.origin },
    })
  }

  signOut() {
    return this.client.auth.signOut()
  }
}
```

## Query a table

```ts
// src/app/recipes.component.ts
import { Component, inject, signal, type OnInit } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { SupabaseService } from './supabase.service'

type Recipe = { id: number; title: string; minutes: number | null }

@Component({
  selector: 'app-recipes',
  standalone: true,
  imports: [FormsModule],
  template: `
    @if (supabase.session()) {
      <form (ngSubmit)="add()">
        <input [(ngModel)]="newTitle" name="title" placeholder="New recipe" required />
        <button>Add</button>
      </form>
    }
    @if (error()) { <p>{{ error() }}</p> }
    <ul>
      @for (r of recipes(); track r.id) {
        <li>{{ r.title }}</li>
      }
    </ul>
  `,
})
export class RecipesComponent implements OnInit {
  readonly supabase = inject(SupabaseService)
  readonly recipes = signal<Recipe[]>([])
  readonly error = signal('')
  newTitle = ''

  async ngOnInit() {
    await this.load()
  }

  async load() {
    const { data, error } = await this.supabase.client
      .from('recipes')
      .select('id, title, minutes')
      .order('created_at', { ascending: false })
    if (error) this.error.set(error.message)
    else this.recipes.set(data)
  }

  async add() {
    const { error } = await this.supabase.client.from('recipes').insert({ title: this.newTitle })
    if (error) this.error.set(error.message)
    else {
      this.newTitle = ''
      await this.load()
    }
  }
}
```

Reading works signed out through the quickstart's select policy. Inserting needs a signed-in user, since the insert policy is for `authenticated` and checks `user_id`.

## Add sign-in

```ts
// src/app/sign-in.component.ts
import { Component, inject, signal } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { SupabaseService } from './supabase.service'

@Component({
  selector: 'app-sign-in',
  standalone: true,
  imports: [FormsModule],
  template: `
    @if (supabase.session(); as s) {
      <p>Signed in as {{ s.user.email }}</p>
      <button (click)="supabase.signOut()">Sign out</button>
    } @else {
      <form (ngSubmit)="signIn()">
        <input [(ngModel)]="email" name="email" type="email" required />
        <input [(ngModel)]="password" name="password" type="password" required />
        <button type="submit">Sign in</button>
        <button type="button" (click)="signUp()">Create account</button>
      </form>
      <p>{{ message() }}</p>
    }
  `,
})
export class SignInComponent {
  readonly supabase = inject(SupabaseService)
  readonly message = signal('')
  email = ''
  password = ''

  async signIn() {
    const { error } = await this.supabase.signIn(this.email, this.password)
    this.message.set(error ? error.message : '')
  }

  async signUp() {
    const { error } = await this.supabase.signUp(this.email, this.password)
    this.message.set(error ? error.message : 'Check your inbox to confirm your email.')
  }
}
```

## Guard routes

A functional guard redirects signed-out visitors:

```ts
// src/app/auth.guard.ts
import { inject } from '@angular/core'
import { Router, type CanActivateFn } from '@angular/router'
import { SupabaseService } from './supabase.service'

export const authGuard: CanActivateFn = async () => {
  // inject() only works before the first await.
  const supabase = inject(SupabaseService)
  const router = inject(Router)
  const { data } = await supabase.client.auth.getSession()
  return data.session ? true : router.createUrlTree(['/sign-in'])
}
```

Use it with `{ path: 'account', component: AccountComponent, canActivate: [authGuard] }`. Guards only shape the UI; Row Level Security is what protects the data.

If you use Angular SSR, the service above runs in the browser only. For server-rendered pages that need the user, read the session from cookies with `@supabase/ssr`; see [Server-side auth and SSR](/docs/auth/server-side).

## Allow your redirect URLs

On the project's **Auth** tab, set **Site URL** and add your origins under **Redirect URLs**, then click **Save Auth settings**:

```text
http://localhost:4200/**
https://recipes.example.com/**
```

## Next steps

- [Social login (OAuth)](/docs/auth/social-login)
- [Broadcast](/docs/realtime/broadcast) and [Postgres Changes](/docs/realtime/postgres-changes)
- [Uploading files](/docs/storage/uploads)
- [Generating TypeScript types](/docs/api/generating-types)
