使用 Angular 构建用户管理应用
本教程演示如何构建一个基本的用户管理应用。该应用对用户进行身份验证和识别,将他们的个人资料信息存储在数据库中,并允许用户登录、更新他们的个人资料详细信息以及上传个人资料照片。该应用使用
- Supabase 数据库 - 一个用于存储用户数据的 Postgres 数据库,以及 行级别安全,以保护数据并确保用户只能访问他们自己的信息。
- Supabase Auth - 允许用户注册和登录。
- Supabase Storage - 允许用户上传个人资料照片。

如果在阅读本指南时遇到问题,请参考 GitHub 上的完整示例。
项目设置#
在开始构建之前,您需要设置数据库和 API。您可以通过在 Supabase 中启动一个新项目,然后在数据库中创建一个“schema”来完成此操作。
创建项目#
- 在 Supabase 控制面板中创建一个新项目。
- 输入您的项目详细信息。
- 等待新的数据库启动。
设置数据库 schema#
现在设置数据库 schema。您可以使用 SQL 编辑器中的“用户管理 Starter”快速入门,或者您可以复制/粘贴下面的 SQL 并运行它。
获取 API 详细信息#
现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。
为此,您需要从 项目的 Connect 对话框中获取项目 URL 和密钥。
API 密钥的更改
Supabase 正在更改密钥的工作方式,以提高项目安全性和开发人员体验。您可以 阅读完整的公告,但在过渡期间,您可以使用当前的 anon 和 service_role 密钥以及新的可发布密钥,格式为 sb_publishable_xxx,它将取代旧的密钥。
在大多数情况下,您可以从 项目的 Connect 对话框中获取正确的密钥,但如果您需要特定的密钥,可以在 项目设置页面的 API 密钥部分中找到所有密钥
- 对于旧版密钥,从 旧版 API 密钥 选项卡中复制
anon密钥用于客户端操作,并复制service_role密钥用于服务器端操作。 - 对于新密钥,打开 API 密钥 选项卡,如果您还没有可发布密钥,请单击 创建新的 API 密钥,并复制 可发布密钥 部分中的值。
阅读 API 密钥文档 以全面了解所有密钥类型及其用途。
构建应用#
从头开始构建 Angular 应用。
初始化 Angular 应用#
您可以使用 Angular CLI 初始化一个名为 supabase-angular 的应用。该命令设置了一些默认值,您可以根据需要进行更改
1npx ng new supabase-angular --routing false --style css --standalone false --ssr false2cd supabase-angular然后,安装唯一的附加依赖项:supabase-js
1npm install @supabase/supabase-js最后,将环境变量保存在一个新的 src/environments/environment.ts 文件中。您需要先创建 src/environments 目录。您只需要之前复制的 API URL 和密钥 即可。应用程序在浏览器中公开这些变量,这很好,因为您启用了数据库上的 行级别安全。
src/environments/environment.ts
1export 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
1import { Injectable } from '@angular/core'2import {3 AuthChangeEvent,4 createClient,5 Session,6 SupabaseClient,7 User,8} from '@supabase/supabase-js'9import { environment } from '../environments/environment'1011export interface Profile {12 id?: string13 username: string14 website: string15 avatar_url: string16}1718@Injectable({19 providedIn: 'root',20})21export class SupabaseService {22 private supabase: SupabaseClient2324 constructor() {25 this.supabase = createClient(environment.supabaseUrl, environment.supabaseKey)26 }2728 async getUser(): Promise<User | null> {29 const { data, error } = await this.supabase.auth.getUser()30 if (error) {31 return null32 }33 return data.user34 }3536 profile(user: User) {37 return this.supabase38 .from('profiles')39 .select(`username, website, avatar_url`)40 .eq('id', user.id)41 .single()42 }4344 authChanges(callback: (event: AuthChangeEvent, session: Session | null) => void) {45 return this.supabase.auth.onAuthStateChange(callback)46 }4748 signIn(email: string) {49 return this.supabase.auth.signInWithOtp({ email })50 }5152 signOut() {53 return this.supabase.auth.signOut()54 }5556 updateProfile(profile: Profile) {57 const update = {58 ...profile,59 updated_at: new Date(),60 }6162 return this.supabase.from('profiles').upsert(update)63 }6465 downLoadImage(path: string) {66 return this.supabase.storage.from('avatars').download(path)67 }6869 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,并添加以下代码。
1import { Component } from '@angular/core'2import { FormBuilder, FormGroup } from '@angular/forms'3import { SupabaseService } from '../supabase.service'45@Component({6 selector: 'app-auth',7 templateUrl: './auth.component.html',8 styleUrls: ['./auth.component.css'],9 standalone: false,10})11export class AuthComponent {12 signInForm!: FormGroup13 constructor(14 private readonly supabase: SupabaseService,15 private readonly formBuilder: FormBuilder16 ) {}1718 loading = false19 ngOnInit() {20 this.signInForm = this.formBuilder.group({21 email: '',22 })23 }2425 async onSubmit(): Promise<void> {26 try {27 this.loading = true28 const email = this.signInForm.value.email as string29 const { error } = await this.supabase.signIn(email)30 if (error) throw error31 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 = false39 }40 }41}账户页面#
用户还需要一种编辑他们的个人资料详细信息并在登录后管理他们的帐户的方式。使用 Angular CLI 命令 ng g c account 创建一个 AccountComponent,并添加以下代码。
1import { Component, Input, OnInit } from '@angular/core'2import { FormBuilder, FormGroup } from '@angular/forms'3import { User } from '@supabase/supabase-js'4import { Profile, SupabaseService } from '../supabase.service'56@Component({7 selector: 'app-account',8 templateUrl: './account.component.html',9 styleUrls: ['./account.component.css'],10 standalone: false,11})12export class AccountComponent implements OnInit {13 loading = false14 profile!: Profile15 updateProfileForm!: FormGroup1617 get avatarUrl() {18 return this.updateProfileForm.value.avatar_url as string19 }2021 async updateAvatar(event: string): Promise<void> {22 this.updateProfileForm.patchValue({23 avatar_url: event,24 })25 await this.updateProfile()26 }2728 @Input()29 user!: User3031 constructor(32 private readonly supabase: SupabaseService,33 private formBuilder: FormBuilder34 ) {35 this.updateProfileForm = this.formBuilder.group({36 username: '',37 website: '',38 avatar_url: '',39 })40 }4142 async ngOnInit(): Promise<void> {43 await this.getProfile()4445 const { username, website, avatar_url } = this.profile46 this.updateProfileForm.patchValue({47 username,48 website,49 avatar_url,50 })51 }5253 async getProfile() {54 try {55 this.loading = true56 const { data: profile, error, status } = await this.supabase.profile(this.user)5758 if (error && status !== 406) {59 throw error60 }6162 if (profile) {63 this.profile = profile64 }65 } catch (error) {66 if (error instanceof Error) {67 alert(error.message)68 }69 } finally {70 this.loading = false71 }72 }7374 async updateProfile(): Promise<void> {75 try {76 this.loading = true7778 const username = this.updateProfileForm.value.username as string79 const website = this.updateProfileForm.value.website as string80 const avatar_url = this.updateProfileForm.value.avatar_url as string8182 const { error } = await this.supabase.updateProfile({83 id: this.user.id,84 username,85 website,86 avatar_url,87 })88 if (error) throw error89 } catch (error) {90 if (error instanceof Error) {91 alert(error.message)92 }93 } finally {94 this.loading = false95 }96 }9798 async signOut() {99 await this.supabase.signOut()100 }101}个人资料照片#
每个 Supabase 项目都配置了 存储,用于管理大型文件,如照片和视频。
创建一个上传小部件#
为用户创建一个头像,以便他们可以上传个人资料照片。使用 Angular CLI 命令 ng g c avatar 创建一个 AvatarComponent,并添加以下代码。
1import { Component, EventEmitter, Input, Output } from '@angular/core'2import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser'3import { SupabaseService } from '../supabase.service'45@Component({6 selector: 'app-avatar',7 templateUrl: './avatar.component.html',8 styleUrls: ['./avatar.component.css'],9 standalone: false,10})11export class AvatarComponent {12 _avatarUrl: SafeResourceUrl | undefined13 uploading = false1415 @Input()16 set avatarUrl(url: string | null) {17 if (url) {18 this.downloadImage(url)19 }20 }2122 @Output() upload = new EventEmitter<string>()2324 constructor(25 private readonly supabase: SupabaseService,26 private readonly dom: DomSanitizer27 ) {}2829 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 }4142 async uploadAvatar(event: any) {43 try {44 this.uploading = true45 if (!event.target.files || event.target.files.length === 0) {46 throw new Error('You must select an image to upload.')47 }4849 const file = event.target.files[0]50 const fileExt = file.name.split('.').pop()51 const filePath = `${Math.random()}.${fileExt}`5253 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 = false61 }62 }63}启动!#
现在您已经准备好所有组件,请更新 AppComponent
1import { Component, OnInit } from '@angular/core'2import { User } from '@supabase/supabase-js'3import { SupabaseService } from './supabase.service'45@Component({6 selector: 'app-root',7 templateUrl: './app.component.html',8 styleUrls: ['./app.component.css'],9 standalone: false,10})11export class AppComponent implements OnInit {12 constructor(private readonly supabase: SupabaseService) {}1314 title = 'angular-user-management'15 user: User | null = null1617 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
1import { NgModule } from '@angular/core'2import { BrowserModule } from '@angular/platform-browser'3import { ReactiveFormsModule } from '@angular/forms'45import { AppComponent } from './app.component'6import { AuthComponent } from './auth/auth.component'7import { AccountComponent } from './account/account.component'8import { AvatarComponent } from './avatar/avatar.component'910@NgModule({11 declarations: [AppComponent, AuthComponent, AccountComponent, AvatarComponent],12 imports: [BrowserModule, ReactiveFormsModule],13 providers: [],14 bootstrap: [AppComponent],15})16export class AppModule {}完成此操作后,在终端中运行该应用程序
1npm run start在浏览器中打开 localhost:4200,您应该会看到完成的应用。

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