使用 Angular 和 Appwrite 构建实时聊天应用程序🤓
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
Appwrite是一款开源的后端即服务 (BaaS),它为开发者提供了一套核心功能,可用于构建任何技术栈的任何应用程序。功能涵盖数据库交互、身份验证、实时更新等等。
在使用 Angular 构建 Web 应用程序时,通常需要连接不同的 API 来管理数据、验证用户身份,以及监听实时数据更新。连接这些不同服务的 API 可能由多个 API 提供商提供。而使用 Appwrite,您只需一个后端即可完成所有这些操作。本文将向您展示如何使用 Appwrite 快速上手,并通过聊天应用程序验证用户身份、管理数据并监听实时事件。
先决条件
要开始使用 Appwrite,您需要在本地计算机或服务器上安装 Docker。Docker 运行后,使用以下命令安装并运行 Appwrite。
docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.15.0
此外,请查看完整的安装指南以了解更多相关信息。如果一切顺利,您可以访问 Appwrite 控制台并注册您的 root 帐户。
接下来,我们来设置第一个项目。
创建项目
您可以使用 Appwrite 中的项目来托管多个不同的应用程序。要创建项目:
- 点击创建项目
- 点击铅笔图标,然后输入ngchat作为自定义项目 ID
- 输入Angular Chat作为名称
- 点击“创建”
接下来,我们来设置聊天应用程序的数据库和集合。
创建数据库和集合
Appwrite 中的数据库是一组用于管理数据的集合。要创建数据库,请访问“数据库”部分:
- 点击创建数据库
- 输入聊天记录作为自定义数据库 ID
- 输入“聊天”作为名称
- 点击“创建”
收藏品:
- 点击“创建收藏集”
- 输入消息作为自定义集合 ID
- 输入“聊天消息”作为名称
- 点击“创建”
我们还需要配置集合的读/写权限。对于消息,您需要选择文档级权限。您可以根据具体使用场景选择更细粒度的权限。权限页面提供了更多权限详情,确保用户始终拥有其消息的所有权。
创建集合属性
Appwrite 数据库中的每个集合都包含一些属性,这些属性用于模拟您要存储的文档的结构。例如,在聊天应用程序中,您将存储用户的姓名和消息。
创建文档属性
属性可以定义为字符串、数字、电子邮件地址等。要创建属性:
- 点击“创建属性”。
- 选择要创建的属性类型。
使用下表创建聊天所需的属性。
| 钥匙 | 尺寸 | 必需的 | 大批 |
|---|---|---|---|
| 用户 | 32 | 真的 | 错误的 |
| 信息 | 10000 | 真的 | 错误的 |
当在集合中创建文档时,它还会包含额外的元数据,分别用于记录文档的创建时间和更新时间$createdAt。$updatedAt您可以将此元数据用于查询、同步和其他用例。
您还可以执行其他操作,例如切换服务、选择要使用的 OAuth 提供程序等等,但对于此聊天应用程序,使用的是匿名身份验证,并且默认情况下也已启用。
接下来,我们来组装 Angular 应用程序。
使用 Angular 构建
首先,克隆一个已运行 Angular 14 版本并配置了登录和聊天路由的现有仓库。使用以下命令克隆 GitHub 仓库。
git clone git@github.com:brandonroberts/appwrite-angular-chat.git
安装依赖项:
yarn
启动应用程序以运行开发服务器
yarn start
在浏览器中访问http://localhost:4200查看登录页面。
设置 Appwrite 配置
要在我们的 Angular 项目中配置 Appwrite,首先需要为 Appwrite 端点、项目和集合值配置一些环境变量。
更新src/environments/environment.ts
export const environment = {
endpoint: 'http://localhost/v1',
projectId: 'ngchat',
databaseId: 'chat',
chatCollectionId: 'messages',
production: false
};
环境变量设置完成后,继续设置 Appwrite Web SDK。
要初始化 Appwrite Web SDK,请使用之前安装的 appwrite 包,并在 Angular 中设置一些注入令牌,以便能够将 SDK 注入到稍后创建的服务中。
让我们创建 2 个令牌,一个用于 Appwrite 环境变量,一个用于 SDK 实例本身。
创建一个名为 `<filename>` 的新文件src/appwrite.ts,并将 2 个令牌配置为根提供程序。
import { inject, InjectionToken } from '@angular/core';
import {
Account,
Client as Appwrite,
Databases
} from 'appwrite';
import { environment } from 'src/environments/environment';
interface AppwriteConfig {
endpoint: string;
projectId: string;
databaseId: string;
chatCollectionId: string;
}
export const AppwriteEnvironment = new InjectionToken<AppwriteConfig>(
'Appwrite Config',
{
providedIn: 'root',
factory() {
const { endpoint, projectId, databaseId, chatCollectionId } = environment;
return {
endpoint,
databaseId,
projectId,
chatCollectionId,
};
},
}
);
第一个令牌设置环境变量,以便将它们注入到一个或多个服务中。
export const AppwriteApi = new InjectionToken<{
database: Databases;
account: Account;
}>('Appwrite SDK', {
providedIn: 'root',
factory() {
const env = inject(AppwriteEnvironment);
const appwrite = new Appwrite();
appwrite.setEndpoint(env.endpoint);
appwrite.setProject(env.projectId);
const database = new Databases(appwrite, env.databaseId);
const account = new Account(appwrite);
return { database, account };
},
});
第二个令牌创建 Appwrite Web SDK 实例,将端点设置为指向正在运行的 Appwrite 实例,以及之前配置的项目 ID。
Appwrite SDK 设置完成后,让我们创建一些用于身份验证和访问聊天消息的服务。
首先,我们创建一个src/auth.service.ts 文件,用于登录、检查身份验证状态和注销。
import { inject, Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Models } from 'appwrite';
import {
BehaviorSubject,
concatMap,
from,
tap,
mergeMap
} from 'rxjs';
import { AppwriteApi } from './appwrite';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private appwriteAPI = inject(AppwriteApi);
private _user = new BehaviorSubject<Models.User<Models.Preferences> | null>(
null
);
readonly user$ = this._user.asObservable();
constructor(private router: Router) {}
login(name: string) {
const authReq = this.appwriteAPI.account.createAnonymousSession();
return from(authReq).pipe(
mergeMap(() => this.appwriteAPI.account.updateName(name)),
concatMap(() => this.appwriteAPI.account.get()),
tap((user) => this._user.next(user))
);
}
async isLoggedIn() {
try {
const user = await this.appwriteAPI.account.get();
this._user.next(user);
return true;
} catch (e) {
return false;
}
}
async logout() {
try {
await this.appwriteAPI.account.deleteSession('current');
} catch (e) {
console.log(`${e}`);
} finally {
this.router.navigate(['/']);
this._user.next(null);
}
}
}
AuthService将Appwrite SDK 注入到:
- 使用登录方法验证用户身份,更新名称,并将当前用户存储在可观察对象中。
- 检查用户是否已登录并返回布尔值
- 通过清除当前会话来注销用户
使用 Appwrite SDK,所有这些操作都无需使用 Angular 的HttpClient服务。您始终可以直接访问 Appwrite 的 REST API,但这并非必需,因为 SDK 会自动处理这些操作。
接下来,我们创建src/chat.service.ts文件来加载和发送聊天消息。
import { inject, Injectable } from '@angular/core';
import { Models, RealtimeResponseEvent } from 'appwrite';
import { BehaviorSubject, take, concatMap, filter } from 'rxjs';
import { AppwriteApi, AppwriteEnvironment } from './appwrite';
import { AuthService } from './auth.service';
export type Message = Models.Document & {
user: string;
message: string;
};
@Injectable({
providedIn: 'root',
})
export class ChatService {
private appwriteAPI = inject(AppwriteApi);
private appwriteEnvironment = inject(AppwriteEnvironment);
private _messages$ = new BehaviorSubject<Message[]>([]);
readonly messages$ = this._messages$.asObservable();
constructor(private authService: AuthService) {}
loadMessages() {
this.appwriteAPI.database
.listDocuments<Message>(
this.appwriteEnvironment.chatCollectionId,
[],
100,
0,
undefined,
undefined,
[],
['ASC']
)
.then((response) => {
this._messages$.next(response.documents);
});
}
sendMessage(message: string) {
return this.authService.user$.pipe(
filter((user) => !!user),
take(1),
concatMap((user) => {
const data = {
user: user!.name,
message,
};
return this.appwriteAPI.database.createDocument(this.appwriteEnvironment.chatCollectionId,
'unique()',
data,
['role:all'],
[`user:${user!.$id}`]
);
})
);
}
}
聊天服务:
- 注入 Appwrite 环境变量
- 设置聊天消息的可观察对象
- 使用 Appwrite SDK 从消息集合中加载聊天消息
- 获取当前登录用户,使其将聊天消息添加到消息集合中。
- 为文档分配权限,使任何人都可以阅读,但只有特定用户才能更新/删除。
服务设置完成后,我们可以继续开发登录和聊天组件。
构建登录页面
对于登录组件,使用AuthService通过提供的名称进行匿名身份验证登录。
import { Component } from '@angular/core';
import {
FormControl,
FormGroup,
ReactiveFormsModule
} from '@angular/forms';
import { Router } from '@angular/router';
import { tap } from 'rxjs';
import { AuthService } from './auth.service';
@Component({
selector: 'app-login',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<div class="app-container">
<div class="content">
<span class="appwrite-chat">Angular Chat</span>
<div class="login-container">
<form [formGroup]="form" class="login-form" (ngSubmit)="login()">
<p class="login-name">
<label for="name">Name</label>
<input
type="text"
id="name"
formControlName="name"
placeholder="Enter Name"
/>
</p>
<button type="submit">Start Chatting</button>
</form>
</div>
</div>
</div>
`
})
export class LoginComponent {
form = new FormGroup({
name: new FormControl('', { nonNullable: true }),
});
constructor(
private authService: AuthService,
private router: Router
) {}
login() {
const name = this.form.controls.name.value;
this.authService
.login(name)
.pipe(
tap(() => {
this.router.navigate(['/chat']);
})
)
.subscribe();
}
}
身份验证成功后,我们将重定向到聊天页面。
显示聊天消息
使用聊天组件时,首先使用ChatService显示聊天消息:
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import {
FormControl,
FormGroup,
ReactiveFormsModule
} from '@angular/forms';
import { tap } from 'rxjs';
import { ChatService } from './chat.service';
import { AuthService } from './auth.service';
@Component({
selector: 'app-chat',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<div class="chat-container" *ngIf="user$ | async as vm; else loading">
<div class="chat-header">
<div class="title">Let's Chat</div>
<div class="leave" (click)="logout()">Leave Room</div>
</div>
<div class="chat-body">
<div
id="{{ message.$id }}"
*ngFor="let message of messages$ | async"
class="message"
>
<span class="name">{{ message.user }}:</span>
{{ message.message }}
</div>
</div>
<div class="chat-message">
<form [formGroup]="form" (ngSubmit)="sendMessage()">
<input
type="text"
formControlName="message"
placeholder="Type a message..."
/>
<button type="submit" class="send-message">
<svg
class="arrow"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.0737 3.06325C12.8704 2.65671 12.4549 2.3999 12.0004 2.3999C11.5459 2.3999 11.1304 2.65671 10.9271 3.06325L2.52709 19.8632C2.31427 20.2889 2.37308 20.8001 2.67699 21.1663C2.98091 21.5325 3.4725 21.6845 3.93007 21.5537L9.93006 19.8395C10.4452 19.6923 10.8004 19.2214 10.8004 18.6856V13.1999C10.8004 12.5372 11.3376 11.9999 12.0004 11.9999C12.6631 11.9999 13.2004 12.5372 13.2004 13.1999V18.6856C13.2004 19.2214 13.5556 19.6923 14.0707 19.8394L20.0707 21.5537C20.5283 21.6845 21.0199 21.5325 21.3238 21.1663C21.6277 20.8001 21.6865 20.2889 21.4737 19.8632L13.0737 3.06325Z"
fill="#373B4D"
/>
</svg>
</button>
</form>
</div>
</div>
<ng-template #loading>Loading...</ng-template>
`,
styles: [...]
})
export class ChatComponent implements OnInit {
form = new FormGroup({
message: new FormControl('', { nonNullable: true }),
});
user$ = this.authService.user$;
messages$ = this.chatService.messages$;
constructor(
private authService: AuthService,
private chatService: ChatService
) {}
ngOnInit() {
this.chatService.loadMessages();
}
sendMessage() {
const message = this.form.controls.message.value;
this.chatService
.sendMessage(message)
.pipe(
tap(() => {
this.form.reset();
})
)
.subscribe();
}
async logout() {
await this.authService.logout();
}
}
ChatComponent利用AuthService和ChatService来实现以下功能:
- 使用当前登录用户
- 仔细聆听聊天记录
- 在组件的ngOnInit中加载聊天消息
- 使用输入字段通过聊天服务发送消息
- 从聊天页面注销
我们已经能够加载聊天消息,但让我们添加更有趣的部分,集成一些实时聊天消息。
连接到实时事件
Appwrite 提供系统中几乎所有事件的实时更新,例如数据库记录的插入、更新或删除。这些事件通过 WebSocket 提供。要订阅实时更新,请使用listenToMessages方法更新ChatService,以便订阅消息集合中的事件。
export class ChatService {
...
listenToMessages() {
return this.appwriteAPI.database.client.subscribe(
`databases.chat.collections.messages.documents`,
(res: RealtimeResponseEvent<Message>) => {
if (res.events.includes('databases.chat.collections.messages.documents.*.create')) {
const messages: Message[] = [...this._messages$.value, res.payload];
this._messages$.next(messages);
}
}
);
}
}
每当创建新消息时,新消息都会推送到用户的可观察对象中,从而实现实时更新。要开始监听实时事件:
- 更新ChatComponent的ngOnInit函数以调用该方法。
- 存储实时连接以便取消订阅
- 组件销毁时,断开实时连接。
export class ChatComponent implements OnInit, OnDestroy {
messageunSubscribe!: () => void;
form = new FormGroup({
message: new FormControl('', { nonNullable: true }),
});
user$ = this.authService.user$;
messages$ = this.chatService.messages$;
constructor(
private authService: AuthService,
private chatService: ChatService
) {}
ngOnInit() {
this.chatService.loadMessages();
this.messageunSubscribe = this.chatService.listenToMessages();
}
ngOnDestroy() {
this.messageunSubscribe();
}
}
概括
就这样!我们现在拥有了一个功能齐全的 Angular 应用程序,它包含:
- 验证
- 数据库管理
- 实时事件
我们还可以做更多,但正如您所见,有了这些已经具备的核心功能,您几乎可以构建任何应用。而云函数则能帮助您进一步扩展 Appwrite 的功能。
查看运行示例:
https://appwrite-angular-chat.netlify.app
GitHub 代码库:https://github.com/brandonroberts/appwrite-angular-chat
了解更多
🚀入门教程
🚀 Appwrite GitHub
📜 Appwrite 文档
💬 Discord 社区
如果你喜欢这篇文章,请点击❤️,让更多人看到。关注Brandon Roberts和Appwrite的Twitter账号,获取更多最新消息!
文章来源:https://dev.to/appwrite/building-a-realtime-chat-application-using-angular-and-appwrite-i3o

