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. プロファイル テーブルを作成します。
#データ テーブル ページで、[新しいデータ テーブル] をクリックします。ページ構成は次のとおりです: プロファイル テーブルのフィールド ID は、auth.users テーブルの ID フィールド (uuid タイプ) の外部キーに関連しています。 2. プロファイルの RLS データ セキュリティ アクセス ルールを有効にします; 作成したプロファイル テーブルを選択し、以下の図に示すようにテーブルの権限バーをクリックし、[RLS を有効にする] をクリックします。ボタン 3. 各ユーザーに公開個人情報の表示を許可します; [新しいルール] ボタンをクリックし、ポップアップ ボックスで [ルール] を選択します。以下に示すように、「すべてのユーザーのアクセスを有効にする」権限」にポリシー名を入力し、「SELECT (クエリ)」操作を選択して、「ポリシーの作成」ボタンをクリックします。 4. ユーザーに自分の個人プロフィール情報の追加、削除、変更、確認のみを許可します。[新しいルール] ボタンをクリックし、以下に示すように、ポップアップ ボックスで「 ユーザー ID に基づいてユーザーのアクセス許可を有効にする」を選択し、ポリシー名を入力して「すべて」操作を選択し、「ポリシーの作成」ボタンをクリックします。1. バケット アバターの作成アプリケーションのクラウド ストレージ ナビゲーション バーで、[新しいバケット] ボタンをクリックしてバケット アバターを作成します。 2.各ユーザーにバケットのアバターの表示を許可しますバケットのアバターを選択し、
権限設定列に切り替えて、次の図に示すように、[新しいルール] ボタンをクリックすると、ポリシー編集ポップアップ ボックスが表示され、[カスタム] を選択します。 下の図に示すように、SELECT 操作を選択し、ポリシー名を入力して、[ポリシーの生成] ボタンをクリックします。 3. ユーザーにバケット アバターのアップロードを許可します。 バケット アバターを選択し、Permission Settings 列に切り替えて、クリックします。 「新規」 次の図に示すように、「ルール」ボタンをクリックしてポリシー編集ポップアップ ボックスを表示し、「カスタム」を選択します。 INSERT 操作を選択します。以下に示すように、ポリシー名を入力し、「ポリシーの生成」ボタンをクリックします。 結果の表示 すべてのデータ テーブルと RLS SQL (ポリシー名は英語に置き換えられます) データベース テーブルをいくつか作成したので、自動生成された API を使用してデータを挿入できます。 API 設定から URL と anon のキーを取得するだけです。 [アプリケーション] -> [概要] ページで、サービス アドレスとトークン情報を取得します。 Anon (公開) キーはクライアント API キーです。ユーザーがログインするまで、データベースへの「匿名アクセス」が許可されます。ログイン後、キーはユーザー自身のログイン トークンに切り替わります。これにより、データの行レベルのセキュリティが有効になります。 注: service_role (秘密) キーは、セキュリティ ポリシーをバイパスして、データへの完全なアクセスを提供します。このキーは秘密にしておき、クライアントやブラウザでは決して使用せず、サーバー環境で使用する必要があります。後続のサンプル コードでは、supabaseUrl と supabaseKey を指定する必要があります。 ユーザーが電子メール内のマジック リンクをクリックしてログインするとき、アプリケーションのログイン インターフェイスにジャンプする必要があります。ここでは、認証設定で関連する URL リダイレクトを構成する必要があります。 最終的なアプリケーションはローカル ポート 4200 (または他のポート) で起動されるため、ここでは URL を一時的に http://localhost:4200 に設定します。このインターフェイスで独自の SMTP サーバーをカスタマイズして使用することもできます。 Angular アプリケーションを最初から構築してみましょう。 Angular CLI を使用して、 Angular には Node.js (>=14.15 次に、唯一の追加の依存関係をインストールしましょう: supabase-js 最後に、環境変数をenvironment.tsに保存します。必要なのはAPIです。 src/environments/environment.ts ファイル API 認証情報を取得したので、-- 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 キーの取得
認証設定
アプリケーションの構築
プロジェクトの初期化
memfiredb-angular
:npm install -g @angular/cli
npx ng new memfiredb-angular --routing false --style css
cd memfiredb-angular
npm install @supabase/supabase-js
anon
でコピーした URL とキー。 export const environment = {
production: false,
supabaseUrl: "YOUR_SUPABASE_URL",
supabaseKey: "YOUR_SUPABASE_KEY"
};
SupabaseService
を作成して、ng g s supabase## によって初期化します。 # Supabase クライアントであり、Supabase API と通信するための機能を実装します。 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; }
AuthComponent を作成します。 ng 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; } } }
#ユーザー情報ページ
##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(); } }
プロジェクト エントリ ファイルを変更します#すべてのコンポーネントの準備ができたので、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 中国語 Web サイトの他の関連記事を参照してください。