Postboy Help

Concepts & Overview

Every subscription created through Postboy (sub(...), recordSubject(...), etc.) holds a reference that must be released when the owning component, service, or module is destroyed.
Without a systematic strategy, manual unsubscribe leads to memory leaks and scattered cleanup logic.

Postboy offers three mechanisms to manage the lifecycle of subscriptions, each suited for a different granularity.

1. The problem: manual lifecycle control

When you call postboy.sub(Message), you receive a Subscription. You must keep it and call unsubscribe() at the appropriate time.

// ❌ error‑prone without automatic cleanup const sub = postboy.sub(OrderShippedEvent).subscribe(msg => { /* … */ }); // somewhere else sub.unsubscribe();

In a real application this becomes fragile — missing a single unsubscribe can cause memory leaks or duplicate reactions.

2. Three scoping mechanisms

2.1 Registrators (PostboyAbstractRegistrator)

A registrator groups message registrations and subscriptions into a single class with an explicit up()/down() lifecycle.

class AnalyticsRegistrator extends PostboyAbstractRegistrator { constructor(postboy: PostboyService) { super(postboy); } protected _up(): void { this.recordSubject(OrderPlacedEvent); } } const analytics = new AnalyticsRegistrator(postboy); analytics.up(); // … analytics.down(); // all subscriptions cleaned up

Best for: global or feature‑level scopes where many messages are involved and you want centralised lifecycle control.

2.2 Namespaces

A namespace is a string‑scoped group of message types. Adding a namespace and recording subjects automatically ties subscriptions to that scope. Eliminating the namespace unsubscribes everything under it.

class UserFilterService { private readonly ns = 'user-filter'; constructor(private postboy: PostboyService) { this.postboy.exec(new AddNamespace(this.ns)) .recordSubject(ApplyFilterEvent); this.postboy.sub(ApplyFilterEvent).subscribe(e => { /* … */ }); } destroy(): void { this.postboy.exec(new EliminateNamespace(this.ns)); } }

Best for: scoped logic inside a single component or service; lighter than creating a full registrator.

2.3 IPostboyDependingService

Services that implement IPostboyDependingService define their subscriptions inside an up() method. A parent registrator calls registerServices([…]) to activate them and automatically cleans them up on down().

Services that implement IPostboyDependingService defer their subscription setup to an up() method. A parent registrator invokes this method only after all message types it owns have been registered, preventing errors caused by premature access to the bus.

class AuthService implements IPostboyDependingService { up(): void { this.postboy.sub(UserLoginEvent).subscribe(/* … */); } } class AppRegistrator extends PostboyAbstractRegistrator { constructor(postboy: PostboyService, auth: AuthService) { super(postboy); this.registerServices([auth]); } protected _up(): void { this.recordSubject(UserLoginEvent); } }

Best for: delegating lifecycle responsibility from a feature module to individual services, while keeping overall cleanup in one place.

3. Choosing the right mechanism

Mechanism

Granularity

Cleanup trigger

Boilerplate

Registrator

Module / global

down()

Medium

Namespace

Component / service

EliminateNamespace

Low

Depending service

Service (via registrator)

down() of parent

Low

  • Use a registrator when you need to manage many message types for a whole feature.

  • Use a namespace when only a few messages live inside a single class.

  • Use a depending service when a service’s logic belongs to a broader registrator and you want automatic cleanup without calling EliminateNamespace manually.

4. Key principle

No matter which mechanism you choose, Postboy guarantees that all subscriptions created within a scope are released when that scope ends — no manual unsubscribe() calls required.

07 мая 2026