Angular应用程序的构建方法及过程是什么
Admin 2022-10-29 群英技术资讯 285 次浏览
MemFire Cloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专注于编写前端应用程序代码,加速WEB或APP应用开发。
此示例提供了使用 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(所有)"操作,点击“创建策略”按钮,如下图。
创建云存储的存储桶,用来存储用户的头像图片,涉及操作包括:
1、创建一个存储桶avatars
在该应用的云存储导航栏,点击“新建Bucket”按钮,创建存储桶avatars。
2、允许每个用户可以查看存储桶avatars
选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:
选择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 <=16.10) 。
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: `
Memfiredb + Angular
使用下面的电子邮件通过魔术链接登录
{{loading ? 'Loading' : 'Send magic link'}}
`,
})
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: `
邮箱
名称
网址
{{loading ? 'Loading ...' : 'Update'}}
退出登录
`
})
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: `
`
})
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: `
{{uploading ? 'Uploading ...' : 'Upload'}}
`,
})
export class AvatarComponent {
_avatarUrl: SafeResourceUrl | undefined;
uploading = false;
@Input()
set avatarUrl(url: string | undefined) {
if (url) {
this.downloadImage(url);
}
};
@Output() upload = new EventEmitter
();
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;
}
}
}
登录后复制
然后我们可以在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: `
邮箱
名称
网址
{{loading ? 'Loading ...' : 'Update'}}
退出登录
`
})
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();
}
}
登录后复制
恭喜!在这个阶段,您拥有一个功能齐全的应用程序!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要为大家详细介绍了react封装全局弹框的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章我们主要了解JS与CSS阻塞的问题,我们知道JS与CSS中都阻塞,但是阻塞不同,JS阻塞DOM树的构建,CSS阻塞页面的显示,接下来我们就详细的了解一下。
目录1.安装wavesurfer2.在页面导入3.上源码4.注释:之前给大家介绍过vue中音频wavesurfer.js的使用方法,感兴趣的朋友可以点击查看,今天继续给大家普及vue解决音频可视化播放,使用wavesurfer.js问题,效果图如下所示:上效果:1.安装wavesurfernpm install wav
有时候我们在一些网站上会看到,广告栏消息有上下滚动的展示效果,那么这个功能是怎样实现的呢?这篇文章就给大家分享使用vue实现广告消息上下滚动的功能,实现代码和效果如下:
这篇文章主要介绍了JavaScript数据结构与算法,文章围绕主题展开数据结构与算法的概念,以及几种常见的数据结构是什么,有什么优点和缺,具有一定的参考价值,需要的小伙伴可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008