Сайт визитка на Angular. Модуль чата.

Aleksandr Serenko
F.A.F.N.U.R
Published in
5 min readMar 27, 2022
Сайт визитка на Angular. Модуль чата

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

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

Возможно стоило бы реализовать интеграцию стороннего решения с собственным API, но это сложно и очень индивидуально. Идейно, никто не мешает добавить интеграцию с telegram, написав бота — но это не в рамках данной статьи.

Интерфейсы

Создадим интерфейсы, которые будут описывать сущности чата и сообщений.

Создадим новую библиотеку:

nx g lib chat/common

Создадим файл chat.interface.ts:

  • Chat — чат представляет собой простую сущность, в которой есть только набор сообщений.
  • ChatMessage — сущность сообщения, которая содержит 4 свойства: номер сообщения, дату создания, текст сообщения и флаг того, что сообщение является сообщением клиента (иначе сообщение написано службой поддержки).
  • ChatMessageCreate — сущность создания сообщения, которая содержит 2 свойства: текст сообщения и признак клиента.

Chat State

Добавим state, который будет хранить сообщения, а также предоставит возможность создавать новые.

Добавим библиотеку:

nx g lib chat/state

Добавим feature state:

nx g @nrwl/angular:ngrx chat --module=libs/chat/state/src/lib/chat-state.module.ts

Изменим chat.actions.ts:

  • init — экшен, который запускает эффект восстановления сообщений из localStorage;
  • restore — экшен, который получает набор сообщений и записывает их в state;
  • createMessage — экшен, который запускает эффект, который добавляет новое сообщение;
  • createMessageSuccess — экшен, который вызывается в случае успешного добавления сообщения;
  • createMessageFailure — экшен, который вызывается в случае ошибки добавления сообщения.

Отредактируем chat.reducer.ts:

В основном в reducer’е управляется добавление нового комментария в state. Помимо сообщений, state содержит 2 свойства:

  • loaded — признак, который говорит о том, что сообщения загружены/восстановлены;
  • messageCreating — переменная, которая говорит о том, что в данный момент идет добавление нового сообщения.

Актуализируем chat.selectors.ts:

  • selectLoaded — селектор, который предоставляет доступ к признаку загрузки сообщений;
  • selectChatMessages — селектор, который возвращает массив сообщений;
  • selectChatMessagesEntities — селектор, который возвращает словарь сообщений.

Добавим эффекты в chat.effects.ts:

  • init$ — эффект, который восстанавливает сообщения из localStorage;
  • restore$ — эффект, который проверяет список сообщений. И если еще не было ни одного сообщения, то создается новое сообщение от имени службы поддержки;
  • createMessage$ — эффект, который добавляет новое сообщение;
  • createMessageSuccess$ — эффект, который подписывается на событие успешного добавления сообщения и сохраняет весь список сообщений в localStorage.

Обновим фасад chat.facade.ts:

Фасад предоставляет доступ к сообщениям и методу создания нового сообщения.

И так как ничего кроме модуля и фасада не нужно, то ограничим экспорт модулей и сервисов:

export * from './lib/chat.facade';
export * from './lib/chat-state.module';

Chat UI

Добавим несколько компонентов для реализации чата:

  • form — форма создания нового сообщения;
  • messages — список сообщений в службу поддержки вместе с ответами;
  • pipes — несколько пайпов для компонентов.

Chat Form

Добавим библиотеку:

nx g lib chat/ui/form

Добавим компонент:

nx g c chat-form --project=chat-ui-form

Форма сообщения содержит textarea и кнопку отправки сообщения:

<form automation-id="form" [formGroup]="form" (ngSubmit)="onSubmit()">
<mat-form-field banshop-full-width>
<mat-label
i18n="Chat form|Write a message">
Write a message
</mat-label>
<textarea
matInput
formControlName="message"
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
></textarea>
</mat-form-field>
<button
aria-label="Send"
i18n-aria-label="Chat form|Send area label"
mat-icon-button
type="button"
(click)="onSubmit()"
>
<mat-icon>send</mat-icon>
</button>
</form>

Сам компонент достаточно прост. При создании компонента идет подписка на добавление нового сообщения:

