Search code examples
angulartypescripteventemitterbehaviorsubject

Can I use a single BehaviorSubject for multiple events?


In Angular 8, I use the following approach in order to refresh Details page after a new record added:

EventProxyService

export class EventProxyService {
    private eventTracker = new BehaviorSubject<any>(undefined);

    /* Allows subscription to the behavior subject as an observable */
    getEvent(): BehaviorSubject<any> {
        return this.eventTracker;
    }

    /* Allows updating the current value of the behavior subject */
    setEvent(param: any): void {
        this.eventTracker.next(param);
    }
}

CreateComponent:

export class CreateComponent implements OnInit {

    constructor(private eventProxyService: EventProxyService) { }

    triggerAnEvent(param: any): void {
        this.eventProxyService.setEvent(param);
    }
}

DetailsComponent:

export class DetailsComponent implements OnInit {

    subscription;

    constructor(private eventProxyService: EventProxyService) { }

    ngOnInit() {
        this.subscription = this.eventProxyService.getEvent().subscribe((param: any) => {
            this.theTargetMethod(param);
        );
    }

    theTargetMethod(param) {
        this.record = param; //update record via new one passed from service
    }

    ngOnDestroy(): void {
        this.subscription.unsubscribe();
    }
}

The approach works as expected, but sometimes there are similar events e.g. Update that needs to refresh the Details page. So, I am wondering if I should create a new BehaviorSubject object (eventTracker), getEvent and setEvent methods for each of different events e.g. Update event? As far as I know, a single BehaviorSubject can be used, but there may be a problem if two different events pass inconsistent data to the subscribers. What is the proper approach?


Solution

  • The simpliest way would be to create a type CustomEvent (wouldn't name it Event since that type is already used). You could either give it a field eventType or use classes that extend from that CustomEvent to differenciate what kind of Event is used.

    1. eventType field

    events.ts

    export interface CustomEvent{
       eventType: 'Update' | 'OtherEvent';
       data: any; // Type unsafe, maybe narrow it down
    }
    

    EventProxy

    export class EventProxyService {
        private eventTracker = new BehaviorSubject<CustomEvent>(undefined);
        getEvent(): BehaviorSubject<CustomEvent> { return this.eventTracker; }
        setEvent(param: CustomEvent): void { this.eventTracker.next(param); }
    }
    

    add/get events

    // Services omitted, too long
    // add new event
    this.eventProxyService.setEvent({
        eventType: 'Update',
        data: {/* Your data here */}
    });
    
    // listen to events
    this.subscription = this.eventProxyService.getEvent()
        // .filter(param => param.evenType === 'Update') // if you want only specific events
        .subscribe((param: CustomEvent) => {
            if (param.eventType === 'Update') {
                this.theTargetMethod(param);
            } else if (param.eventType === 'OtherEvent') {
                // do something else
            }
        );
    
    

    2. Event classes

    events.ts

    export class CustomEvent {}
    export class UpdateEvent extends CustomEvent {
       constructor(
          public newData: Data // Use a specific type and as many fields as you need
       ) {}
    
    }
    export class OtherEvent extends CustomEvent {
       constructor(
          public otherData: OtherData// Use a specific type and as many fields as you need
       ) {}
    }
    

    EventProxy

    export class EventProxyService {
        private eventTracker = new BehaviorSubject<CustomEvent>(undefined);
        getEvent(): BehaviorSubject<CustomEvent> { return this.eventTracker; }
        setEvent(param: CustomEvent): void { this.eventTracker.next(param); }
    }
    

    add/get events

    // Services omitted, too long
    // add new event
    this.eventProxyService.setEvent(new UpdateEvent({
       /* Your data here */
    }));
    
    // listen to events
    this.subscription = this.eventProxyService.getEvent()
        // .filter(param => param instanceof UpdateEvent) // if you want only specific events
        .subscribe((param: CustomEvent) => {
            if (param instanceof UpdateEvent) {
                this.theTargetMethod(param);
            } else if (param instanceof OtherEvent) {
                // do something else
            }
        );
    
    

    3. Multiple Subjects

    EventProxy

    export type EventType: 'update' | 'other';
    
    export class EventProxyService {
        // Use BehaviourSubject<SPECIFIC_TYPE> if possible
        private updateEventTracker = new BehaviorSubject<any>(undefined);
        private otherEventTracker = new BehaviorSubject<any>(undefined);
    
        setEvent(type: EventType, param: any): void { 
            this.getEventTrackerForType(type).next(param);
        }
        getEvent(type?: EventType): BehaviorSubject<any> { 
            return this.getEventTrackerForType(type);
        }
    
        private getEventTrackerForType(type?:EventType): BehaviorSubject<any> {
            switch(type) {
                case 'update': return this.updateEventTracker;
                case 'other': return this.otherEventTracker;
                // if no type specified, return ALL events as one Observable (bonus)
                default: return merge(
                    this.updateEventTracker, 
                    this.otherEventTracker
                )
            }
        }
    
        // alternatively, create setEvent/getEvent methods for each subject specifically (setUpdateEvent/getUpdateEvent/...)
    }