i have this event management service in my angular application. i want to set the return type of the OnEvent() method dynamiclly and i want to set the type where i inject the service in my components.
for example this is component A's constructor which injected the same service with two differnt return type as T.
consructor(
private goodEventManagerService: EventManagementService<Array<IGood>>,
private customerEventManagerService: EventManagementService<ICustomer>
){
customerEventManagerService.emitEvent(EventManagementTypeEnum.CustomerChanged,null)
goodEventManagerService.emitEvent(EventManagementTypeEnum.CustomerChanged , [])
}
import { Injectable, OnDestroy } from '@angular/core';
import { AsyncSubject, Observable, Subject } from 'rxjs';
import { IEventPayload } from '../models/event-management';
import { EventManagementTypeEnum } from '../enums/event-management-type';
@Injectable({
providedIn: 'any',
})
export class EventManagementService<T> implements OnDestroy {
private _eventSubject$:AsyncSubject<IEventPayload<T>> = new AsyncSubject<IEventPayload<T>>();
emitEvent(eventType: EventManagementTypeEnum, data: T):void {
this._eventSubject$.next({ data: data, event: eventType });
this._eventSubject$.complete();
}
onEvent(): Observable<IEventPayload<T>> {
return this._eventSubject$.asObservable();
}
ngOnDestroy(): void {
this._eventSubject$.complete();
this._eventSubject$.unsubscribe();
}
}
is this possible in angular or even Typescript?
i dont want to check for the type in every subscription.
i tested it and the result is just the same return type as the first event emits.
ngOnInit(): void {
this.goodEventManagerService.onEvent().subscribe(res=>{
console.log("Cart Detail Component ---goodEventManagerService",res);
})
this.customerEventManagerService.onEvent().subscribe(res=>{
console.log("Cart Detail Component ---customerEventManagerService",res);
})
}
Cart Detail Component ---goodEventManagerService
{data: null, event: 'customer-changed'}
------------
Cart Detail Component ---customerEventManagerService
{data: null, event: 'customer-changed'}
as you can see the first data value is set to null and the second one is an empty array. but the result of both subscriptions are null
the EventManagementService is declared in a Core module which is a root module as a Library.
and the constructor is in a component called A and it is declared in an angular application using that internal library.
how do i solve this? any idea?
Since your service is a singleton, the approach you intend to follow does not work because you are always getting the same instance whenever you inject it, meaning, all places where you use it will subscribe to the same subject/observable and receive any events emitted by it, unless you properly filter them.
If you really want this singleton to be a global event manager in your app you'll need to change the implementation to something like this:
For example, let's say you have these events in your app:
enum AppEventType {
CustomerEvent = 'CustomerEvent',
GoodEvent = 'GoodEvent',
}
You could create a generic AppEvent<T>
type that represents any event that occurs in your app:
interface AppEvent<T = any> {
readonly type: AppEventType;
readonly data: T;
}
Then, you start creating specific events that implement this interface:
/* ICustomer Events */
class CustomerEvent implements AppEvent<ICustomer> {
public readonly type = AppEventType.CustomerEvent;
constructor(public readonly data: ICustomer) {}
}
/* IGood Events */
class GoodEvent implements AppEvent<IGood> {
public readonly type = AppEventType.GoodEvent;
constructor(public readonly data: IGood) {}
}
Now, since the service is a singleton and its subject is emitting all events for all instances where this singleton is used, we would need to offer a mechanism to filter the events by event type. We can do that by checking if the current event is an instance of the given type in the on$
method of the service:
@Injectable()
class AppEventManagerService {
// choose an appropriate subject type here based on your needs:
private readonly subject = new ReplaySubject<AppEvent>();
/** emit any event that implements AppEvent interface */
public emit(event: AppEvent): void {
this.subject.next(event);
}
/** create an observable that will track a specific type of event */
public on$<T extends AppEvent>(
type: new (...args: any[]) => T
): Observable<T> {
return this.subject
.asObservable()
.pipe(filter((e): e is T => e instanceof type));
}
}
NOTE: If your service will be used throughout the lifetime of your app, don't complete its subject, or in other words, DON'T DESTROY IT!
// x.component.ts
customer$!: Observable<CustomerEvent>;
good$!: Observable<GoodEvent>;
// inject your service only once and setup your event listeners
constructor(private appEventManager: AppEventManagerService) {
// create an observable that will track only CustomerEvents!
this.customer$ = this.appEventManager.on$(CustomerEvent);
// create an observable that will track only GoodEvents!
this.good$ = this.appEventManager.on$(GoodEvent);
}
// subscribe to your events
public ngOnInit(): void {
this.customer$.subscribe((event: CustomerEvent) => console.log(event));
this.good$.subscribe((event: GoodEvent) => console.log(event));
}
// emit a CustomerEvent
public emitCustomerEvent() {
const event = new CustomerEvent({ name: 'CUSTOMER' });
this.eventManager.emit(event);
}
// emit a GoodEvent
public emitGoodEvent() {
const event = new GoodEvent({ price: 29.99 });
this.eventManager.emit(event);
}
Here's a quick example that follows this pattern!