入门

使用 Angular 构建用户管理应用


本教程演示如何构建一个基本的用户管理应用。该应用对用户进行身份验证和识别,将他们的个人资料信息存储在数据库中,并允许用户登录、更新他们的个人资料详细信息以及上传个人资料照片。该应用使用

Supabase User Management example

项目设置#

在开始构建之前,您需要设置数据库和 API。您可以通过在 Supabase 中启动一个新项目,然后在数据库中创建一个“schema”来完成此操作。

创建项目#

  1. 在 Supabase 控制面板中创建一个新项目
  2. 输入您的项目详细信息。
  3. 等待新的数据库启动。

设置数据库 schema#

现在设置数据库 schema。您可以使用 SQL 编辑器中的“用户管理 Starter”快速入门,或者您可以复制/粘贴下面的 SQL 并运行它。

  1. 转到控制面板中的 SQL 编辑器 页面。
  2. 点击 社区 > 快速入门 选项卡下的 用户管理 Starter
  3. 点击 运行

获取 API 详细信息#

现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。

为此,您需要从 项目的 Connect 对话框中获取项目 URL 和密钥。

阅读 API 密钥文档 以全面了解所有密钥类型及其用途。

构建应用#

从头开始构建 Angular 应用。

初始化 Angular 应用#

您可以使用 Angular CLI 初始化一个名为 supabase-angular 的应用。该命令设置了一些默认值,您可以根据需要进行更改

1
npx ng new supabase-angular --routing false --style css --standalone false --ssr false
2
cd supabase-angular

然后,安装唯一的附加依赖项:supabase-js

1
npm install @supabase/supabase-js

最后,将环境变量保存在一个新的 src/environments/environment.ts 文件中。您需要先创建 src/environments 目录。您只需要之前复制的 API URL 和密钥 即可。应用程序在浏览器中公开这些变量,这很好,因为您启用了数据库上的 行级别安全

src/environments/environment.ts
1
export const = {
2
: false,
3
: 'YOUR_SUPABASE_URL',
4
: 'YOUR_SUPABASE_KEY',
5
}
查看源代码

将 API 凭据设置到位后,使用 ng g s supabase 创建一个 SupabaseService,并添加以下代码以初始化 Supabase 客户端并实现与 Supabase API 通信的功能。

这使用 getUser 方法获取当前用户详细信息(如果存在会话)。此方法向 Supabase Auth 服务器执行网络请求。

src/app/supabase.service.ts
1
import { Injectable } from '@angular/core'
2
import {
3
AuthChangeEvent,
4
createClient,
5
Session,
6
SupabaseClient,
7
User,
8
} from '@supabase/supabase-js'
9
import { environment } from '../environments/environment'
10
11
export interface Profile {
12
id?: string
13
username: string
14
website: string
15
avatar_url: string
16
}
17
18
@Injectable({
19
providedIn: 'root',
20
})
21
export class SupabaseService {
22
private supabase: SupabaseClient
23
24
constructor() {
25
this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)
26
}
27
28
async getUser(): Promise<User | null> {
29
const { data, error } = await this.supabase.auth.getUser()
30
if (error) {
31
return null
32
}
33
return data.user
34
}
35
36
profile(user: User) {
37
return this.supabase
38
.from('profiles')
39
.select(`username, website, avatar_url`)
40
.eq('id', user.id)
41
.single()
42
}
43
44
authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {
45
return this.supabase.auth.onAuthStateChange(callback)
46
}
47
48
signIn(email: string) {
49
return this.supabase.auth.signInWithOtp({ email })
50
}
51
52
signOut() {
53
return this.supabase.auth.signOut()
54
}
55
56
updateProfile(profile: Profile) {
57
const update = {
58
...profile,
59
updated_at: new Date(),
60
}
61
62
return this.supabase.from('profiles').upsert(update)
63
}
64
65
downLoadImage(path: string) {
66
return this.supabase.storage.from('avatars').download(path)
67
}
68
69
uploadAvatar(filePath: string, file: File) {
70
return this.supabase.storage.from('avatars').upload(filePath, file)
71
}
72
}
查看源代码

