• 技术文章 >web前端 >js教程

    聊聊如何使用MemFire Cloud构建Angular应用程序

    青灯夜游青灯夜游2022-08-30 20:44:12转载371
    如何构建Angular应用程序?下面本篇文章给大家介绍一下使用MemFire Cloud构建Angular应用程序的方法,希望对大家有所帮助!

    大前端成长进阶课程:进入学习

    【相关教程推荐:《angular教程》】

    MemFire Cloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专注于编写前端应用程序代码,加速WEB或APP应用开发。

    学习视频地址: www.bilibili.com/video/BV1wt…

    此示例提供了使用 MemFire Cloud 和 angular构建简单用户管理应用程序(从头开始)的步骤。这包括:

    在本指南结束时,您将拥有一个允许用户登录和更新一些基本个人资料详细信息的应用程序:

    1.png

    创建应用

    目的:我们的应用就是通过在这里创建的应用来获得数据库、云存储等一系列资源,并获得该应用专属的API访问链接和访问密钥,用户可以轻松的与以上资源进行交互。

    登录cloud.memfiredb.com/auth/login 创建应用

    2.png

    创建数据表

    点击应用,视图化创建数据表

    1、创建profiles表;

    在数据表页面,点击“新建数据表”,页面配置如下:

    3.png

    其中profiles表字段id和auth.users表中的id字段(uuid类型)外键关联。

    2、开启Profiles的RLS数据安全访问规则;

    选中创建的Profiles表,点击表权限栏,如下图所示,点击"启用RLS"按钮

    4.png

    3、允许每个用户可以查看公共的个人信息资料;

    点击"新规则"按钮,在弹出弹框中,选择"为所有用户启用访问权限",输入策略名称,选择"SELECT(查询)"操作,点击“创建策略”按钮,如下图。

    5.png

    4、仅允许用户增删改查本人的个人资料信息;

    点击"新规则"按钮,在弹出弹框中,选择"根据用户ID为用户启用访问权限",输入策略名称,选择"ALL(所有)"操作,点击“创建策略”按钮,如下图。

    6.png

    创建avatars存储桶

    创建云存储的存储桶,用来存储用户的头像图片,涉及操作包括:

    1、创建一个存储桶avatars

    在该应用的云存储导航栏,点击“新建Bucket”按钮,创建存储桶avatars。

    7.png

    2、允许每个用户可以查看存储桶avatars

    选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:

    8.png

    选择SELECT操作,输入策略名称,点击“生成策略”按钮,如下图所示。

    9.png

    3、允许用户上传存储桶avatars;

    选中存储桶avatars,切换到权限设置栏,点击“新规则”按钮,弹出策略编辑弹框,选择“自定义”,如下图所示:

    10.png

    选择INSERT操作,输入策略名称,点击“生成策略”按钮,如下图所示。

    11.png

    查看结果

    12.png

    所有数据表及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 插入数据。我们只需要从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: `
        <div class="row flex flex-center">
          <form class="col-6 form-widget">
            <h1 class="header">Memfiredb + Angular</h1>
            <p class="description">使用下面的电子邮件通过魔术链接登录</p>
            <div>
              <input
                #input
                class="inputField"
                type="email"
                placeholder="Your email"
              />
            </div>
            <div>
              <button
                type="submit"
                (click)="handleLogin(input.value)"
              class="button block"
              [disabled]="loading"
              >
              {{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 class="form-widget">
          <div>
            <label for="email">邮箱</label>
            <input id="email" type="text" [value]="session?.user?.email" disabled/>
          </div>
          <div>
            <label for="username">名称</label>
            <input
              #username
              id="username"
              type="text"
              [value]="profile?.username ?? ''"
            />
          </div>
          <div>
            <label for="website">网址</label>
            <input
              #website
              id="website"
              type="url"
              [value]="profile?.website ?? ''"
            />
          </div>
    
          <div>
            <button
              class="button block primary"
              (click)="updateProfile(username.value, website.value)"
              [disabled]="loading"
            >
              {{loading ? 'Loading ...' : 'Update'}}
            </button>
          </div>
    
          <div>
            <button class="button block" (click)="signOut()">
              退出登录
            </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 class="container" style="padding: 50px 0 100px 0">
        <app-account *ngIf="session; else auth" [session]="session"></app-account>
        <ng-template #auth>
          <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
            *ngIf="_avatarUrl"
            [src]="_avatarUrl"
            alt="Avatar"
            class="avatar image"
            style="height: 150px; width: 150px"
          ></div>
        <div *ngIf="!_avatarUrl" class="avatar no-image" style="height: 150px; width: 150px"></div>
        <div style="width: 150px">
          <label class="button primary block" for="single">
            {{uploading ? 'Uploading ...' : 'Upload'}}
          </label>
          <input
            style="visibility: hidden;position: absolute"
            type="file"
            id="single"
            accept="image/*"
            (change)="uploadAvatar($event)"
            [disabled]="uploading"
          />
        </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;
        }
      }
    }

    然后我们可以在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
            [avatarUrl]="this.profile?.avatar_url"
            (upload)="updateProfile(username.value, website.value, $event)">
        </app-avatar>
        <div class="form-widget">
          <div>
            <label for="email">邮箱</label>
            <input id="email" type="text" [value]="session?.user?.email" disabled/>
          </div>
          <div>
            <label for="username">名称</label>
            <input
              #username
              id="username"
              type="text"
              [value]="profile?.username ?? ''"
            />
          </div>
          <div>
            <label for="website">网址</label>
            <input
              #website
              id="website"
              type="url"
              [value]="profile?.website ?? ''"
            />
          </div>
    
          <div>
            <button
              class="button block primary"
              (click)="updateProfile(username.value, website.value)"
              [disabled]="loading"
            >
              {{loading ? 'Loading ...' : 'Update'}}
            </button>
          </div>
    
          <div>
            <button class="button block" (click)="signOut()">
              退出登录
            </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中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:Angular
    上一篇:如何进行Node.js扩展开发?前置知识分享 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 项目过大怎么办?如何合理拆分Angular项目?• 如何上手Angular,先从 8 个开源项目开始!• 4个Angular单元测试编写的小技巧,快来看看!• 5 个管理 Angular 项目的实用技巧(总结分享)• Angular中什么是结构指令?怎么使用?• 一文详解Angular中的依赖注入
    1/1

    PHP中文网