--- id: with-ionic-angular title: 'Quickstart: Ionic Angular' description: Learn how to use Supabase in your Ionic Angular App. sidebar_label: Ionic Angular --- import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import QuickstartIntro from './_partials/quickstart_intro.md' import ProjectSetup from './_partials/project_setup.mdx' ![Supabase User Management example](/img/ionic-demos/ionic-angular-account.png) ### GitHub Should you get stuck while working through the guide, refer to [this repo](https://github.com/mhartington/supabase-ionic-angular). ## Building the App Let's start building the Angular app from scratch. ### Initialize an Ionic Angular app We can use the [Ionic CLI](https://ionicframework.com/docs/cli) to initialize an app called `supabase-ionic-angular`: ```bash npm install -g @ionic/cli ionic start supabase-ionic-angular blank --type angular cd supabase-ionic-angular ``` Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js) ```bash npm install @supabase/supabase-js ``` And finally we want to save the environment variables in the `environment.ts` file. All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```ts title="environment.ts" export const environment = { production: false, supabaseUrl: 'YOUR_SUPABASE_URL', supabaseKey: 'YOUR_SUPABASE_KEY', } ``` Now that we have the API credentials in place, let's create a **SupabaseService** with `ionic g s supabase` to initialize the Supabase client and implement functions to communicate with the Supabase API. ```ts title="src/app/supabase.service.ts" import { Injectable } from '@angular/core' import { LoadingController, ToastController } from '@ionic/angular' import { AuthChangeEvent, createClient, Session, SupabaseClient, } from '@supabase/supabase-js' import { environment } from '../environments/environment' export interface Profile { username: string website: string avatar_url: string } @Injectable({ providedIn: 'root', }) export class SupabaseService { private supabase: SupabaseClient constructor( private loadingCtrl: LoadingController, private toastCtrl: ToastController ) { this.supabase = createClient( environment.supabaseUrl, environment.supabaseKey ) } get user() { return this.supabase.auth.user() } get session() { return this.supabase.auth.session() } get profile() { return this.supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', this.user?.id) .single() } authChanges( callback: (event: AuthChangeEvent, session: Session | null) => void ) { return this.supabase.auth.onAuthStateChange(callback) } signIn(email: string) { return this.supabase.auth.signIn({ email }) } signOut() { return this.supabase.auth.signOut() } updateProfile(profile: Profile) { const update = { ...profile, id: this.user?.id, updated_at: new Date(), } return this.supabase.from('profiles').upsert(update, { returning: 'minimal', // Don't return the value after inserting }) } 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) } async createNotice(message: string) { const toast = await this.toastCtrl.create({ message, duration: 5000 }) await toast.present() } createLoader() { return this.loadingCtrl.create() } } ``` ### Set up a Login route Let's set up an route to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. Create an **LoginPage** with `ionic g page login` Ionic CLI command. > This guide will show the template inline, but the example app will have templateUrls ```ts title="src/app/login/login.page.ts" import { Component, OnInit } from '@angular/core' import { SupabaseService } from '../supabase.service' @Component({ selector: 'app-login', template: ` Login

Supabase + Ionic Angular

Sign in via magic link with your email below

