Асинхронное управление событиями в Angular-приложениях — одна из основных задач при разработке, особенно если приложение активно взаимодействует с динамическими данными или имеет сложную архитектуру. Зачастую для решения этой задачи разработчики используют подписки и/или промежуточные сервисы. Несмотря на популярность этих подходов, они могут приводить к избыточным зависимостям, высокому уровню связности и сложному коду.
Библиотека @artstesh/postboy предоставляет альтернативный взгляд на эту проблему: она позволяет избавиться промежуточных сервисов и использовать асинхронные события простым и лаконичным способом.
Как отправлять и обрабатывать асинхронные события
*В данном опусе пропущены все объяснения того, что такое @artstesh/postboy и как его устанавливать, за этой информацией можете заглянуть сюда или на сайт проекта.
Отправка событий
Для публикации событий в @artstesh/postboy используется удобный интерфейс, позволяющий публиковать события с параметрами или без них. Пример отправки события:
// Отправка события без параметров
postboy.fire(new SimpleEvent());
// Отправка события с параметрами
postboy.fire(new EventWithParams('Hello, world!'));В этом примере библиотека полностью берет на себя задачу передачи сообщений. Благодаря этому вам не нужно вручную связывать компоненты через сервисы или использовать Input/Output. Также стоит отметить, что и complete подписок библиотека так же возьмет на себя (в конце жизненного цикла модуля в котором обитает компонент).
Обработка событий
Для обработки событий достаточно подписаться через саму библиотеку. Это не стандартная подписка RXJS/Observable, а способ привязки, при котором (с некоторыми оговорками) стирается необходимость в явных отписках.
Пример обработки событий:
// Подписка на событие без параметров
postboy.sub(SimpleEvent).subscribe(() => {
console.log('Событие simpleEvent выполнено!');
});
// Подписка на событие с параметрами
postboy.sub(EventWithParams).subscribe(message => {
console.log('Получено сообщение:', message);
});Теперь при вызове postboy.fire(new SimpleEvent()) метод обработки автоматически выполнится. Если событие передавало данные, они будут доступны в обработчике.
Чем это лучше традиционных подходов
Преимущества подхода @artstesh/postboy:
Сокращение кода: Больше никакого создания промежуточных сервисов. Одного экземпляра Postboy достаточно для сообщения между всеми частями приложения (либо отдельными его модулями, если такое разделение будет ��еобходимо, тогда мы разделяем зоны событий, выделяя на каждую зону отдельный регистратор).
Повышение читаемости кода: Нет громоздких цепочек подписок и управлений состояниями. Роль каждого компонента становится явно определённой.
Ослабление проблемы утечек памяти: При использовании библиотеки не нужно явно отписываться от событий — как это необходимо, например, в случае с RXJS, система вызовет complete() на всех подписках завершаемого модуля.
Гибкость управления: Возможность легко изменять поведение приложения при работе с событиями, добавлять новые сценарии обработки событий, удалять или перенаправлять события без необходимости вносить изменения в саму бизнес-логику компонентов или жестко связывать их друг с другом. Это упрощает развитие приложения и его поддержку в долгосрочной перспективе.
Разница подходов
Давайте рассмотрим пример синтетического сценария: у нас есть кнопка, которая инициирует запрос и показывает результат пользователю в компоненте.
Без @artstesh/postboy
В стандартной архитектуре Angular это могло бы выглядеть примерно так:
//middle.service.ts
import {Injectable} from "@angular/core";
import {ReplaySubject} from "rxjs";
@Injectable({providedIn: 'root'})
public class MiddleService {
message$ = new ReplaySubject<string>()
}// button.component.ts
import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-button',
template: '<button (click)="loadData()">Загрузить данные</button>',
})
export class ButtonComponent {
constructor(private dataService: DataService, private middle: MiddleService) {}
loadData() {
this.dataService.loadData().subscribe((data) => {
this.middle.message$.next(data.result);
});
}
}// display.component.ts
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-display',
template: '{{ data }}',
})
export class DisplayComponent implements OnInit {
data: string = '';
constructor(private middle: MiddleService) {
}
ngOnInit() {
this.middle.message$.subscribe(d => this.data = d);
}
}С @artstesh/postboy
Теперь перепишем этот пример с использованием нашей библиотеки:
// button.component.ts
import { Component } from '@angular/core';
import { AppPostboyService } from './app-postboy.service';
@Component({
selector: 'app-button',
template: '<button (click)="loadData()">Загрузить данные</button>',
})
export class ButtonComponent {
constructor(private postboy: AppPostboyService) {}
loadData() {
this.postboy.fireCallback(new LoadDataCommand(),response => {
// Логика после загрузки данных
const data = { result: 'Данные успешно загружены' };
this.postboy.fire(new DataLoadedEvent(data));
});
}
}// display.component.ts
import {Component, OnInit} from '@angular/core';
import {Postboy} from '@artstesh/postboy';
@Component({
selector: 'app-display',
template: '{{ data }}',
})
export class DisplayComponent implements OnInit {
data: string = '';
constructor(private postboy: AppPostboyService) {}
ngOnInit() {
this.postboy.sub(DataLoadedEvent).subscribe(data => {
this.data = data.result;
});
}pp
Для пары компонентов разница, возможно, не выглядит действительно впечатляющей, но с увеличением масштаба разница становится все более заметной - оригинальный код обрастает все большим количеством подписок и новых сервисов, а управляемый postboy все так же зависит только от AppPostboyService
Заключение
Асинхронное управление событиями с библиотекой @artstesh/postboy позволяет сделать код в Angular-приложениях более понятным и простым в сопровождении. Благодаря тому, что вам больше не нужно создавать промежуточные сервисы, объединять компоненты через Input/Output, работа с событиями становится гораздо проще.
Не забудьте ознакомиться с другим функционалом библиотеки, а еще я буду рад услышать предложения по улучшению и дополнению функционала.