可选地,更新 src/styles.css 以设置应用样式。您可以在 示例仓库中找到此文件的完整内容。

设置登录组件#

接下来,设置一个 Angular 组件来管理登录和注册。该组件使用 Magic Links,因此用户可以使用他们的电子邮件进行登录,而无需使用密码。

使用 Angular CLI 命令 ng g c auth 创建一个 AuthComponent,并添加以下代码。

1
import { Component } from '@angular/core'
2
import { FormBuilder, FormGroup } from '@angular/forms'
3
import { SupabaseService } from '../supabase.service'
4
5
@Component({
6
selector: 'app-auth',
7
templateUrl: './auth.component.html',
8
styleUrls: ['./auth.component.css'],
9
standalone: false,
10
})
11
export class AuthComponent {
12
signInForm!: FormGroup
13
constructor(
14
private readonly supabase: SupabaseService,
15
private readonly formBuilder: FormBuilder
16
) {}
17
18
loading = false
19
ngOnInit() {
20
this.signInForm = this.formBuilder.group({
21
email: '',
22
})
23
}
24
25
async onSubmit(): Promise<void> {
26
try {
27
this.loading = true
28
const email = this.signInForm.value.email as string
29
const { error } = await this.supabase.signIn(email)
30
if (error) throw error
31
alert('Check your email for the login link!')
32
} catch (error) {
33
if (error instanceof Error) {
34
alert(error.message)
35
}
36
} finally {
37
this.signInForm.reset()
38
this.loading = false
39
}
40
}
41
}
查看源代码

账户页面#

用户还需要一种编辑他们的个人资料详细信息并在登录后管理他们的帐户的方式。使用 Angular CLI 命令 ng g c account 创建一个 AccountComponent,并添加以下代码。

1
import { Component, Input, OnInit } from '@angular/core'
2
import { FormBuilder, FormGroup } from '@angular/forms'
3
import { User } from '@supabase/supabase-js'
4
import { Profile, SupabaseService } from '../supabase.service'
5
6
@Component({
7
selector: 'app-account',
8
templateUrl: './account.component.html',
9
styleUrls: ['./account.component.css'],
10
standalone: false,
11
})
12
export class AccountComponent implements OnInit {
13
loading = false
14
profile!: Profile
15
updateProfileForm!: FormGroup
16
17
get avatarUrl() {
18
return this.updateProfileForm.value.avatar_url as string
19
}
20
21
async updateAvatar(event: string): Promise<void> {
22
this.updateProfileForm.patchValue({
23
avatar_url: event,
24
})
25
await this.updateProfile()
26
}
27
28
@Input()
29
user!: User
30
31
constructor(
32
private readonly supabase: SupabaseService,
33
private formBuilder: FormBuilder
34
) {
35
this.updateProfileForm = this.formBuilder.group({
36
username: '',
37
website: '',
38
avatar_url: '',
39
})
40
}
41
42
async ngOnInit(): Promise<void> {
43
await this.getProfile()
44
45
const { username, website, avatar_url } = this.profile
46
this.updateProfileForm.patchValue({
47
username,
48
website,
49
avatar_url,
50
})
51
}
52
53
async getProfile() {
54
try {
55
this.loading = true
56
const { data: profile, error, status } = await this.supabase.profile(this.user)
57
58
if (error && status !== 406) {
59
throw error
60
}
61
62
if (profile) {
63
this.profile = profile
64
}
65
} catch (error) {
66
if (error instanceof Error) {
67
alert(error.message)
68
}
69
} finally {
70
this.loading = false
71
}
72
}
73
74
async updateProfile(): Promise<void> {
75
try {
76
this.loading = true
77
78
const username = this.updateProfileForm.value.username as string
79
const website = this.updateProfileForm.value.website as string
80
const avatar_url = this.updateProfileForm.value.avatar_url as string
81
82
const { error } = await this.supabase.updateProfile({
83
id: this.user.id,
84
username,
85
website,
86
avatar_url,
87
})
88
if (error) throw error
89
} catch (error) {
90
if (error instanceof Error) {
91
alert(error.message)
92
}
93
} finally {
94
this.loading = false
95
}
96
}
97
98
async signOut() {
99
await this.supabase.signOut()
100
}
101
}
查看源代码