Email
Login
`, styleUrls: ['./login.page.scss'], }) export class LoginPage implements OnInit { email = '' constructor(private readonly supabase: SupabaseService) {} ngOnInit() {} async handleLogin(event: any) { event.preventDefault() const loader = await this.supabase.createLoader() await loader.present() try { await this.supabase.signIn(this.email) await loader.dismiss() await this.supabase.createNotice('Check your email for the login link!') } catch (error) { await loader.dismiss() await this.supabase.createNotice(error.error_description || error.message) } } } ``` ### Account page After a user is signed in we can allow them to edit their profile details and manage their account. Create an **AccountComponent** with `ionic g page account` Ionic CLI command. ```ts title="src/app/account.component.ts" import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' import { Profile, SupabaseService } from '../supabase.service' @Component({ selector: 'app-account', template: ` Account
Email Name Website
Update Profile
Log Out
`, styleUrls: ['./account.page.scss'], }) export class AccountPage implements OnInit { profile: Profile = { username: '', avatar_url: '', website: '', } session = this.supabase.session constructor( private readonly supabase: SupabaseService, private router: Router ) {} ngOnInit() { this.getProfile() } async getProfile() { try { let { data: profile, error, status } = await this.supabase.profile if (error && status !== 406) { throw error } if (profile) { this.profile = profile } } catch (error) { alert(error.message) } } async updateProfile(avatar_url: string = '') { const loader = await this.supabase.createLoader() await loader.present() try { await this.supabase.updateProfile({ ...this.profile, avatar_url }) await loader.dismiss() await this.supabase.createNotice('Profile updated!') } catch (error) { await this.supabase.createNotice(error.message) } } async signOut() { console.log('testing?') await this.supabase.signOut() this.router.navigate(['/'], { replaceUrl: true }) } } ``` ### Launch! Now that we have all the components in place, let's update **AppComponent**: ```ts title="src/app/app.component.ts" import { Component } from '@angular/core' import { Router } from '@angular/router' import { SupabaseService } from './supabase.service' @Component({ selector: 'app-root', template: ` `, styleUrls: ['app.component.scss'], }) export class AppComponent { constructor(private supabase: SupabaseService, private router: Router) { this.supabase.authChanges((_, session) => { console.log(session) if (session?.user) { this.router.navigate(['/account']) } }) } } ``` Then update the **AppRoutingModule** ```ts title="src/app/app.ts" import { NgModule } from '@angular/core' import { PreloadAllModules, RouterModule, Routes } from '@angular/router' const routes: Routes = [ { path: '/', loadChildren: () => import('./login/login.module').then((m) => m.LoginPageModule), }, { path: 'account', loadChildren: () => import('./account/account.module').then((m) => m.AccountPageModule), }, ] @NgModule({ imports: [ RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules, }), ], exports: [RouterModule], }) export class AppRoutingModule {} ``` Once that's done, run this in a terminal window: ```bash ionic serve ``` And the browser will auomatically open to show the app. ![Supabase Angular](/img/ionic-demos/ionic-angular.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 Let's create an avatar for the user so that they can upload a profile photo. First install two packages in order to interact with the user's camera. ```bash npm install @ionic/pwa-elements @capacitor/camera ``` [CapacitorJS](https://capacitorjs.com) is a cross platform native runtime from Ionic that enables web apps to be deployed through the app store and provides access to native deavice API. Ionic PWA elements is a companion package that will polyfill certain browser APIs that provide no user interface with custom Ionic UI. With those packages installed we can update our `main.ts` to include an additional bootstapping call for the Ionic PWA Elements. ```ts title="src/main.ts" import { enableProdMode } from '@angular/core' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { AppModule } from './app/app.module' import { environment } from './environments/environment' import { defineCustomElements } from '@ionic/pwa-elements/loader' defineCustomElements(window) if (environment.production) { enableProdMode() } platformBrowserDynamic() .bootstrapModule(AppModule) .catch((err) => console.log(err)) ``` Then create an **AvatarComponent** with this Ionic CLI command: ```bash ionic g component avatar --module=/src/app/account/account.module.ts --create-module ``` ```ts title="src/app/avatar.component.ts" import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' import { SupabaseService } from '../supabase.service' import { Camera, CameraResultType } from '@capacitor/camera' @Component({ selector: 'app-avatar', template: `
`, style: [ ` :host { display: block; margin: auto; min-height: 150px; } :host .avatar_wrapper { margin: 16px auto 16px; border-radius: 50%; overflow: hidden; height: 150px; aspect-ratio: 1; background: var(--ion-color-step-50); border: thick solid var(--ion-color-step-200); } :host .avatar_wrapper:hover { cursor: pointer; } :host .avatar_wrapper ion-icon.no-avatar { width: 100%; height: 115%; } :host img { display: block; object-fit: cover; width: 100%; height: 100%; } `, ], }) export class AvatarComponent implements OnInit { _avatarUrl: SafeResourceUrl | undefined uploading = false @Input() set avatarUrl(url: string | undefined) { if (url) { this.downloadImage(url) } } @Output() upload = new EventEmitter() constructor( private readonly supabase: SupabaseService, private readonly dom: DomSanitizer ) {} ngOnInit() {} async downloadImage(path: string) { try { const { data } = await this.supabase.downLoadImage(path) this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl( URL.createObjectURL(data) ) } catch (error) { console.error('Error downloading image: ', error.message) } } async uploadAvatar() { const loader = await this.supabase.createLoader() try { const photo = await Camera.getPhoto({ resultType: CameraResultType.DataUrl, }) const file = await fetch(photo.dataUrl) .then((res) => res.blob()) .then( (blob) => new File([blob], 'my-file', { type: `image/${photo.format}` }) ) const fileName = `${Math.random()}-${new Date().getTime()}.${ photo.format }` await loader.present() await this.supabase.uploadAvatar(fileName, file) this.upload.emit(fileName) } catch (error) { this.supabase.createNotice(error.message) } finally { loader.dismiss() } } } ``` ### Add the new widget And then we can add the widget on top of the **AccountComponent** html template: ```ts title="src/app/account.component.ts" template: ` Account ` ``` ## Next steps At this stage you have a fully functional application! - Got a question? [Ask here](https://github.com/supabase/supabase/discussions). - Sign in: [app.supabase.com](https://app.supabase.com)