ngOnInit(): void {
this.chatFacade.createMessageFailure$
.pipe(
tap(() => {
this.submitted = false;
this.changeDetectorRef.markForCheck();
}),
takeUntil(this.destroy$)
)
.subscribe();

this.chatFacade.createMessageSuccess$
.pipe(
tap(() => {
this.submitted = false;
this.form.reset();
this.changeDetectorRef.markForCheck();
}),
takeUntil(this.destroy$)
)
.subscribe();
}

При клике на кнопку добавления сообщения проверяется валидность формы, в данном случае наличии текста. Если текст есть, то комментарий добавляется.

onSubmit(): void {
this.form.markAllAsTouched();

if (!this.submitted && this.form.value?.message?.length) {
this.submitted = true;
this.chatFacade.addMessage(this.form.value.message);
}

this.changeDetectorRef.markForCheck();
}

В случае успешного добавления комментария, форма очищается.

В результате получим:

Если добавить текст:

Chat Pipes

Добавим модуль пайпов.

nx g lib chat/ui/pipes

Добавим 3 pipe:

  • chat-date-can — пайп, который проверяет возможность показ даты;
  • chat-icon — пайп, который возвращает иконку для данного сообщения;
  • chat-icon-can — пайп, который проверяет необходимость показа иконки.

Логика отображения иконки: иконку нужно показать если меняется собственник сообщения.

Логика типа иконки — у каждого участника своя иконка. В данном случае у оператора иконка в виде логотипа, а у клиента просто стоковое изображение.

Логика отображения даты — показывается каждый новый день.

Chat Messages

Добавим компонент, который будет выводить список сообщений.

Создадим библиотеку:

nx g lib chat/ui/messages

Создадим компонент:

nx g c chat-messages --project=chat-ui-messages

Компонент сообщений выводит два модуля, это сообщения и дату.

<ng-container 
*ngFor="let chatMessage of chatMessages; trackBy: trackByFn">
<banshop-chat-date
[chatMessage]="chatMessage"
*ngIf="chatMessage | banshopChatDateCan | async">
</banshop-chat-date>
<banshop-chat-message
[chatMessage]="chatMessage">
</banshop-chat-message>
</ng-container>

Создадим эти два модуля:

nx g m chat-date --project=chat-ui-messages
nx g c chat-date --project=chat-ui-messages

Компонент просто оформляет вывод даты.

nx g m chat-message --project=chat-ui-messages
nx g c chat-message --project=chat-ui-messages

Если вкратце, то компонент сообщения состоит из отображения двух компонентов: иконки и блока с сообщением:

<banshop-chat-icon [chatMessage]="chatMessage"></banshop-chat-icon>
<banshop-chat-box
[chatMessage]="chatMessage"
[lastOwner]="chatMessage | banshopChatIconCan | async">
</banshop-chat-box>

Компонент с иконкой это просто image с условием отображения:

<img 
alt=""
[src]="chatMessage.isOwner | banshopChatIcon"
*ngIf="chatMessage | banshopChatIconCan | async"
/>

Блок с сообщением содержит тело сообщения и дату:

<banshop-chat-body automation-id="body">
{{ chatMessage.message }}
</banshop-chat-body>
<banshop-chat-time automation-id="time">
{{ chatMessage.created | date: 'shortTime' }}
</banshop-chat-time>

В результате получим:

Chat Page

Добавим страницу чата, на которой отобразим список сообщений и форму.

Создадим библиотеку:

nx g lib chat/page

Создадим компонент:

nx g c chat-page --project=chat-page

Добавим модуль роутинга:

nx g m chat-page-routing --project=chat-page

В компоненте берется список сообщений из ChatFacade:

export class ChatPageComponent implements OnInit {
charMessages$!: Observable<ChatMessage[]>;

constructor(private readonly chatFacade: ChatFacade) {}

ngOnInit(): void {
this.charMessages$ = this.chatFacade.chatMessages$;
}
}

В шаблоне компонента подписываемся и отображаем список сообщений:

<banshop-chat-messages
automation-id="messages"
[chatMessages]="charMessages"
*ngIf="charMessages$ | async; let charMessages"
></banshop-chat-messages>
<banshop-chat-form automation-id="form"></banshop-chat-form>

В результате получим страницу:

Ссылки

Оглавление

Предыдущая статья — Модуль оформления заказа.

Следующая статья — Страницы ошибок.

Все исходники находятся на github, в репозитории:

Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — article.

Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, и веб-разработку. Medium | Telegram| VK |Tw| Ln

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock