--- 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) 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). <$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', } ``` 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) } } ``` 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 { 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

Supabase + Angular

Sign in via magic link with your email below

``` ### 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 { 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 { 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 { 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
``` ### 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
``` 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 {} ``` 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() 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
Avatar
``` ### 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
``` 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 { this.updateProfileForm.patchValue({ avatar_url: event, }) await this.updateProfile() } // ... } ``` At this stage you have a fully functional application!