Angular
Postboy integrates naturally with Angular’s dependency injection, RxJS-based workflows, and component lifecycles. This guide focuses on Angular-specific integration: how to provide the bus through DI, how to bind registrators to Angular scopes, and how to avoid common lifecycle issues when using @artstesh/postboy in Angular applications.
For general Postboy concepts, see:
Installation
Install Postboy with npm:
Postboy is published in major lines that follow the RxJS generation used by your project:
Postboy branch | RxJS version | Typical usage |
|---|---|---|
|
| Older Angular/RxJS projects |
|
| Current Angular projects using RxJS 7 |
|
| Upcoming line for projects using RxJS 8 |
Choose the Postboy major version that matches the RxJS version in your Angular project.
For most current Angular projects using RxJS 7:
When your Angular project moves to RxJS 8:
For more details about versioning, see Versions.
Angular integration model
Angular integration is based on three ideas:
Expose Postboy through Angular DI.
Create an Angular service that extendsPostboyService.Register messages and handlers through registrators.
UsePostboyAbstractRegistratorto bind Postboy registrations to Angular scopes.Align registrator lifetime with Angular lifetime.
Root-level communication belongs to root services. Feature-level communication belongs to feature components, route components, or lazy-loaded areas.
Postboy itself is framework-agnostic. Angular-specific integration mainly consists of choosing the correct injector scope and lifecycle hook.
Providing Postboy through Angular DI
Angular applications should use a project-specific injectable wrapper around PostboyService.
This service becomes the application-wide message bus.
Using a wrapper has several advantages:
it gives Angular a stable DI token;
it prevents manual bus creation in components;
it keeps integration code in one place;
it allows future application-specific extensions such as logging, diagnostics, or helper methods.
In most applications, AppPostboyService should be provided in root.
Root registrator
A root registrator is useful for application-wide communication: authentication state, locale changes, global notifications, application settings, diagnostics, and other cross-cutting concerns.
Keep the root registrator small. It should not become a single place for every feature message in the application.
For details about registration methods, see PostboyAbstractRegistrator.
Activating the root registrator
Inject the root registrator into the root component and activate it when the application starts.
Calling down() in ngOnDestroy() keeps cleanup predictable for tests, microfrontend shells, and non-standard bootstrapping scenarios.
For the root component, ngOnDestroy() is rarely called during normal SPA usage, but it is still a good place to document teardown behavior.
Registration order matters
Postboy channels and executor handlers must be registered before they are used.
If a component or service calls fire, sub, fireCallback, or exec before the corresponding registration exists, Postboy can throw an error such as:
In Angular this usually means one of the following:
the registrator was not injected anywhere, so Angular never created it;
registrator.up()was not called;a service subscribed in its constructor before the feature registrator was activated;
a feature message was registered in a component-level registrator, but used outside that component scope;
a lazy-loaded feature expected root-level registration, but the message was only registered locally.
The safe rule is simple:
create the registrator in the correct Angular injector scope;
call
up();only then publish, subscribe, call callbacks, or execute handlers.
For more about this lifecycle model, see Registration vs activation and Lifecycle concepts.
Feature registrators
Feature-specific communication should usually be registered by a feature registrator, not by the root registrator.
This is useful for:
standalone components;
lazy-loaded route components;
dialogs;
feature pages;
dashboards;
embedded widgets;
microfrontend entry points.
A feature registrator is provided by the component that owns its lifecycle:
Then provide and activate it in the component:
When the component is destroyed, down() cleans the feature scope. This keeps feature communication isolated and prevents long-lived subscriptions from surviving after the UI area is gone.
Standalone components
Angular standalone components work especially well with local registrators because the providers array is directly attached to the component.
This pattern gives each standalone feature its own communication scope.
Use it when the messages are meaningful only while that component or route is active.
NgModule-based applications
In NgModule-based applications, the same idea applies. You can provide a registrator at the module level or at the component level.
Prefer component-level providers when the communication should be destroyed together with the component:
Use module-level providers only when the registrator lifetime should match the module lifetime.
Lazy-loaded routes
Lazy-loaded routes are a good place for feature-level registrators.
If a message belongs only to a lazy feature, provide its registrator inside that feature’s route component. This keeps the message channels active only while the route is active.
If a lazy feature must communicate with the rest of the application, decide explicitly whether the message belongs to:
the root registrator, if it is application-wide;
the feature registrator, if it is meaningful only inside that feature.
Avoid registering a feature message globally just because it is convenient.
Working with Angular services
Be careful when subscribing to Postboy messages in Angular service constructors.
Angular may create a service before the registrator that defines the message channel has been activated. If the service subscribes too early, the channel may not exist yet.
Potentially problematic pattern:
Prefer lifecycle-controlled activation:
activate the feature registrator first;
create subscriptions after the message channels are registered;
use registrator-managed dependent services if the service must subscribe as part of the feature lifecycle.
If your service participates in the Postboy lifecycle, see IPostboyDependingService and Registrator-dependent services.
Subscriptions in Angular components
Postboy subscriptions are RxJS subscriptions. Manage them the same way you manage any Angular subscription.
Depending on your Angular version and style, you can use:
explicit
Subscriptioncleanup;takeUntilDestroyed;DestroyRef;asyncpipe where applicable;registrator-managed lifecycle for feature-level wiring.
Example with takeUntilDestroyed:
This is standard Angular subscription management. Postboy does not require a special Angular-specific unsubscribe mechanism.
For general message subscription patterns, see Generic messages and Callback messages.
Angular signals and change detection
Postboy can be used with Angular signals as a convenient way to update component state from message streams.
In standard Angular usage, subscriptions created inside Angular components and services usually update the UI as expected.
If you use custom schedulers, external integrations, zone-less Angular, or manually controlled change detection, make sure updates are visible to Angular. In such cases, prefer signals, ChangeDetectorRef, or your project’s established zone-less update strategy.
Root vs feature scope
A practical Angular rule:
provide
AppPostboyServiceinroot;provide truly global registrators in
root;provide feature registrators at the component or route level;
call
up()when the scope starts;call
down()when the scope ends.
Use root scope for:
authentication;
global notifications;
application configuration;
locale and theme;
global error events;
application-wide diagnostics.
Use feature scope for:
page-specific communication;
dialog-specific communication;
lazy route interactions;
editor state;
feature-local commands and queries;
temporary workflows.
This keeps Angular injector scope and Postboy lifecycle aligned.
Suggested Angular project structure
One possible structure is:
The exact structure is up to your application, but the important rule is ownership:
global messages should be easy to find globally;
feature messages should live near the feature that owns them;
registrators should be located close to the scope they activate.
For naming conventions and message roles, see Message roles.
Common Angular mistakes
Activating a feature registrator too late
If a component subscribes before the feature registrator is activated, the message channel may not exist yet.
Prefer this order:
Registering all messages in the root registrator
This makes cleanup harder and turns the root registrator into a global dependency hub.
Use feature registrators for feature-owned messages.
Subscribing in service constructors
This can work for root-level channels, but it is risky for feature-level channels.
Prefer lifecycle-aware activation or registrator-dependent services.
Providing a feature registrator in the wrong injector
If a registrator is provided in root, it will not be destroyed with the component.
If a registrator is provided at the component level, it will not be available outside that component subtree.
Choose the provider scope intentionally.
Using different imports for the same message class
Make sure the class registered by the registrator is the same class used by publishers and subscribers. Avoid duplicate barrels or inconsistent import paths that can lead to different runtime references.
Troubleshooting
"There is no registered event"
Check that:
the message is registered in a registrator;
the registrator instance was created by Angular DI;
up()was called before using the message;the message belongs to the scope where it is being used;
the same message class reference is used in registration and usage.
For more details, see Registration vs activation.
Feature messages still react after navigation
Check that:
the feature registrator is provided at the component or route level;
down()is called inngOnDestroy();the message was not registered globally by mistake;
long-lived services are not keeping independent subscriptions.
A service cannot subscribe to a feature message
The service may be created before the feature registrator is activated, or it may live in a different injector scope.
Consider:
moving subscription setup into the feature lifecycle;
using a feature-level provider;
using
IPostboyDependingService;moving the message to the root registrator only if it is truly global.
Migration tips for Angular projects
From EventEmitter
Use Postboy when communication is not a direct parent-child output relationship.
Angular EventEmitter is still fine for component outputs. Postboy is more suitable for cross-component, service-to-service, or feature-level communication.
From shared RxJS subjects
A shared Subject in a service is simple, but ownership and cleanup can become unclear.
Postboy registrators make registration and lifecycle explicit. This is especially useful for feature scopes and lazy-loaded areas.
From direct service calls
Keep direct calls when the dependency is local and clear.
Use Postboy when:
the caller should not know the receiver;
multiple receivers may react;
the interaction crosses feature boundaries;
you want to model the interaction as an explicit message contract.
Moving to RxJS 8
When your Angular stack moves to RxJS 8, use the Postboy 4.x line.
Check:
Angular compatibility with RxJS 8;
RxJS operator changes in your application code;
dependencies that still require RxJS 7;
Postboy version alignment.
Summary
In Angular, Postboy works best when its lifecycle is aligned with Angular DI scopes:
Provide one
AppPostboyServiceinroot.Use a root registrator only for global communication.
Use feature registrators for standalone components, lazy routes, dialogs, and feature pages.
Activate registrators before using messages.
Clean local scopes with
down().Manage component subscriptions like normal RxJS subscriptions.
Use the Postboy major version that matches your RxJS generation.
For message types, registration APIs, namespaces, middleware, and general architecture patterns, use the dedicated documentation sections linked at the top of this guide.