-1

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?

Vishal G. Gohel
  • 1,008
  • 1
  • 16
  • 31

1 Answers1

1

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/...)
}
pascalpuetz
  • 5,238
  • 1
  • 13
  • 26
  • Thanks a lot for your help, voted up. This is really great approach and can be used easily. On the other hand, I also would like to be clarified on if I should use new BehaviorSubjects and get-set methods for each kind of operations if I want to use my approach above.Because if I have 4-5 different type of event (update, create, etc) I will need to use a long if-else block in your example. Any helps pls? –  Aug 05 '19 at 18:54
  • You can do that if want - but in that case you would have multiple subscriptions (or you would need to combine them back into one by using merge, then you'd be back to square one). Don't think that's any better. I'd rather try to get the events as similiar as possible so you won't need to differenciate between them in your subscription. – pascalpuetz Aug 05 '19 at 19:11
  • @hexadecimal Maybe, if that helps to decide, the [angular router events](https://angular.io/api/router/Event) work exactly like the second example (classes). – pascalpuetz Aug 05 '19 at 19:14
  • Sure, I keep in mind the caveats as you mentioned. If I do not understand wrong, I can create multiple subscriptions and in this case **I also need to create get-set methods for each of these subscriptions (tracker = new BehaviorSubject(undefined))**. Is that true? –  Aug 05 '19 at 19:16
  • Yes, that's what you would need. Alternatively, you could create a method with a parameter and the return the correct observable. I'll add that as an example to my answer. – pascalpuetz Aug 05 '19 at 19:17
  • You rock!.. Many thanks for your helps. Last question : **How can I group events and create BehaviorSubject for each of these groups?** I mean that for example there are two update events; one for employee, the other is salary. In this case I think I should create 2 updateTracker, is that true? –  Aug 05 '19 at 19:30
  • You're very welcome! :) There are a lot of ways to go about that. One way would be indeed to create multiple behaviour subjects. For listening you can group them as I did with `merge`. And for setting you usually want to only set a specific one - or you would need to logically group them by using a setter method. Tbh, in your case I'd switch to a state management library like @ngxs by now. That makes it a lot less complex. – pascalpuetz Aug 05 '19 at 19:33
  • Actually there are some state management libraries but first of all I want to get experience on Angular and use the most common ones. Later of course think of use that. Marked as answer ;) many thanks... –  Aug 05 '19 at 19:52
  • What about using a single subscription as mentioned on [Using a single subscription variable with BehaviorSubject](https://stackoverflow.com/questions/57562345/using-a-single-subscription-variable-with-behaviorsubject)? Any idea about that? –  Aug 19 '19 at 18:52