Создание приложения для чата в реальном времени с помощью Angular и Appwrite

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Appwrite — это бэкенд как сервис с открытым исходным кодом, который предоставляет разработчикам основной набор функций, необходимых для создания всех приложений с любым стеком. От взаимодействия с базой данных до аутентификации, обновлений в реальном времени и многого другого.

При создании веб-приложений с помощью Angular обычно требуется подключение к различным 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 Console и зарегистрировать свой рут-аккаунт.

Итак, давайте создадим первый проект.

Создание проекта

В Appwrite можно размещать множество различных приложений, используя проекты. Чтобы создать проект:

  • Нажмите на кнопку Create Project (создать проект)

  • Нажмите на значок карандаша и введите ngchat в качестве пользовательского идентификатора проекта

  • Введите Angular Chat в качестве названия

  • Нажмите Create (создать)

Далее давайте настроим базу данных и коллекцию для приложения чата.

Создание базы данных и коллекции

База данных в Appwrite — это группа коллекций для управления данными. Чтобы создать базу данных, перейдите в раздел Database (База данных):

  • Нажмите на кнопку Create Database (создать базу данных)

  • Введите chat (чат) в качестве пользовательского идентификатора базы данных

  • Введите Chat в качестве имени

  • Нажмите Create (создать)

Для коллекции:

  • Нажмите на Create Collection (создать коллекцию)

  • Введите messages (сообщения) в качестве пользовательского идентификатора коллекции

  • Введите Chat Messages (сообщения чата) в качестве имени

  • Нажмите Create (создать)

Мы также хотим настроить разрешения для коллекции на чтение/запись. Для сообщений определите разрешения Document Level (уровень документа). Вы можете подобрать более детализированные разрешения в зависимости от вашего сценария использования. На странице разрешений есть более подробная информация о таких правах; в-действительности, пользователь может самостоятельно распоряжаться своим сообщением.

Создание атрибутов коллекции

Каждая коллекция в базе данных Appwrite состоит из атрибутов, которые моделируют структуру документа, который вы желаете сохранить. Для приложения чата вы будете хранить имя пользователя и его сообщение.

Создание атрибутов документа

Атрибуты могут быть определены как строки, числа, электронные письма и многое другое. Чтобы создать атрибут:

  • Нажмите на кнопку Create Attribute (Создать атрибут).

  • Выберите type of Attribute (тип атрибута) для создания.

Используйте приведенную ниже строку для создания необходимых атрибутов для чата.

keyizerequiredarrayuser32truefalsemessage10000truefalse

Когда документ создается в коллекции, он также имеет дополнительные метаданные о своем времени создания и обновления, названные $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

Чтобы осуществить конфигурацию Appwrite в нашем проекте Angular, сначала настройте некоторые переменные окружения для эндпойнта 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, установленный ранее, а также настройте несколько Injection Tokens (токены внедрения) в Angular, чтобы иметь возможность вводить SDK в сервисы, созданные позже.

Давайте создадим 2 токена, один для переменных окружения Appwrite, а другой для самого инстанса SDK.

Создайте новый файл src/appwrite.ts и настройте эти два токена в качестве рут-провайдеров.

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 для:

  • Аутентификация пользователя с помощью метода login, обновление имени и сохранение текущего пользователя в объекте observable.

  • Проверяет, вошел ли пользователь в систему и возвращает булев (логическое “истина/ложь” значение).

  • Выводит пользователя из системы, очищая текущую сессию.

С помощью Appwrite SDK все это делается без использования HttpClient сервиса Angular. Вы всегда можете обратиться к REST API Appwrite напрямую, хотя в этом нет необходимости, поскольку 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}`]
        );
      })
    );
  }
}

ChatService:

  • Внедряет переменные среды Appwrite

  • Настраивает объект observable сообщений чата

  • Использует Appwrite SDK для загрузки сообщений чата из коллекции messages  (сообщений)

  • Заводит текущего зарегистрированного пользователя для добавления сообщений чата в коллекцию messages.

  • Назначает права доступа к документу, таким образом, что читать может любой, но обновлять/удалять будет только определенный пользователь.

Когда сервисы настроены, мы можем перейти к компонентам для авторизации и чата.

Создание страницы авторизации

Для компонента login (логин) используйте 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();
 }
}

После успешной аутентификации мы перенаправляемся на страницу чата.

Отображение сообщений чата

В компоненте Chat начните с отображения сообщений чата с помощью 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 компонента

  • Использование поля ввода для отправки сообщения с помощью ChatService

  • Выход со страницы чата

У нас есть возможность загружать сообщения чата, но давайте добавим интересную деталь и интегрируем некоторые сообщения чата в реальном времени.

Подключение к событиям реального времени

Appwrite обеспечивает обновление в реальном времени практически всех событий, происходящих в системе Appwrite, таких как вставка, обновление или удаление записей в базе данных. Эти события предоставляются через WebSocket. Чтобы подписаться на реалтайм, обновите ChatService с помощью метода listenToMessages, для подписки на события из коллекции messages (сообщения).

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);
       }
     }
   );
 }
}

Когда создается новое сообщение, оно попадает в объект  observable (наблюдаемый) пользователей; таким образом, у нас обеспечено обновление в реальном времени. Чтобы начать прослушивать события в реальном времени:

  • Обновите ngOnInit компонента ChatComponent, чтобы вызвать метод.

  • Сохраните live-подключение для отписки

  • Уничтожьте live-подключение, когда компонент будет ликвидирован

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 Repo: https://github.com/brandonroberts/appwrite-angular-chat


Приложения для мобильных устройств сейчас невероятно популярны. Если у компании есть действующее веб-приложение, дополнительная разработка двух нативных мобильных приложений для iOS и Android достаточно pecypcoзaтpaтна. Фреймворк Angular в связке с фреймворком Ionic позволяет одной кодовой базой создать сразу веб-приложение и/или два мобильных приложения для двух самых популярных платформ. Это сильно экономит время на разработку, а также позволяет сделать все три приложения одной командой разработчиков.

Тем, кто знаком с Angular, фреймворк Ionic не покажется сложным, так как он по сути является дополнительным функционалом и полностью повторяет привычный подход к написанию кода. Приглашаем на открытое занятие, на котором мы познакомимся с преимуществами такой разработки, с использованием нативных функций устройств и рассмотрим всё это на примере. Регистрация открыта для всех желающих по ссылке.

Источник: https://habr.com/ru/company/otus/blog/700016/


Интересные статьи

Интересные статьи

Однажды ты просыпаешься и понимаешь: избыточность компонентов и рассинхронизация в твоём приложении начинают вредить пользователям. Однажды ты смотришь на написанное давным-давно ядро, плачешь гор...
В этом посте будет описано практическое применение semantic-release для terraform модуля terraform-yandex-compute (Модуль Terraform, который создает вычислительные ресурсы в облаке Яндекса) c Github a...
В предыдущих статьях (раз, два) я описывал устройство качания детской кроватки с маятниковым механизмом. Прошло всего каких-то пять лет – и теперь вашему вниманию хочу пр...
Все началось, когда мое внимание привлек старый светодиодный дисплей, лежащий в ящике среди мелочевки и запасных деталей. Он сохранился еще со времен древних 386/486 ПК и мог отобража...
Продолжаю публикацию решений отправленных на дорешивание машин с площадки HackTheBox. В данной статье реверсим два Java приложения, при этом изменяем и рекомпилируем клиент, для...