个人资料照片#

每个 Supabase 项目都配置了 存储,用于管理大型文件,如照片和视频。

创建一个上传小部件#

为用户创建一个头像,以便他们可以上传个人资料照片。使用 Angular CLI 命令 ng g c avatar 创建一个 AvatarComponent,并添加以下代码。

1
import { Component, EventEmitter, Input, Output } from '@angular/core'
2
import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'
3
import { SupabaseService } from '../supabase.service'
4
5
@Component({
6
selector: 'app-avatar',
7
templateUrl: './avatar.component.html',
8
styleUrls: ['./avatar.component.css'],
9
standalone: false,
10
})
11
export class AvatarComponent {
12
_avatarUrl: SafeResourceUrl | undefined
13
uploading = false
14
15
@Input()
16
set avatarUrl(url: string | null) {
17
if (url) {
18
this.downloadImage(url)
19
}
20
}
21
22
@Output() upload = new EventEmitter<string>()
23
24
constructor(
25
private readonly supabase: SupabaseService,
26
private readonly dom: DomSanitizer
27
) {}
28
29
async downloadImage(path: string) {
30
try {
31
const { data } = await this.supabase.downLoadImage(path)
32
if (data instanceof Blob) {
33
this._avatarUrl = this.dom.bypassSecurityTrustResourceUrl(URL.createObjectURL(data))
34
}
35
} catch (error) {
36
if (error instanceof Error) {
37
console.error('Error downloading image: ', error.message)
38
}
39
}
40
}
41
42
async uploadAvatar(event: any) {
43
try {
44
this.uploading = true
45
if (!event.target.files || event.target.files.length === 0) {
46
throw new Error('You must select an image to upload.')
47
}
48
49
const file = event.target.files[0]
50
const fileExt = file.name.split('.').pop()
51
const filePath = `${Math.random()}.${fileExt}`
52
53
await this.supabase.uploadAvatar(filePath, file)
54
this.upload.emit(filePath)
55
} catch (error) {
56
if (error instanceof Error) {
57
alert(error.message)
58
}
59
} finally {
60
this.uploading = false
61
}
62
}
63
}
查看源代码

启动!#

现在您已经准备好所有组件,请更新 AppComponent

1
import { Component, OnInit } from '@angular/core'
2
import { User } from '@supabase/supabase-js'
3
import { SupabaseService } from './supabase.service'
4
5
@Component({
6
selector: 'app-root',
7
templateUrl: './app.component.html',
8
styleUrls: ['./app.component.css'],
9
standalone: false,
10
})
11
export class AppComponent implements OnInit {
12
constructor(private readonly supabase: SupabaseService) {}
13
14
title = 'angular-user-management'
15
user: User | null = null
16
17
async ngOnInit() {
18
this.user = await this.supabase.getUser()
19
this.supabase.authChanges(async () => {
20
this.user = await this.supabase.getUser()
21
})
22
}
23
}
查看源代码

您还需要更改 app.module.ts 以包含来自 @angular/forms 包的 ReactiveFormsModule

src/app/app.module.ts
1
import { NgModule } from '@angular/core'
2
import { BrowserModule } from '@angular/platform-browser'
3
import { ReactiveFormsModule } from '@angular/forms'
4
5
import { AppComponent } from './app.component'
6
import { AuthComponent } from './auth/auth.component'
7
import { AccountComponent } from './account/account.component'
8
import { AvatarComponent } from './avatar/avatar.component'
9
10
@NgModule({
11
declarations: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],
12
imports: [BrowserModule, ReactiveFormsModule],
13
providers: [],
14
bootstrap: [AppComponent],
15
})
16
export class AppModule {}
查看源代码

完成此操作后,在终端中运行该应用程序

1
npm run start

在浏览器中打开 localhost:4200,您应该会看到完成的应用。

Screenshot of the Supabase Angular application running in a browser

此时,您已经拥有一个功能齐全的应用!