如何建構Angular應用程式?以下這篇文章為大家介紹一下使用MemFire Cloud建立Angular應用程式的方法,希望對大家有幫助!
【相關教學推薦:《angular教學》】
MemFire Cloud是一款提供雲資料庫,使用者可以建立雲端資料庫,並對資料庫進行管理,也可以對資料庫進行備份操作。它還提供後端即服務,使用者可以在1分鐘內新建一個應用,使用自動產生的API和SDK,存取雲端資料庫、物件儲存、使用者認證與授權等功能,可專注於編寫前端應用程式程式碼,加速WEB或APP應用開發。
學習視訊位址:www.bilibili.com/video/BV1wt…
此範例提供了使用MemFire Cloud 和angular建立簡單用戶管理應用程式(從頭開始)的步驟。這包括:
在本指南結束時,您將擁有一個允許使用者登入和更新一些基本個人資料詳細資訊的應用程式:
目的:我們的應用程式就是透過在這裡創建的應用程式來獲得資料庫、雲端儲存等一系列資源,並獲得該應用程式專屬的API存取連結和存取金鑰,使用者可以輕鬆的與以上資源進行互動。
登入cloud.memfiredb.com/auth/login 建立應用程式
##建立資料表點選應用,視圖化建立資料表1、建立profiles表;在資料表頁面,點選“新建資料表”,頁面配置如下: 其中profiles表格欄位id和auth.users表中的id欄位(uuid類型)外鍵關聯。 2、開啟Profiles的RLS資料安全存取規則;選取已建立的Profiles表,點選表格權限欄,如下圖所示,點選"啟用RLS"按鈕 3、允許每個使用者可以查看公共的個人資訊資料;點擊"新規則"按鈕,在彈出彈框中,選擇"為所有使用者啟用訪問權限",輸入策略名稱,選擇"SELECT(查詢)"操作,點選「建立策略」按鈕,如下圖。 4、只允許使用者增刪改查本人的個人資料資訊;點擊"新規則"按鈕,在彈出彈框中,選擇"根據使用者ID為使用者啟用存取權限",輸入策略名稱,選擇"ALL(所有)"操作,點選「建立原則」按鈕,如下圖。權限設定欄,點擊“新規則”按鈕,彈出策略編輯彈框,選擇“自訂”,如下圖所示:
選擇SELECT操作,輸入策略名稱,點選「產生策略」按鈕,如下圖所示。
3、允許使用者上傳儲存桶avatars;
#選取儲存桶avatars,切換到權限設定欄,點選「新規則”按鈕,彈出策略編輯彈框,選擇“自訂”,如下圖所示:
#選擇INSERT操作,輸入策略名稱,點擊“生成策略”按鈕,如下圖所示。
查看結果
#所有資料表及RLS的sql(策略名稱用英文取代)
-- Create a table for public "profiles" create table profiles ( id uuid references auth.users not null, updated_at timestamp with time zone, username text unique, avatar_url text, website text, primary key (id), unique(username), ); alter table profiles enable row level security; create policy "Public profiles are viewable by everyone." on profiles for select using ( true ); create policy "Users can insert their own profile." on profiles for insert with check ( auth.uid() = id ); create policy "Users can update own profile." on profiles for update using ( auth.uid() = id ); -- Set up Storage! insert into storage.buckets (id, name) values ('avatars', 'avatars'); create policy "Avatar images are publicly accessible." on storage.objects for select using ( bucket_id = 'avatars' ); create policy "Anyone can upload an avatar." on storage.objects for insert with check ( bucket_id = 'avatars' );
現在您已經建立了一些資料庫表,您可以使用自動產生的API 插入資料。我們只需要從API設定中取得URL和anon的金鑰。
在應用程式->概括頁面,取得服務位址以及token資訊。
Anon(公開)金鑰是客戶端API金鑰。它允許“匿名訪問”您的資料庫,直到用戶登入。登入後,密鑰將切換到使用者自己的登入令牌。這將為資料啟用行級安全性。
注意:service_role(秘密)金鑰可以繞過任何安全性策略完全存取您的資料。這個金鑰必須保密,並且要在伺服器環境中使用,絕不能在客戶端或瀏覽器上使用。在後續範例程式碼中,需要提供supabaseUrl和supabaseKey。
當使用者點擊郵件內魔法連結進行登入時,是需要跳到我們應用的登入介面的。這裡需要在認證設定中進行相關URL重定向的設定。
因為我們最終的應用程式會在本地的4200埠啟動(也或其他連接埠),所以這裡我們暫時將url設定為http://localhost:4200
除此之外,在此介面也可以自訂使用我們自己的smtp伺服器。
讓我們從頭開始建立 Angular應用程式。
我們可以使用Angular CLI來初始化一個名為memfiredb-angular
:
Angular 需要Node.js (>=14.15
npm install -g @angular/cli npx ng new memfiredb-angular --routing false --style css cd memfiredb-angular
然後讓我們安裝唯一的附加依賴項:supabase-js
npm install @supabase/supabase-js
最後,我們要將環境變數保存在environment.ts, 我們需要的是API URL 和您上面anon
複製的金鑰。
src/environments/environment.ts檔案
export const environment = { production: false, supabaseUrl: "YOUR_SUPABASE_URL", supabaseKey: "YOUR_SUPABASE_KEY" };
現在我們已經有了API 憑證,透過ng g s supabase
建立一個SupabaseService來初始化Supabase 用戶端並實作與Supabase API 通訊的函數。
src/app/supabase.service.ts
import { Injectable } from '@angular/core'; 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() { 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); } }
可以看到介面實在是不太優雅,更新下樣式,讓它好看一些。修改src/styles.css
檔。
html, body { --custom-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; --custom-bg-color: #101010; --custom-panel-color: #222; --custom-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.8); --custom-color: #fff; --custom-color-brand: #24b47e; --custom-color-secondary: #666; --custom-border: 1px solid #333; --custom-border-radius: 5px; --custom-spacing: 5px; padding: 0; margin: 0; font-family: var(--custom-font-family); background-color: var(--custom-bg-color); } * { color: var(--custom-color); font-family: var(--custom-font-family); box-sizing: border-box; } html, body, #__next { height: 100vh; width: 100vw; overflow-x: hidden; } /* Grid */ .container { width: 90%; margin-left: auto; margin-right: auto; } .row { position: relative; width: 100%; } .row [class^='col'] { float: left; margin: 0.5rem 2%; min-height: 0.125rem; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12 { width: 96%; } .col-1-sm { width: 4.33%; } .col-2-sm { width: 12.66%; } .col-3-sm { width: 21%; } .col-4-sm { width: 29.33%; } .col-5-sm { width: 37.66%; } .col-6-sm { width: 46%; } .col-7-sm { width: 54.33%; } .col-8-sm { width: 62.66%; } .col-9-sm { width: 71%; } .col-10-sm { width: 79.33%; } .col-11-sm { width: 87.66%; } .col-12-sm { width: 96%; } .row::after { content: ''; display: table; clear: both; } .hidden-sm { display: none; } @media only screen and (min-width: 33.75em) { /* 540px */ .container { width: 80%; } } @media only screen and (min-width: 45em) { /* 720px */ .col-1 { width: 4.33%; } .col-2 { width: 12.66%; } .col-3 { width: 21%; } .col-4 { width: 29.33%; } .col-5 { width: 37.66%; } .col-6 { width: 46%; } .col-7 { width: 54.33%; } .col-8 { width: 62.66%; } .col-9 { width: 71%; } .col-10 { width: 79.33%; } .col-11 { width: 87.66%; } .col-12 { width: 96%; } .hidden-sm { display: block; } } @media only screen and (min-width: 60em) { /* 960px */ .container { width: 75%; max-width: 60rem; } } /* Forms */ label { display: block; margin: 5px 0; color: var(--custom-color-secondary); font-size: 0.8rem; text-transform: uppercase; } input { width: 100%; border-radius: 5px; border: var(--custom-border); padding: 8px; font-size: 0.9rem; background-color: var(--custom-bg-color); color: var(--custom-color); } input[disabled] { color: var(--custom-color-secondary); } /* Utils */ .block { display: block; width: 100%; } .inline-block { display: inline-block; width: 100%; } .flex { display: flex; } .flex.column { flex-direction: column; } .flex.row { flex-direction: row; } .flex.flex-1 { flex: 1 1 0; } .flex-end { justify-content: flex-end; } .flex-center { justify-content: center; } .items-center { align-items: center; } .text-sm { font-size: 0.8rem; font-weight: 300; } .text-right { text-align: right; } .font-light { font-weight: 300; } .opacity-half { opacity: 50%; } /* Button */ button, .button { color: var(--custom-color); border: var(--custom-border); background-color: var(--custom-bg-color); display: inline-block; text-align: center; border-radius: var(--custom-border-radius); padding: 0.5rem 1rem; cursor: pointer; text-align: center; font-size: 0.9rem; text-transform: uppercase; } button.primary, .button.primary { background-color: var(--custom-color-brand); border: 1px solid var(--custom-color-brand); } /* Widgets */ .card { width: 100%; display: block; border: var(--custom-border); border-radius: var(--custom-border-radius); padding: var(--custom-spacing); } .avatar { border-radius: var(--custom-border-radius); overflow: hidden; max-width: 100%; } .avatar.image { object-fit: cover; } .avatar.no-image { background-color: #333; border: 1px solid rgb(200, 200, 200); border-radius: 5px; } .footer { position: absolute; max-width: 100%; bottom: 0; left: 0; right: 0; display: flex; flex-flow: row; border-top: var(--custom-border); background-color: var(--custom-bg-color); } .footer div { padding: var(--custom-spacing); display: flex; align-items: center; width: 100%; } .footer div > img { height: 20px; margin-left: 10px; } .footer > div:first-child { display: none; } .footer > div:nth-child(2) { justify-content: left; } @media only screen and (min-width: 60em) { /* 960px */ .footer > div:first-child { display: flex; } .footer > div:nth-child(2) { justify-content: center; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .mainHeader { width: 100%; font-size: 1.3rem; margin-bottom: 20px; } .avatarPlaceholder { border: var(--custom-border); border-radius: var(--custom-border-radius); width: 35px; height: 35px; background-color: rgba(255, 255, 255, 0.2); display: flex; align-items: center; justify-content: center; } /* Auth */ .auth-widget { display: flex; flex-direction: column; gap: 20px; } .auth-widget > .button { display: flex; align-items: center; justify-content: center; border: none; background-color: #444444; text-transform: none !important; transition: all 0.2s ease; } .auth-widget .button:hover { background-color: #2a2a2a; } .auth-widget .button > .loader { width: 17px; animation: spin 1s linear infinite; filter: invert(1); } /* Account */ .account { display: flex; flex-direction: column; gap: 20px; } .account > * > .avatarField { display: flex; align-items: center; margin-bottom: 30px; } .account > * > .avatarField > .avatarContainer { margin-right: 20px; } /* Profile Card */ .profileCard { border-radius: 5px; display: flex; border: var(--custom-border); background-color: var(--custom-panel-color); padding: 20px 20px; margin-bottom: 20px; } .profileCard:last-child { margin-bottom: 0px; } .profileCard > .userInfo { margin-left: 20px; font-weight: 300; display: flex; flex-direction: column; justify-content: center; } .profileCard > .userInfo > p { margin: 0; } .profileCard > .userInfo > .username { font-size: 1.3rem; font-weight: 500; margin-bottom: 5px; } .profileCard > .userInfo > .website { font-size: 0.9rem; color: var(--custom-color-brand); margin-bottom: 10px; text-decoration: none; }
#讓我們設定一個 Angular 元件來管理登入和註冊。我們將使用 Magic Links,因此用戶無需使用密碼即可使用電子郵件登入。使用Angular CLI 命令建立一個AuthComponent 。 ng g c auth
src/app/auth/auth.component.ts
import { Component } from '@angular/core'; import {SupabaseService} from "../supabase.service"; @Component({ selector: 'app-auth', template: ` <div> <form> <h1>Memfiredb + Angular</h1> <p>使用下面的电子邮件通过魔术链接登录</p> <div> <input> </div> <div> <button> {{loading ? 'Loading' : 'Send magic link'}} </button> </div> </form> </div> `, }) export class AuthComponent { loading = false; constructor(private readonly supabase: SupabaseService) { } async handleLogin(input: string) { try { this.loading = true; await this.supabase.signIn(input); alert('请检查您的电子邮件以获取登录链接!'); } catch (error:any) { alert(error.error_description || error.message) } finally { this.loading = false; } } }
#使用者登入後,我們可以允許他們編輯他們的個人資料詳細資料並管理他們的帳戶。使用Angular CLI 指令建立一個AccountComponent 。 ng g c account
src/app/account/account.component.ts
import {Component, Input, OnInit} from '@angular/core'; import {Profile, SupabaseService} from "../supabase.service"; import {Session} from "@supabase/supabase-js"; @Component({ selector: 'app-account', template: ` <div> <div> <label>邮箱</label> <input> </div> <div> <label>名称</label> <input> </div> <div> <label>网址</label> <input> </div> <div> <button> {{loading ? 'Loading ...' : 'Update'}} </button> </div> <div> <button> 退出登录 </button> </div> </div> ` }) export class AccountComponent implements OnInit { loading = false; profile: Profile | undefined; @Input() session: Session | undefined; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.getProfile(); } async getProfile() { try { this.loading = true; let {data: profile, error, status} = await this.supabase.profile; if (error && status !== 406) { throw error; } if (profile) { this.profile = profile; } } catch (error:any) { alert(error.message) } finally { this.loading = false; } } async updateProfile(username: string, website: string, avatar_url: string = '') { try { this.loading = true; await this.supabase.updateProfile({username, website, avatar_url}); } catch (error:any) { alert(error.message); } finally { this.loading = false; } } async signOut() { await this.supabase.signOut(); } }
#現在我們已經準備好了所有元件,讓我們更新AppComponent:
src/app/app.component.ts
import {Component, OnInit} from '@angular/core'; import {SupabaseService} from "./supabase.service"; @Component({ selector: 'app-root', template: ` <div> <app-account></app-account> <ng-template> <app-auth></app-auth> </ng-template> </div> ` }) export class AppComponent implements OnInit { session = this.supabase.session; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.supabase.authChanges((_, session) => this.session = session); } }
完成后,在终端窗口中运行它:
npm run start
然后打开浏览器到 http://localhost:4200,你应该会看到完整的应用程序。
每个 MemFire Cloud项目都配置了存储,用于管理照片和视频等大文件。
让我们为用户创建一个头像,以便他们可以上传个人资料照片。使用Angular CLI 命令创建AvatarComponent 。 ng g c avatar
src/app/avatar/avatar.component.ts
import {Component, EventEmitter, Input, Output} from '@angular/core'; import {SupabaseService} from "../supabase.service"; import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; @Component({ selector: 'app-avatar', template: ` <div> <img alt="聊聊如何使用MemFire Cloud建立Angular應用程式" > </div> <div></div> <div> <label> {{uploading ? 'Uploading ...' : 'Upload'}} </label> <input> </div> `, }) export class AvatarComponent { _avatarUrl: SafeResourceUrl | undefined; uploading = false; @Input() set avatarUrl(url: string | undefined) { if (url) { this.downloadImage(url); } }; @Output() upload = new EventEmitter<string>(); 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:any) { console.error('下载图片出错: ', error.message); } } async uploadAvatar(event: any) { try { this.uploading = true; if (!event.target.files || event.target.files.length === 0) { throw new Error('必须选择要上载的图像。'); } const file = event.target.files[0]; const fileExt = file.name.split('.').pop(); const fileName = `${Math.random()}.${fileExt}`; const filePath = `${fileName}`; await this.supabase.uploadAvatar(filePath, file); this.upload.emit(filePath); this.downloadImage(filePath) } catch (error:any) { alert(error.message); } finally { this.uploading = false; } } }</string>
然后我们可以在AccountComponent html 模板的顶部添加小部件:
src/app/account/account.component.ts
import {Component, Input, OnInit} from '@angular/core'; import {Profile, SupabaseService} from "../supabase.service"; import {Session} from "@supabase/supabase-js"; @Component({ selector: 'app-account', template: ` <app-avatar> </app-avatar> <div> <div> <label>邮箱</label> <input> </div> <div> <label>名称</label> <input> </div> <div> <label>网址</label> <input> </div> <div> <button> {{loading ? 'Loading ...' : 'Update'}} </button> </div> <div> <button> 退出登录 </button> </div> </div> ` }) export class AccountComponent implements OnInit { loading = false; profile: Profile | undefined; @Input() session: Session | undefined; constructor(private readonly supabase: SupabaseService) { } ngOnInit() { this.getProfile(); } async getProfile() { try { this.loading = true; let {data: profile, error, status} = await this.supabase.profile; if (error && status !== 406) { throw error; } if (profile) { this.profile = profile; } } catch (error:any) { alert(error.message) } finally { this.loading = false; } } async updateProfile(username: string, website: string, avatar_url: string = '') { try { this.loading = true; await this.supabase.updateProfile({username, website, avatar_url}); alert("修改成功") } catch (error:any) { alert(error.message); } finally { this.loading = false; } } async signOut() { await this.supabase.signOut(); } }
恭喜!在这个阶段,您拥有一个功能齐全的应用程序!
更多编程相关知识,请访问:编程视频!!
以上是聊聊如何使用MemFire Cloud建立Angular應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!