on-ngx-event-bus 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -0
- package/fesm2022/on-ngx-event-bus.mjs +189 -0
- package/fesm2022/on-ngx-event-bus.mjs.map +1 -0
- package/ngx-event-bus-lib-cover.jpg +0 -0
- package/package.json +45 -0
- package/types/on-ngx-event-bus.d.ts +118 -0
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<img
|
|
4
|
+
alt="Ngx Event Bus"
|
|
5
|
+
src="./ngx-event-bus-lib-cover.jpg"
|
|
6
|
+
width="100%"
|
|
7
|
+
/>
|
|
8
|
+
</picture>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
# ngx-event-bus
|
|
12
|
+
|
|
13
|
+
**A lightweight, fully-tested, type-safe global event bus for Angular — powered by decorators, pure functions, and zero shared state.**
|
|
14
|
+
|
|
15
|
+
Broadcast strongly-typed events anywhere in your app and react to them declaratively —
|
|
16
|
+
without services, DI, providers, RxJS, Signals, or tight coupling.
|
|
17
|
+
|
|
18
|
+
## Motivation
|
|
19
|
+
|
|
20
|
+
In many Angular applications, **components that are completely unrelated still need to communicate**.
|
|
21
|
+
|
|
22
|
+
When the app is not built around a state-management solution, a common approach is to introduce a shared service —
|
|
23
|
+
usually based on RxJS `Subject`'s or Signals — and use it as a communication bridge.
|
|
24
|
+
|
|
25
|
+
This typically requires:
|
|
26
|
+
- Services, providers, and dependency injections
|
|
27
|
+
- RxJS tools or Signals
|
|
28
|
+
- Manual lifecycle handling to avoid memory leaks (in the case of RxJS)
|
|
29
|
+
|
|
30
|
+
`ngx-event-bus` takes a different approach.
|
|
31
|
+
|
|
32
|
+
It is built on **native JavaScript events**, automatically adds and removes event listeners to prevent **memory leaks**, and requires **no services, no DI, and no module setup or imports**. Event handling is simple, declarative, and free from shared state.
|
|
33
|
+
|
|
34
|
+
## Compatibility
|
|
35
|
+
|
|
36
|
+
✅ **Angular support:** Angular **v9 and above**
|
|
37
|
+
|
|
38
|
+
Supports **all Angular entities**:
|
|
39
|
+
|
|
40
|
+
- Components
|
|
41
|
+
- Directives
|
|
42
|
+
- Services
|
|
43
|
+
- Pipes
|
|
44
|
+
|
|
45
|
+
## Quick Start 🚀
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
```bash
|
|
49
|
+
# npm
|
|
50
|
+
npm install on-ngx-event-bus
|
|
51
|
+
|
|
52
|
+
# yarn
|
|
53
|
+
yarn add on-ngx-event-bus
|
|
54
|
+
|
|
55
|
+
# pnpm
|
|
56
|
+
pnpm add on-ngx-event-bus
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Broadcasting an event 🛜
|
|
62
|
+
```ts
|
|
63
|
+
import { broadcast, GEvent } from "on-ngx-event-bus";
|
|
64
|
+
|
|
65
|
+
publish(): void {
|
|
66
|
+
broadcast(
|
|
67
|
+
new GEvent("MY_EVENT", {
|
|
68
|
+
metadata: "My event data..."
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- The event's payload can be **any type of data** — primitives, objects, functions, and more. (If no payload is provided, the default is null)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Intercepting an event 📡
|
|
79
|
+
```ts
|
|
80
|
+
import { Component } from "@angular/core";
|
|
81
|
+
import { Interceptor, intercept } from "on-ngx-event-bus";
|
|
82
|
+
|
|
83
|
+
@Interceptor([
|
|
84
|
+
{ type: "MY_EVENT", action: "handleEvent" }
|
|
85
|
+
])
|
|
86
|
+
@Component({
|
|
87
|
+
selector: "app-home",
|
|
88
|
+
template: `Home`
|
|
89
|
+
})
|
|
90
|
+
export class HomeComponent {
|
|
91
|
+
constructor() {
|
|
92
|
+
intercept(this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
handleEvent(payload: { metadata: string }): void {
|
|
96
|
+
console.log("Event intercepted: ", payload);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
- ⚠️ Mandatory: Always call intercept(this) in the constructor to activate the `@Interceptor`.
|
|
101
|
+
|
|
102
|
+
- The `@Interceptor` decorator can intercept and handle **any number of events**, without limits.
|
|
103
|
+
|
|
104
|
+
## 🎯 Targeted Events
|
|
105
|
+
|
|
106
|
+
By default, events are **broadcast globally** — each interceptor listening to the same event type will receive them.
|
|
107
|
+
|
|
108
|
+
However, in some scenarios you may want **only specific listeners** to react to an event, even if multiple interceptors are registered for the same type. To support this, events can be optionally sent with a **`key`** (`string`).
|
|
109
|
+
|
|
110
|
+
### Broadcasting a targeted event 🛜
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
publish(): void {
|
|
114
|
+
broadcast(
|
|
115
|
+
new GEvent("MY_EVENT", {
|
|
116
|
+
metadata: "My event data..."
|
|
117
|
+
}, "BUS::MY_EVENT::A9F3-77XQ") // 🔑
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Intercepting a targeted event 📡
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
@Interceptor([
|
|
126
|
+
{ type: "MY_EVENT", action: "handleTargetedEvent", key: "BUS::MY_EVENT::A9F3-77XQ" }
|
|
127
|
+
])
|
|
128
|
+
@Component({
|
|
129
|
+
selector: "app-home",
|
|
130
|
+
template: `Home`
|
|
131
|
+
})
|
|
132
|
+
export class HomeComponent {
|
|
133
|
+
constructor() {
|
|
134
|
+
intercept(this);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
handleTargetedEvent(): void {
|
|
138
|
+
console.log("Will be triggered only if the key matches...");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
- Events broadcast with a mismatched key will be **rejected** by the `@Interceptor` ❌
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## Advanced Usage ⚡
|
|
146
|
+
|
|
147
|
+
`ngx-event-bus` supports **fully-typed events** in 3 different levels, from quick-and-loose to fully enforced best practices.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### 1️⃣ Loose / Quick Usage
|
|
152
|
+
```ts
|
|
153
|
+
broadcast(new GEvent("MY_EVENT", {
|
|
154
|
+
metadata: "Quick, untyped payload"
|
|
155
|
+
}));
|
|
156
|
+
```
|
|
157
|
+
- ✅ Fast — minimal setup, just fire-and-forget.
|
|
158
|
+
- ✅ Flexible — any shape of payload is allowed.
|
|
159
|
+
- ❌ No type safety (developer choice)
|
|
160
|
+
|
|
161
|
+
### 2️⃣ Generic enforce - Strongly Typed
|
|
162
|
+
```ts
|
|
163
|
+
broadcast(
|
|
164
|
+
new GEvent<"MY_EVENT", { metadata: string }>("MY_EVENT", {
|
|
165
|
+
metadata: "Payload and event name are generic enforced.
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
```
|
|
169
|
+
Or even smarter, with Enums/types and interfaces
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
enum MyEventTypes {
|
|
173
|
+
MyEvent = "MY_EVENT"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface MyEventPayload {
|
|
177
|
+
metadata: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
broadcast(
|
|
181
|
+
new GEvent<MyEventTypes.MyEvent, MyEventPayload>(
|
|
182
|
+
MyEventTypes.MyEvent, {
|
|
183
|
+
metadata: "Payload and event name are generic enforced.
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
```
|
|
187
|
+
- ✅ Payload enforced — TypeScript ensures payload shape is correct.
|
|
188
|
+
- ✅ Event names centralized — reduces typos and keeps event names consistent.
|
|
189
|
+
- ✅ Better developer experience — IDE autocompletion works.
|
|
190
|
+
- ❌ Event–payload relationship not fully enforced — nothing prevents using the wrong payload with a given event type.
|
|
191
|
+
|
|
192
|
+
### 3️⃣ Fully Enforced, Best Practice 🥇
|
|
193
|
+
By extending the `GEvent` class, you can create your own fully enforced events. This ensures **both the event type and its payload are strictly typed**, making your code refactor-safe and perfect for large apps.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { GEvent, broadcast } from 'on-ngx-event-bus';
|
|
197
|
+
|
|
198
|
+
export class MyEvent extends GEvent<MyEventTypes.MyEvent, MyEventPayload> {
|
|
199
|
+
static readonly TYPE = MyEventTypes.MyEvent;
|
|
200
|
+
|
|
201
|
+
constructor(payload: MyEventPayload) {
|
|
202
|
+
super(MyEvent.TYPE, payload);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
broadcast(
|
|
207
|
+
new MyEvent({
|
|
208
|
+
metadata: "Fully typed and refactor-safe!"
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
```
|
|
212
|
+
- ✅ Fully typed — TypeScript strictly enforces both event type and payload, guaranteeing their correct relationship.
|
|
213
|
+
- ✅ Refactor-safe — renaming the event or payload interface will automatically propagate errors if used incorrectly.
|
|
214
|
+
- ✅ Best developer experience — IDE autocompletion, type-checking, and maintainability are maximized.
|
|
215
|
+
- ✅ Large-app ready — ideal for apps with many events and complex interactions.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Final Thoughts ✨
|
|
220
|
+
|
|
221
|
+
`ngx-event-bus` is designed to scale with your application — from small components to large, event-rich Angular systems.
|
|
222
|
+
|
|
223
|
+
It removes boilerplate, enforces correctness at compile time, and lets you focus on **intent**, not wiring.
|
|
224
|
+
If your app relies on clean, decoupled communication, this library is built for you.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ɵNG_PIPE_DEF as _NG_PIPE_DEF, ɵɵdirectiveInject as __directiveInject, RendererFactory2 } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
const NG_PIPE_DEF = _NG_PIPE_DEF;
|
|
4
|
+
|
|
5
|
+
const GLOBAL_HOST = "window";
|
|
6
|
+
|
|
7
|
+
const DECORATOR_APPLIED = Symbol('__interceptorDecoratorApplied');
|
|
8
|
+
|
|
9
|
+
const TRUSTED_EVENT = Symbol('__trustedEvent');
|
|
10
|
+
|
|
11
|
+
const PAYLOAD_PROTOTYPE = Object.freeze({
|
|
12
|
+
[TRUSTED_EVENT]: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sends a global, system-wide event via the Event Bus.
|
|
17
|
+
*
|
|
18
|
+
* The event will be received by all active `@Interceptor`s that are
|
|
19
|
+
* listening for the same event type.
|
|
20
|
+
*
|
|
21
|
+
* Events may optionally include a `key`. When a key is provided,
|
|
22
|
+
* only interceptors registered with the same key will accept the
|
|
23
|
+
* event; all others will be rejected.
|
|
24
|
+
*
|
|
25
|
+
* The payload is strongly typed and may be any value:
|
|
26
|
+
* primitives, objects, arrays, etc.
|
|
27
|
+
*
|
|
28
|
+
* Internally, the payload is wrapped in a trusted object to ensure
|
|
29
|
+
* listeners only handle legitimate, system-generated events.
|
|
30
|
+
* Interceptors always access the original payload via `event.detail.data`.
|
|
31
|
+
*
|
|
32
|
+
* @template T - Type of the event name (string literal recommended for type safety).
|
|
33
|
+
* @template P - Type of the event payload.
|
|
34
|
+
*
|
|
35
|
+
* @param event - The typed event instance to broadcast.
|
|
36
|
+
*/
|
|
37
|
+
function broadcast(event) {
|
|
38
|
+
const trustedPayload = Object.create(PAYLOAD_PROTOTYPE);
|
|
39
|
+
Object.assign(trustedPayload, {
|
|
40
|
+
data: event.payload || null,
|
|
41
|
+
key: event.key
|
|
42
|
+
});
|
|
43
|
+
window.dispatchEvent(new CustomEvent(event.type, {
|
|
44
|
+
detail: trustedPayload,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Registers an instance to receive events through its @Interceptor-decorated methods.
|
|
50
|
+
*
|
|
51
|
+
* This function must be called inside the class constructor with `this` as the argument:
|
|
52
|
+
* ```ts
|
|
53
|
+
* constructor() {
|
|
54
|
+
* intercept(this);
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* Only classes decorated with `@Interceptor` can be intercepted. Calling this on an
|
|
59
|
+
* undecorated class will throw an error.
|
|
60
|
+
*
|
|
61
|
+
* After calling this, methods specified in the `@Interceptor` decorator will receive
|
|
62
|
+
* events of the corresponding type via the Event Bus.
|
|
63
|
+
*
|
|
64
|
+
* @template T - Type of the instance being intercepted.
|
|
65
|
+
*
|
|
66
|
+
* @param instance - The class instance to register for interception.
|
|
67
|
+
*/
|
|
68
|
+
function intercept(instance) {
|
|
69
|
+
return (function () {
|
|
70
|
+
const unDecorated = !(DECORATOR_APPLIED in (Object.getPrototypeOf(instance)));
|
|
71
|
+
if (unDecorated) {
|
|
72
|
+
throw new Error('intercept() function cannot be used inside any Angular classes ' +
|
|
73
|
+
'that are not decorated with @Interceptor decorator');
|
|
74
|
+
}
|
|
75
|
+
instance.constructor.prototype._intercept(instance);
|
|
76
|
+
})();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Represents a strongly-typed event in the Event Bus system.
|
|
81
|
+
*
|
|
82
|
+
* Each `GEvent` has a `type` (string literal recommended for type safety) and a `payload`.
|
|
83
|
+
* This object is used with the Event Bus functions, ensuring
|
|
84
|
+
* type-safe communication across your Angular application.
|
|
85
|
+
*
|
|
86
|
+
* @template T - Type of the event name. Defaults to `string`, but string literals are recommended.
|
|
87
|
+
* @template P - Type of the event payload. Defaults to `null` if no payload is needed.
|
|
88
|
+
*/
|
|
89
|
+
class GEvent {
|
|
90
|
+
/** The type of the event */
|
|
91
|
+
type;
|
|
92
|
+
/** The strongly-typed payload of the event */
|
|
93
|
+
payload;
|
|
94
|
+
/** Optional key used to scope or target the event */
|
|
95
|
+
key;
|
|
96
|
+
/**
|
|
97
|
+
* Creates a new typed event instance.
|
|
98
|
+
*
|
|
99
|
+
* @param type - The event type identifier.
|
|
100
|
+
* @param payload - Optional event payload.
|
|
101
|
+
* @param key - Optional key used to target specific interceptors.
|
|
102
|
+
*/
|
|
103
|
+
constructor(type, payload, key) {
|
|
104
|
+
this.type = type;
|
|
105
|
+
this.payload = payload;
|
|
106
|
+
this.key = key;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function initListeners(events, instance, renderer2) {
|
|
111
|
+
return events.map((event) => {
|
|
112
|
+
return renderer2.listen(GLOBAL_HOST, event.type, data => {
|
|
113
|
+
// Guard 1:
|
|
114
|
+
// Event Source - events originated from unknown or external sources will be rejected ❌
|
|
115
|
+
if (Object.getPrototypeOf(data.detail) !== PAYLOAD_PROTOTYPE) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Guard 2:
|
|
119
|
+
// Key-based event filtering - events broadcast with a mismatched key will be rejected ❌
|
|
120
|
+
if (event.key !== data.detail.key) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Guard 3:
|
|
124
|
+
// Interceptor integrity check — ensure the configured action exists and callable, misconfiguration is treated as a hard error ❌
|
|
125
|
+
if (typeof instance[event.action] !== 'function') {
|
|
126
|
+
throw new Error(`Interceptor error: "${event.action}" is not a function on ${instance.constructor.name}`);
|
|
127
|
+
}
|
|
128
|
+
// Event accepted ✅
|
|
129
|
+
// At this point, the event is verified, trusted, and correctly targeted.
|
|
130
|
+
instance[event.action](data.detail.data);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isPipe(target) {
|
|
136
|
+
return !!target[NG_PIPE_DEF];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Class decorator that registers methods to handle cross-system events.
|
|
141
|
+
*
|
|
142
|
+
* Use this decorator on an Angular class to specify which events it should listen to.
|
|
143
|
+
* Each object in the `events` array defines an event type and the corresponding class
|
|
144
|
+
* method (action) that should be called when that event is broadcast.
|
|
145
|
+
*
|
|
146
|
+
* After decorating the class, you must call `intercept(this)` in the constructor to
|
|
147
|
+
* activate interception, otherwise the methods will not receive any events.
|
|
148
|
+
*
|
|
149
|
+
* @template T - Type of the class constructor being decorated.
|
|
150
|
+
*
|
|
151
|
+
* @param events - An array of event descriptors. Each descriptor must include:
|
|
152
|
+
* - `type`: the event type string (strongly typed recommended)
|
|
153
|
+
* - `action`: the name of the class method to execute when the event occurs
|
|
154
|
+
* @returns A class constructor function with interception logic applied.
|
|
155
|
+
*/
|
|
156
|
+
function Interceptor(events = []) {
|
|
157
|
+
return function (orgConstructor) {
|
|
158
|
+
let listeners = [];
|
|
159
|
+
orgConstructor.prototype[DECORATOR_APPLIED] = true;
|
|
160
|
+
orgConstructor.prototype._intercept = function (instance) {
|
|
161
|
+
const rendererFactory = __directiveInject(RendererFactory2);
|
|
162
|
+
const renderer2 = rendererFactory.createRenderer(null, null);
|
|
163
|
+
listeners = initListeners(events, instance, renderer2);
|
|
164
|
+
};
|
|
165
|
+
const onDestroy = orgConstructor.prototype.ngOnDestroy;
|
|
166
|
+
orgConstructor.prototype.ngOnDestroy = function () {
|
|
167
|
+
listeners.map((listener) => listener());
|
|
168
|
+
onDestroy && onDestroy.call(this);
|
|
169
|
+
};
|
|
170
|
+
if (isPipe(orgConstructor)) {
|
|
171
|
+
const def = orgConstructor.prototype.constructor.ɵpipe;
|
|
172
|
+
def.onDestroy = function () {
|
|
173
|
+
listeners.map((listener) => listener());
|
|
174
|
+
onDestroy && onDestroy.call(this);
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* Public API Surface of ngx-event-bus
|
|
182
|
+
*/
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generated bundle index. Do not edit.
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
export { GEvent, Interceptor, broadcast, intercept };
|
|
189
|
+
//# sourceMappingURL=on-ngx-event-bus.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-ngx-event-bus.mjs","sources":["../../../projects/ngx-event-bus-lib/src/lib/consts/ng-pipe-def.const.ts","../../../projects/ngx-event-bus-lib/src/lib/consts/global-host.const.ts","../../../projects/ngx-event-bus-lib/src/lib/consts/decorator-applied.const.ts","../../../projects/ngx-event-bus-lib/src/lib/consts/trusted-event.const.ts","../../../projects/ngx-event-bus-lib/src/lib/consts/payload-prototype.const.ts","../../../projects/ngx-event-bus-lib/src/lib/api/broadcast.function.ts","../../../projects/ngx-event-bus-lib/src/lib/api/intercept.function.ts","../../../projects/ngx-event-bus-lib/src/lib/classes/gevent.class.ts","../../../projects/ngx-event-bus-lib/src/lib/utils/init-listeners.util.ts","../../../projects/ngx-event-bus-lib/src/lib/utils/is-pipe.util.ts","../../../projects/ngx-event-bus-lib/src/lib/decorators/interceptor.decorator.ts","../../../projects/ngx-event-bus-lib/src/public-api.ts","../../../projects/ngx-event-bus-lib/src/on-ngx-event-bus.ts"],"sourcesContent":["import { ɵNG_PIPE_DEF } from \"@angular/core\";\n\nexport const NG_PIPE_DEF: 'ɵpipe' = ɵNG_PIPE_DEF as 'ɵpipe';\n","\nexport const GLOBAL_HOST: \"window\" = \"window\";","\nexport const DECORATOR_APPLIED: symbol = Symbol('__interceptorDecoratorApplied');\n","\nexport const TRUSTED_EVENT: symbol = Symbol('__trustedEvent');\n","import { TRUSTED_EVENT } from \"./trusted-event.const\";\n\nexport const PAYLOAD_PROTOTYPE = Object.freeze({\n [TRUSTED_EVENT]: true,\n});","import { GEvent } from \"../classes\";\nimport { PAYLOAD_PROTOTYPE } from \"../consts\";\n\n/**\n * Sends a global, system-wide event via the Event Bus.\n *\n * The event will be received by all active `@Interceptor`s that are\n * listening for the same event type.\n *\n * Events may optionally include a `key`. When a key is provided,\n * only interceptors registered with the same key will accept the\n * event; all others will be rejected.\n *\n * The payload is strongly typed and may be any value:\n * primitives, objects, arrays, etc.\n *\n * Internally, the payload is wrapped in a trusted object to ensure\n * listeners only handle legitimate, system-generated events.\n * Interceptors always access the original payload via `event.detail.data`.\n *\n * @template T - Type of the event name (string literal recommended for type safety).\n * @template P - Type of the event payload.\n *\n * @param event - The typed event instance to broadcast.\n*/\nexport function broadcast<T extends string, P = null>(\n event: GEvent<T, P>\n): void {\n const trustedPayload = Object.create(PAYLOAD_PROTOTYPE);\n\n Object.assign(trustedPayload, { \n data: event.payload || null,\n key: event.key\n });\n \n window.dispatchEvent(\n new CustomEvent(event.type, {\n detail: trustedPayload,\n })\n );\n}\n","import { DECORATOR_APPLIED } from \"../consts\";\nimport { Args } from \"../models\";\n\n/**\n * Registers an instance to receive events through its @Interceptor-decorated methods.\n *\n * This function must be called inside the class constructor with `this` as the argument:\n * ```ts\n * constructor() {\n * intercept(this);\n * }\n * ```\n *\n * Only classes decorated with `@Interceptor` can be intercepted. Calling this on an\n * undecorated class will throw an error.\n *\n * After calling this, methods specified in the `@Interceptor` decorator will receive\n * events of the corresponding type via the Event Bus.\n *\n * @template T - Type of the instance being intercepted.\n *\n * @param instance - The class instance to register for interception.\n*/\nexport function intercept<T>(instance: T): void {\n return (function<U extends Args>(): void {\n const unDecorated: boolean = !(DECORATOR_APPLIED in (Object.getPrototypeOf(instance)));\n\n if(unDecorated) {\n throw new Error('intercept() function cannot be used inside any Angular classes ' +\n 'that are not decorated with @Interceptor decorator');\n }\n\n (instance as InstanceType<U>).constructor.prototype._intercept(instance);\n })()\n}\n","/**\n * Represents a strongly-typed event in the Event Bus system.\n *\n * Each `GEvent` has a `type` (string literal recommended for type safety) and a `payload`.\n * This object is used with the Event Bus functions, ensuring\n * type-safe communication across your Angular application.\n *\n * @template T - Type of the event name. Defaults to `string`, but string literals are recommended.\n * @template P - Type of the event payload. Defaults to `null` if no payload is needed.\n*/\nexport class GEvent<T extends string, P = null> {\n /** The type of the event */\n readonly type: T;\n\n /** The strongly-typed payload of the event */\n readonly payload?: P;\n\n /** Optional key used to scope or target the event */\n readonly key?: string;\n\n /**\n * Creates a new typed event instance.\n *\n * @param type - The event type identifier.\n * @param payload - Optional event payload.\n * @param key - Optional key used to target specific interceptors.\n */\n constructor(type: T, payload?: P, key?: string) {\n this.type = type;\n this.payload = payload;\n this.key = key;\n }\n}\n","import { Renderer2 } from \"@angular/core\";\n\nimport { Args, Event } from \"../models\";\nimport { GLOBAL_HOST, PAYLOAD_PROTOTYPE } from \"../consts\";\n\nexport function initListeners<T extends Args>(events: Event[], instance: InstanceType<T>, renderer2: Renderer2): Function[] {\n return events.map((event: Event) => {\n return renderer2.listen(GLOBAL_HOST, event.type, data => {\n // Guard 1:\n // Event Source - events originated from unknown or external sources will be rejected ❌\n if (Object.getPrototypeOf(data.detail) !== PAYLOAD_PROTOTYPE) {\n return;\n }\n\n // Guard 2:\n // Key-based event filtering - events broadcast with a mismatched key will be rejected ❌\n if (event.key !== data.detail.key) {\n return;\n }\n \n // Guard 3:\n // Interceptor integrity check — ensure the configured action exists and callable, misconfiguration is treated as a hard error ❌\n if (typeof instance[event.action] !== 'function') {\n throw new Error(\n `Interceptor error: \"${event.action}\" is not a function on ${instance.constructor.name}`\n );\n }\n \n // Event accepted ✅\n // At this point, the event is verified, trusted, and correctly targeted.\n instance[event.action](data.detail.data);\n })\n })\n}","import { PipeType } from \"../models\";\nimport { NG_PIPE_DEF } from \"../consts\";\n\nexport function isPipe<T>(target: T): target is T {\n return !!(target as unknown as PipeType<T>)[NG_PIPE_DEF];\n}","import { Renderer2, RendererFactory2, ɵPipeDef, ɵɵdirectiveInject } from \"@angular/core\";\n\nimport { initListeners, isPipe } from \"../utils\";\nimport { DECORATOR_APPLIED } from \"../consts\";\nimport { Event, Args } from \"../models\";\n\n/**\n * Class decorator that registers methods to handle cross-system events.\n *\n * Use this decorator on an Angular class to specify which events it should listen to.\n * Each object in the `events` array defines an event type and the corresponding class\n * method (action) that should be called when that event is broadcast.\n *\n * After decorating the class, you must call `intercept(this)` in the constructor to\n * activate interception, otherwise the methods will not receive any events.\n *\n * @template T - Type of the class constructor being decorated.\n *\n * @param events - An array of event descriptors. Each descriptor must include:\n * - `type`: the event type string (strongly typed recommended)\n * - `action`: the name of the class method to execute when the event occurs\n * @returns A class constructor function with interception logic applied.\n*/\nexport function Interceptor<T extends Args>(events: Event[] = []): (orgConstructor: T) => void { \n return function(orgConstructor: T): void {\n let listeners: Function[] = [];\n \n orgConstructor.prototype[DECORATOR_APPLIED] = true;\n orgConstructor.prototype._intercept = function(instance: InstanceType<T>): void {\n const rendererFactory: RendererFactory2 = ɵɵdirectiveInject(RendererFactory2);\n const renderer2: Renderer2 = rendererFactory.createRenderer(null, null);\n\n listeners = initListeners(\n events, instance, renderer2);\n }\n \n const onDestroy: Function = orgConstructor.prototype.ngOnDestroy;\n orgConstructor.prototype.ngOnDestroy = function(): void {\n listeners.map(\n (listener: Function) => listener());\n \n onDestroy && onDestroy.call(this);\n };\n\n if(isPipe<T>(orgConstructor)) {\n const def: ɵPipeDef<T> = orgConstructor.prototype.constructor.ɵpipe;\n def.onDestroy = function (): void {\n listeners.map(\n (listener: Function) => listener());\n \n onDestroy && onDestroy.call(this);\n };\n }\n }\n}\n","/*\n * Public API Surface of ngx-event-bus\n*/\nexport * from './lib/api';\nexport * from './lib/classes';\nexport * from './lib/decorators';","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["ɵNG_PIPE_DEF","ɵɵdirectiveInject"],"mappings":"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"}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "on-ngx-event-bus",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Orel Naten <natenorel@gmail.com>",
|
|
5
|
+
"description": "Broadcast cross-system Angular events — no services, zero DI/providers, zero RxJS/signals, zero leaks, fully typed and effortless.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"angular",
|
|
8
|
+
"event-bus",
|
|
9
|
+
"events",
|
|
10
|
+
"global-events",
|
|
11
|
+
"intercept",
|
|
12
|
+
"broadcast",
|
|
13
|
+
"typed",
|
|
14
|
+
"lightweight",
|
|
15
|
+
"angular-events"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/orelnatan/ngx-event-bus-lib",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/orelnatan/ngx-event-bus-lib.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/orelnatan/ngx-event-bus-lib/issues"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@angular/common": ">=9.0.0",
|
|
28
|
+
"@angular/core": ">=9.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "^2.3.0"
|
|
32
|
+
},
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"module": "fesm2022/on-ngx-event-bus.mjs",
|
|
35
|
+
"typings": "types/on-ngx-event-bus.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": {
|
|
38
|
+
"default": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./types/on-ngx-event-bus.d.ts",
|
|
42
|
+
"default": "./fesm2022/on-ngx-event-bus.mjs"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a strongly-typed event in the Event Bus system.
|
|
3
|
+
*
|
|
4
|
+
* Each `GEvent` has a `type` (string literal recommended for type safety) and a `payload`.
|
|
5
|
+
* This object is used with the Event Bus functions, ensuring
|
|
6
|
+
* type-safe communication across your Angular application.
|
|
7
|
+
*
|
|
8
|
+
* @template T - Type of the event name. Defaults to `string`, but string literals are recommended.
|
|
9
|
+
* @template P - Type of the event payload. Defaults to `null` if no payload is needed.
|
|
10
|
+
*/
|
|
11
|
+
declare class GEvent<T extends string, P = null> {
|
|
12
|
+
/** The type of the event */
|
|
13
|
+
readonly type: T;
|
|
14
|
+
/** The strongly-typed payload of the event */
|
|
15
|
+
readonly payload?: P;
|
|
16
|
+
/** Optional key used to scope or target the event */
|
|
17
|
+
readonly key?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new typed event instance.
|
|
20
|
+
*
|
|
21
|
+
* @param type - The event type identifier.
|
|
22
|
+
* @param payload - Optional event payload.
|
|
23
|
+
* @param key - Optional key used to target specific interceptors.
|
|
24
|
+
*/
|
|
25
|
+
constructor(type: T, payload?: P, key?: string);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Sends a global, system-wide event via the Event Bus.
|
|
30
|
+
*
|
|
31
|
+
* The event will be received by all active `@Interceptor`s that are
|
|
32
|
+
* listening for the same event type.
|
|
33
|
+
*
|
|
34
|
+
* Events may optionally include a `key`. When a key is provided,
|
|
35
|
+
* only interceptors registered with the same key will accept the
|
|
36
|
+
* event; all others will be rejected.
|
|
37
|
+
*
|
|
38
|
+
* The payload is strongly typed and may be any value:
|
|
39
|
+
* primitives, objects, arrays, etc.
|
|
40
|
+
*
|
|
41
|
+
* Internally, the payload is wrapped in a trusted object to ensure
|
|
42
|
+
* listeners only handle legitimate, system-generated events.
|
|
43
|
+
* Interceptors always access the original payload via `event.detail.data`.
|
|
44
|
+
*
|
|
45
|
+
* @template T - Type of the event name (string literal recommended for type safety).
|
|
46
|
+
* @template P - Type of the event payload.
|
|
47
|
+
*
|
|
48
|
+
* @param event - The typed event instance to broadcast.
|
|
49
|
+
*/
|
|
50
|
+
declare function broadcast<T extends string, P = null>(event: GEvent<T, P>): void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Registers an instance to receive events through its @Interceptor-decorated methods.
|
|
54
|
+
*
|
|
55
|
+
* This function must be called inside the class constructor with `this` as the argument:
|
|
56
|
+
* ```ts
|
|
57
|
+
* constructor() {
|
|
58
|
+
* intercept(this);
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* Only classes decorated with `@Interceptor` can be intercepted. Calling this on an
|
|
63
|
+
* undecorated class will throw an error.
|
|
64
|
+
*
|
|
65
|
+
* After calling this, methods specified in the `@Interceptor` decorator will receive
|
|
66
|
+
* events of the corresponding type via the Event Bus.
|
|
67
|
+
*
|
|
68
|
+
* @template T - Type of the instance being intercepted.
|
|
69
|
+
*
|
|
70
|
+
* @param instance - The class instance to register for interception.
|
|
71
|
+
*/
|
|
72
|
+
declare function intercept<T>(instance: T): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Describes an Event Bus mapping for a class method.
|
|
76
|
+
*
|
|
77
|
+
* Used in the `@Interceptor` decorator to specify which class method should
|
|
78
|
+
* respond to a particular event type.
|
|
79
|
+
*
|
|
80
|
+
* @property type - The event type string that this method should listen to.
|
|
81
|
+
* @property action - The name of the class method to execute when the event is intercepted.
|
|
82
|
+
* @property key - Optional event key used to scope or target events.
|
|
83
|
+
*/
|
|
84
|
+
interface Event {
|
|
85
|
+
/** The event type string */
|
|
86
|
+
type: string;
|
|
87
|
+
/** The class method name that handles the event */
|
|
88
|
+
action: string;
|
|
89
|
+
/** Optional key used to filter events */
|
|
90
|
+
key?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type Args = {
|
|
94
|
+
new (...args: any[]): {
|
|
95
|
+
[key: string]: any;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Class decorator that registers methods to handle cross-system events.
|
|
101
|
+
*
|
|
102
|
+
* Use this decorator on an Angular class to specify which events it should listen to.
|
|
103
|
+
* Each object in the `events` array defines an event type and the corresponding class
|
|
104
|
+
* method (action) that should be called when that event is broadcast.
|
|
105
|
+
*
|
|
106
|
+
* After decorating the class, you must call `intercept(this)` in the constructor to
|
|
107
|
+
* activate interception, otherwise the methods will not receive any events.
|
|
108
|
+
*
|
|
109
|
+
* @template T - Type of the class constructor being decorated.
|
|
110
|
+
*
|
|
111
|
+
* @param events - An array of event descriptors. Each descriptor must include:
|
|
112
|
+
* - `type`: the event type string (strongly typed recommended)
|
|
113
|
+
* - `action`: the name of the class method to execute when the event occurs
|
|
114
|
+
* @returns A class constructor function with interception logic applied.
|
|
115
|
+
*/
|
|
116
|
+
declare function Interceptor<T extends Args>(events?: Event[]): (orgConstructor: T) => void;
|
|
117
|
+
|
|
118
|
+
export { GEvent, Interceptor, broadcast, intercept };
|