diff --git a/apps/reference/docs/guides/with-angular.mdx b/apps/reference/docs/guides/with-angular.mdx
index 93389bfc2df..2d4dc88df31 100644
--- a/apps/reference/docs/guides/with-angular.mdx
+++ b/apps/reference/docs/guides/with-angular.mdx
@@ -33,7 +33,7 @@ If you want to do it yourself, let's get started!
### GitHub
-Whenever you get stuck at any point, take a look at [this repo](https://github.com/angular-supa/supabase-angular-user-management).
+Whenever you get stuck at any point, take a look at [this repo](https://github.com/supabase/examples/tree/main/supabase-js-v2/user-management/angular-user-management).
## Project set up
@@ -180,281 +180,365 @@ export const environment = {
Now that we have the API credentials in place, let's create a **SupabaseService** with `ng 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 { Injectable } from '@angular/core';
import {
AuthChangeEvent,
+ AuthSession,
createClient,
Session,
SupabaseClient,
-} from '@supabase/supabase-js'
-import { environment } from '../environments/environment'
+ User,
+} from '@supabase/supabase-js';
+import { environment } from 'src/environments/environment';
+import { Database } from 'src/schema';
export interface Profile {
- username: string
- website: string
- avatar_url: string
+ id?: string;
+ username: string;
+ website: string;
+ avatar_url: string;
}
@Injectable({
providedIn: 'root',
})
export class SupabaseService {
- private supabase: SupabaseClient
+ private supabase: SupabaseClient;
+ _session: AuthSession | null = null;
constructor() {
this.supabase = createClient(
environment.supabaseUrl,
environment.supabaseKey
- )
- }
-
- get user() {
- return this.supabase.auth.user()
+ );
}
get session() {
- return this.supabase.auth.session()
+ this.supabase.auth.getSession().then(({ data }) => {
+ this._session = data.session;
+ })
+ return this._session;
}
- get profile() {
+ profile(user: User) {
return this.supabase
.from('profiles')
.select(`username, website, avatar_url`)
- .eq('id', this.user?.id)
- .single()
+ .eq('id', user.id)
+ .single();
}
authChanges(
callback: (event: AuthChangeEvent, session: Session | null) => void
) {
- return this.supabase.auth.onAuthStateChange(callback)
+ return this.supabase.auth.onAuthStateChange(callback);
}
signIn(email: string) {
- return this.supabase.auth.signIn({ email })
+ return this.supabase.auth.signInWithOtp({ email });
}
signOut() {
- return this.supabase.auth.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
- })
+ return this.supabase.from('profiles').upsert(update);
}
downLoadImage(path: string) {
- return this.supabase.storage.from('avatars').download(path)
+ return this.supabase.storage.from('avatars').download(path);
}
uploadAvatar(filePath: string, file: File) {
- return this.supabase.storage.from('avatars').upload(filePath, file)
+ return this.supabase.storage.from('avatars').upload(filePath, file);
}
}
```
-And one optional step is to update the CSS file `src/index.css` to make the app look nice.
-You can find the full contents of this file [here](https://raw.githubusercontent.com/angular-supa/supabase-angular-user-management/main/src/styles.css).
+Optionally, update [src/styles.css](https://raw.githubusercontent.com/supabase/examples/main/supabase-js-v2/user-management/angular-user-management/src/styles.css) to style the app.
### Set up a Login component
Let's set up an Angular component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords.
Create an **AuthComponent** with `ng g c auth` Angular CLI command.
-```ts title="src/app/auth.component.ts"
-import { Component } from '@angular/core'
-import { SupabaseService } from './supabase.service'
+```ts title="src/app/auth/auth.component.ts"
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { SupabaseService } from '../supabase.service';
@Component({
selector: 'app-auth',
- template: `
-
- `,
+ templateUrl: './auth.component.html',
+ styleUrls: ['./auth.component.css'],
})
-export class AuthComponent {
- loading = false
+export class AuthComponent implements OnInit {
+ loading = false;
- constructor(private readonly supabase: SupabaseService) {}
+ signInForm = this.formBuilder.group({
+ email: '',
+ });
- async handleLogin(input: string) {
+ constructor(
+ private readonly supabase: SupabaseService,
+ private readonly formBuilder: FormBuilder
+ ) {}
+
+ ngOnInit(): void {}
+
+ async onSubmit(): Promise {
try {
- this.loading = true
- await this.supabase.signIn(input)
- alert('Check your email for the login link!')
+ 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) {
- alert(error.error_description || error.message)
+ if (error instanceof Error) {
+ alert(error.message);
+ }
} finally {
- this.loading = false
+ this.signInForm.reset();
+ this.loading = false;
}
}
}
```
+```html title="src/app/auth/auth.component.html"
+
-
- Email
-
-
-
- Name
-
-
-
- Website
-
-
-
-
-
- {{ loading ? 'Loading ...' : 'Update' }}
-
-
-
-
- Sign Out
-
-
- `,
+ templateUrl: './account.component.html',
+ styleUrls: ['./account.component.css'],
})
export class AccountComponent implements OnInit {
- loading = false
- profile: Profile | undefined
+ loading = false;
+ profile!: Profile;
- @Input() session: Session | undefined
+ @Input()
+ session!: AuthSession;
- constructor(private readonly supabase: SupabaseService) {}
+ updateProfileForm = this.formBuilder.group({
+ username: '',
+ website: '',
+ avatar_url: '',
+ });
- ngOnInit() {
- this.getProfile()
+ constructor(
+ private readonly supabase: SupabaseService,
+ private formBuilder: FormBuilder
+ ) {}
+
+ 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
- let { data: profile, error, status } = await this.supabase.profile
+ this.loading = true;
+ const { user } = this.session;
+ let { data: profile, error, status } = await this.supabase.profile(user);
if (error && status !== 406) {
- throw error
+ throw error;
}
if (profile) {
- this.profile = profile
+ this.profile = profile;
}
} catch (error) {
- alert(error.message)
+ if (error instanceof Error) {
+ alert(error.message);
+ }
} finally {
- this.loading = false
+ this.loading = false;
}
}
- async updateProfile(
- username: string,
- website: string,
- avatar_url: string = ''
- ) {
+ async updateProfile(): Promise {
try {
- this.loading = true
- await this.supabase.updateProfile({ username, website, avatar_url })
+ 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) {
- alert(error.message)
+ if (error instanceof Error) {
+ alert(error.message);
+ }
} finally {
- this.loading = false
+ this.loading = false;
}
}
async signOut() {
- await this.supabase.signOut()
+ await this.supabase.signOut();
}
}
```
+```html title="src/app/account/account.component.html"
+
+```
+
### Launch!
Now that we have all the components in place, let's update **AppComponent**:
```ts title="src/app/app.component.ts"
-import { Component, OnInit } from '@angular/core'
-import { SupabaseService } from './supabase.service'
+import { Component, OnInit } from '@angular/core';
+import { SupabaseService } from './supabase.service';
@Component({
selector: 'app-root',
- template: `
-
- `,
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
- session = this.supabase.session
+ title = 'angular-user-management';
+
+ session = this.supabase.session;
constructor(private readonly supabase: SupabaseService) {}
ngOnInit() {
- this.supabase.authChanges((_, session) => (this.session = session))
+ this.supabase.authChanges((_, session) => (this.session = session));
}
}
```
+```html title="src/app/app.component.html"
+
+```
+
+`app.module.ts` also needs to be modified to include the `ReactiveFormsModule` from the `@angular/forms` package.
+
+```ts title="src/app/app.module.ts"
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+
+import { AppComponent } from './app.component';
+import { AuthComponent } from './auth/auth.component';
+import { AccountComponent } from './account/account.component';
+import { ReactiveFormsModule } from '@angular/forms';
+import { AvatarComponent } from './avatar/avatar.component';
+
+@NgModule({
+ declarations: [
+ AppComponent,
+ AuthComponent,
+ AccountComponent,
+ AvatarComponent,
+ ],
+ imports: [BrowserModule, ReactiveFormsModule],
+ providers: [],
+ bootstrap: [AppComponent],
+})
+export class AppModule {}
+```
+
Once that's done, run this in a terminal window:
```bash
@@ -474,110 +558,141 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
Let's 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.
-```ts title="src/app/avatar.component.ts"
-import { Component, EventEmitter, Input, Output } from '@angular/core'
-import { SupabaseService } from './supabase.service'
-import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
+```ts title="src/app/avatar/avatar.component.ts"
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
+import { SupabaseService } from '../supabase.service';
@Component({
selector: 'app-avatar',
- template: `
-
-
-
-
-
-
- {{ uploading ? 'Uploading ...' : 'Upload' }}
-
-
-
- `,
+ templateUrl: './avatar.component.html',
+ styleUrls: ['./avatar.component.css'],
})
-export class AvatarComponent {
- _avatarUrl: SafeResourceUrl | undefined
- uploading = false
+export class AvatarComponent implements OnInit {
+ _avatarUrl: SafeResourceUrl | undefined;
+ uploading = false;
@Input()
- set avatarUrl(url: string | undefined) {
+ set avatarUrl(url: string | null) {
if (url) {
- this.downloadImage(url)
+ this.downloadImage(url);
}
}
- @Output() upload = new EventEmitter()
+ @Output() upload = new EventEmitter();
constructor(
private readonly supabase: SupabaseService,
private readonly dom: DomSanitizer
) {}
+ ngOnInit(): void {}
+
async downloadImage(path: string) {
try {
- const { data } = await this.supabase.downLoadImage(path)
+ const { data } = await this.supabase.downLoadImage(path);
if (data instanceof Blob) {
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(
URL.createObjectURL(data)
- )
+ );
}
} catch (error) {
- console.error('Error downloading image: ', error.message)
+ if (error instanceof Error) {
+ console.error('Error downloading image: ', error.message);
+ }
}
}
async uploadAvatar(event: any) {
try {
- this.uploading = true
+ this.uploading = true;
if (!event.target.files || event.target.files.length === 0) {
- throw new Error('You must select an image to upload.')
+ throw new Error('You must select an image to upload.');
}
- const file = event.target.files[0]
- const fileExt = file.name.split('.').pop()
- const fileName = `${Math.random()}.${fileExt}`
- const filePath = `${fileName}`
+ 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)
+ await this.supabase.uploadAvatar(filePath, file);
+ this.upload.emit(filePath);
} catch (error) {
- alert(error.message)
+ if (error instanceof Error) {
+ alert(error.message);
+ }
} finally {
- this.uploading = false
+ this.uploading = false;
}
}
}
```
+```html title="src/app/avatar/avatar.component.html"
+
+
+
+
+
+
+ {{ uploading ? 'Uploading ...' : 'Upload' }}
+
+
+
+```
+
### 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: `
-
-
+```html title="src/app/account.component.html"
+
+```
-
-`
+And add an `updateAvatar` function along with an `avatarUrl` getter to the **AccountComponent** typescript file:
+
+```ts title="src/app/account.component.ts"
+@Component({
+ selector: 'app-account',
+ templateUrl: './account.component.html',
+ styleUrls: ['./account.component.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();
+ }
+ // ...
+}
```
## Next steps