---
title: 'Build a User Management App with Angular'
description: 'Learn how to use Supabase in your Angular App.'
---

<$Partial path="quickstart_intro.mdx" />

![Supabase User Management example](/docs/img/user-management-demo.png)

<Admonition type="note">

If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/angular-user-management).

</Admonition>

<$Partial path="project_setup.mdx" />

## Building the app

Start with building the Angular app from scratch.

### Initialize an Angular app

You can use the [Angular CLI](https://angular.io/cli) to initialize
an app called `supabase-angular`. The command sets some defaults, that you change to suit your needs:

```bash
npx ng new supabase-angular --routing false --style css --standalone false --zoneless true --ssr false
cd supabase-angular
```

Then, install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)

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

Finally, save the environment variables in the `src/environments/environment.ts` file.
All you need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
The application exposes these variables in the browser, and that's fine as you have [Row Level Security](/docs/guides/auth#row-level-security) enabled on the Database.

<$CodeTabs>

```ts name=src/environments/environment.ts
export const environment = {
  production: false,
  supabaseUrl: 'YOUR_SUPABASE_URL',
  supabaseKey: 'YOUR_SUPABASE_KEY',
}
```

</$CodeTabs>

Now you have the API credentials in place, create a `SupabaseService` with `ng g s supabase` and add the following code to initialize the Supabase client and implement functions to communicate with the Supabase API.

<$CodeTabs>

```ts name=src/app/supabase.service.ts
import { Injectable } from '@angular/core'
import {
  AuthChangeEvent,
  AuthSession,
  createClient,
  Session,
  SupabaseClient,
  User,
} from '@supabase/supabase-js'
import { environment } from '../environments/environment'

export interface Profile {
  id?: string
  username: string
  website: string
  avatar_url: string
}

@Injectable({
  providedIn: 'root',
})
export class SupabaseService {
  private supabase: SupabaseClient
  _session: AuthSession | null = null

  constructor() {
    this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
  }

  get session() {
    this.supabase.auth.getSession().then(({ data }) => {
      this._session = data.session
    })
    return this._session
  }

  profile(user: User) {
    return this.supabase
      .from('profiles')
      .select(`username, website, avatar_url`)
      .eq('id', user.id)
      .single()
  }

  authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
    return this.supabase.auth.onAuthStateChange(callback)
  }

  signIn(email: string) {
    return this.supabase.auth.signInWithOtp({ email })
  }

  signOut() {
    return this.supabase.auth.signOut()
  }

  updateProfile(profile: Profile) {
    const update = {
      ...profile,
      updated_at: new Date(),
    }

    return this.supabase.from('profiles').upsert(update)
  }

  downLoadImage(path: string) {
    return this.supabase.storage.from('avatars').download(path)
  }

  uploadAvatar(filePath: string, file: File) {
    return this.supabase.storage.from('avatars').upload(filePath, file)
  }
}
```

</$CodeTabs>

Optionally, update `src/styles.css` [with the following styles](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/angular-user-management/src/styles.css) to style the app.

### Set up a login component

Next, set up an Angular component to manage logins and sign ups. The component uses [Magic Links](/docs/guides/auth/auth-email-passwordless#with-magic-link), so users can sign in with their email without using passwords.

Create an `AuthComponent` with the `ng g c auth` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/auth/auth.ts
import { Component } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'
import { SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-auth',
  templateUrl: './auth.html',
  styleUrls: ['./auth.css'],
  standalone: false,
})
export class AuthComponent {
  signInForm!: FormGroup
  constructor(
    private readonly supabase: SupabaseService,
    private readonly formBuilder: FormBuilder
  ) {}

  loading = false
  ngOnInit() {
    this.signInForm = this.formBuilder.group({
      email: '',
    })
  }

  async onSubmit(): Promise<void> {
    try {
      this.loading = true
      const email = this.signInForm.value.email as string
      const { error } = await this.supabase.signIn(email)
      if (error) throw error
      alert('Check your email for the login link!')
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.signInForm.reset()
      this.loading = false
    }
  }
}
```

```html name=src/app/auth/auth.html
<div class="row flex-center flex">
  <div class="col-6 form-widget" aria-live="polite">
    <h1 class="header">Supabase + Angular</h1>
    <p class="description">Sign in via magic link with your email below</p>
    <form [formGroup]="signInForm" (ngSubmit)="onSubmit()" class="form-widget">
      <div>
        <label for="email">Email</label>
        <input
          id="email"
          formControlName="email"
          class="inputField"
          type="email"
          placeholder="Your email"
        />
      </div>
      <div>
        <button type="submit" class="button block" [disabled]="loading">
          {{ loading ? "Loading" : "Send magic link" }}
        </button>
      </div>
    </form>
  </div>
</div>
```

</$CodeTabs>

### Account page

Users also need a way to edit their profile details and manage their accounts after signing in.
Create an `AccountComponent` with the `ng g c account` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/account/account.ts
import { Component, Input, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'
import { AuthSession } from '@supabase/supabase-js'
import { Profile, SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-account',
  templateUrl: './account.html',
  styleUrls: ['./account.css'],
  standalone: false,
})
export class AccountComponent implements OnInit {
  loading = false
  profile!: Profile
  updateProfileForm!: FormGroup

  get avatarUrl() {
    return this.updateProfileForm.value.avatar_url as string
  }
  async updateAvatar(event: string): Promise<void> {
    this.updateProfileForm.patchValue({
      avatar_url: event,
    })
    await this.updateProfile()
  }

  @Input()
  session!: AuthSession

  constructor(
    private readonly supabase: SupabaseService,
    private formBuilder: FormBuilder
  ) {
    this.updateProfileForm = this.formBuilder.group({
      username: '',
      website: '',
      avatar_url: '',
    })
  }

  async ngOnInit(): Promise<void> {
    await this.getProfile()

    const { username, website, avatar_url } = this.profile
    this.updateProfileForm.patchValue({
      username,
      website,
      avatar_url,
    })
  }

  async getProfile() {
    try {
      this.loading = true
      const { user } = this.session
      const { data: profile, error, status } = await this.supabase.profile(user)

      if (error && status !== 406) {
        throw error
      }

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

  async updateProfile(): Promise<void> {
    try {
      this.loading = true
      const { user } = this.session

      const username = this.updateProfileForm.value.username as string
      const website = this.updateProfileForm.value.website as string
      const avatar_url = this.updateProfileForm.value.avatar_url as string

      const { error } = await this.supabase.updateProfile({
        id: user.id,
        username,
        website,
        avatar_url,
      })
      if (error) throw error
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.loading = false
    }
  }

  async signOut() {
    await this.supabase.signOut()
  }
}
```

```html name=src/app/account/account.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
  <app-avatar [avatarUrl]="this.avatarUrl" (upload)="updateAvatar($event)"> </app-avatar>
  <div>
    <label for="email">Email</label>
    <input id="email" type="text" [value]="session.user.email" disabled />
  </div>
  <div>
    <label for="username">Name</label>
    <input formControlName="username" id="username" type="text" />
  </div>
  <div>
    <label for="website">Website</label>
    <input formControlName="website" id="website" type="url" />
  </div>

  <div>
    <button type="submit" class="button primary block" [disabled]="loading">
      {{ loading ? "Loading ..." : "Update" }}
    </button>
  </div>

  <div>
    <button class="button block" (click)="signOut()">Sign Out</button>
  </div>
</form>
```

</$CodeTabs>

### Launch!

Now you have all the components in place, update `AppComponent`:

<$CodeTabs>

```ts name=src/app/app.ts
import { Component, OnInit } from '@angular/core'
import { SupabaseService } from './supabase.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrls: ['./app.css'],
  standalone: false,
})
export class AppComponent implements OnInit {
  constructor(private readonly supabase: SupabaseService) {}

  title = 'angular-user-management'
  session: any

  ngOnInit() {
    this.session = this.supabase.session
    this.supabase.authChanges((_, session) => (this.session = session))
  }
}
```

```html name=src/app/app.html
<div class="container" style="padding: 50px 0 100px 0">
  <app-account *ngIf="session; else auth" [session]="session"></app-account>
  <ng-template #auth>
    <app-auth></app-auth>
  </ng-template>
</div>
```

</$CodeTabs>

You also need to change `app.module.ts` to include the `ReactiveFormsModule` from the `@angular/forms` package.

<$CodeTabs>

```ts name=src/app/app.module.ts
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'

import { AppComponent } from './app'
import { AuthComponent } from './auth/auth'
import { AccountComponent } from './account/account'
import { ReactiveFormsModule } from '@angular/forms'
import { AvatarComponent } from './avatar/avatar'

@NgModule({
  declarations: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
  imports: [BrowserModule, ReactiveFormsModule],
  providers: [],
  bootstrap: [AppComponent],
  exports: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
})
export class AppModule {}
```

</$CodeTabs>

Once that's done, run the application in a terminal:

```bash
npm run start
```

Open the browser to [localhost:4200](http://localhost:4200) and you should see the completed app.

![Screenshot of the Supabase Angular application running in a browser](/docs/img/supabase-angular-demo.png)

## Bonus: Profile photos

Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos.

### Create an upload widget

Create an avatar for the user so that they can upload a profile photo.
Create an `AvatarComponent` with `ng g c avatar` Angular CLI command and add the following code.

<$CodeTabs>

```ts name=src/app/avatar/avatar.ts
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'
import { SupabaseService } from '../supabase.service'

@Component({
  selector: 'app-avatar',
  templateUrl: './avatar.html',
  styleUrls: ['./avatar.css'],
  standalone: false,
})
export class AvatarComponent {
  _avatarUrl: SafeResourceUrl | undefined
  uploading = false

  @Input()
  set avatarUrl(url: string | null) {
    if (url) {
      this.downloadImage(url)
    }
  }

  @Output() upload = new EventEmitter<string>()

  constructor(
    private readonly supabase: SupabaseService,
    private readonly dom: DomSanitizer
  ) {}

  async downloadImage(path: string) {
    try {
      const { data } = await this.supabase.downLoadImage(path)
      if (data instanceof Blob) {
        this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data))
      }
    } catch (error) {
      if (error instanceof Error) {
        console.error('Error downloading image: ', error.message)
      }
    }
  }

  async uploadAvatar(event: any) {
    try {
      this.uploading = true
      if (!event.target.files || event.target.files.length === 0) {
        throw new Error('You must select an image to upload.')
      }

      const file = event.target.files[0]
      const fileExt = file.name.split('.').pop()
      const filePath = `${Math.random()}.${fileExt}`

      await this.supabase.uploadAvatar(filePath, file)
      this.upload.emit(filePath)
    } catch (error) {
      if (error instanceof Error) {
        alert(error.message)
      }
    } finally {
      this.uploading = false
    }
  }
}
```

```html name=src/app/avatar/avatar.html
<div>
  <img
    *ngIf="_avatarUrl"
    [src]="_avatarUrl"
    alt="Avatar"
    class="avatar image"
    style="height: 150px; width: 150px"
  />
</div>
<div *ngIf="!_avatarUrl" class="avatar no-image" style="height: 150px; width: 150px"></div>
<div style="width: 150px">
  <label class="button primary block" for="single">
    {{ uploading ? "Uploading ..." : "Upload" }}
  </label>
  <input
    style="visibility: hidden; position: absolute"
    type="file"
    id="single"
    accept="image/*"
    (change)="uploadAvatar($event)"
    [disabled]="uploading"
  />
</div>
```

</$CodeTabs>

### Add the new widget

And then we can add the widget on top of the `AccountComponent` HTML template:

<$CodeTabs>

```html name=src/app/account.html
<form [formGroup]="updateProfileForm" (ngSubmit)="updateProfile()" class="form-widget">
  <app-avatar [avatarUrl]="this.avatarUrl" (upload)="updateAvatar($event)"></app-avatar>
  <!-- input fields -->
</form>
```

</$CodeTabs>

And add an `updateAvatar` function along with an `avatarUrl` getter to the `AccountComponent` typescript file:

<$CodeTabs>

```ts name=src/app/account.ts
@Component({
  selector: 'app-account',
  templateUrl: './account.html',
  styleUrls: ['./account.css'],
})
export class AccountComponent implements OnInit {
  // ...
  get avatarUrl() {
    return this.updateProfileForm.value.avatar_url as string
  }

  async updateAvatar(event: string): Promise<void> {
    this.updateProfileForm.patchValue({
      avatar_url: event,
    })
    await this.updateProfile()
  }
  // ...
}
```

</$CodeTabs>

At this stage you have a fully functional application!
