3

I have started working on an angular 8 project where two sibling components have to exchange data. So far, the approach was to have an EventEmitter in the parent's service. The child components then called the emit methods on these emitters to pass the data to the other sibling. Here an example case:

Shared Service: (bad)

@Injectable()
export class DocumentViewerService {
    public readonly annotationDeletedInAnnotationPanel = new EventEmitter<string>();

Child Component: (bad)

this.documentViewerService.annotationDeletedInAnnotationPanel.emit(annotationId);

However, I found several online sources (1, 2) that claim using EventEmitters in Services is bad practice.

Instead, most online sources (3, 4) propose a pattern using Subjects and shared services for sibling communication.

Shared Service: (good)

@Injectable()
export class DocumentViewerService {
   private deletedAnnotationInAnnotationPanel$ = new Subject<string>();
   public readonly deletedAnnotationInPanel$ = this.deletedAnnotationInAnnotationPanel$.asObservable();

   deleteAnnotationInPanel(annotationId: string) {
        this.deletedAnnotationInWebViewer$.next(annotationId);
   }

Child Component: (good)

this.documentViewerService.deleteAnnotationInPanel(id);

Both approaches leave me with a few questions:

  1. Why is it bad practice to use EventEmitters in Service. You don't need to put the @Output tag and it will act similar to an observable when called with emit in one of the childs.
  2. What's the reason to make the Subject private in the supposedly better approach? Children components can still change the values by calling the provided method deleteAnnotationInPanel (in sample above).
  3. What's the difference in making the Subject deletedAnnotationInAnnotationPanel$ private vs. public readonly. The latter would allow everyone to subscribe and call .next, but not to change initialisation. Instead of five lines of code, I would only have one:

    public readonly deletedAnnotationInPanel$ = new Subject();

None of the online sources seem to explain why to make the Subject private with a setter method and an additional public observable for children.

Hopefully, you can provide me with some strong arguments or insights into why one should be preferred over the others. Thanks in advance for the support!

skye93
  • 61
  • 5
  • 1. `EventEmitter` extends `Subject` so nothing stops you from using it. But it is not intended to be used like that (since you already have `Subject`) ([source_code](https://github.com/angular/angular/blob/master/packages/core/src/event_emitter.ts)) – miselking Feb 21 '20 at 16:40

2 Answers2

1
  1. Why is it bad practice to use EventEmitters in Service.

    EventEmitter is meant to be used in Components. As the documentation says,

    Use in components with the @Output directive to emit custom events synchronously or asynchronously...

    Even though it has some semantics of Subject, its not designed for that purpose.

  2. What's the reason to make the Subject private in the supposedly better approach?

    Subject is like state of your Service class, you don't want to expose it publicly and have rest of the application start triggering events out of it. You want to ensure that service emits events only when observers needs to be informed for real. Thus, by writing methods like deleteAnnotationInPanel, you are building a layer of abstraction, and ensuring that users of service perform actions using service and some of those actions may result in events to be fired.

  3. What's the difference in making the Subject deletedAnnotationInAnnotationPanel$ private vs. public readonly.

    If you make it private, users of service class will not have access to it and no one will be able to subscribe to events being published by subject. Thus, making it public readonly is inevitable. You want it to be readonly because you don't want a rogue code to overwrite it to be observer for some other subject and start emitting events unnecessarily.

Wand Maker
  • 18,476
  • 8
  • 53
  • 87
  • Thanks for the quick response! About question 2. I still don't see the benefit of having an additional method when you could do the same with a public readonly subject in the first place. When calling the method, the observers can still change the subject. So there is no real benefit as far as I can see. About 3. It was rather the question whether a public readonly Subject could do the whole job of the supposedly better approach, similar to EventEmitter. Children can still subscribe and call next on it, similar to the other approaches, but with a single line of code. What do you think? – skye93 Feb 21 '20 at 18:59
1
  1. Why is it bad practice to use EventEmitters in Service. You don't need to put the @Output tag and it will act similar to an observable when called with emit in one of the childs.

The Angular team don't guarantee that EventEmitter will always act like an observable, and using it as an observable is discouraged. EventEmitters are an abstraction of user events, don't have any place in services that are meant to be isolated from the UI.

  1. What's the reason to make the Subject private in the supposedly better approach? Children components can still change the values by calling the provided method deleteAnnotationInPanel (in sample above).

This is all about creating your own interface for how the different areas of your app interact. When you create your own service API, you are in control of it, and can make changes to the implementation without changing your interface.

  1. What's the difference in making the Subject deletedAnnotationInAnnotationPanel$ private vs. public readonly. The latter would allow everyone to subscribe and call .next, but not to change initialisation. Instead of five lines of code, I would only have one:

Exposing the raw subject means that any component can complete the subject and spoil the party for everybody else. That's pretty dangerous. Expose only the behaviour that is needed to maintain control.

A few more lines of code that are much more maintainable are far better than fewer lines of code that have the potential to cause chaos in an unconstrained environment.

Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40
  • Thanks for the detailed response! You make some very good points. I will go for the better approach then. To me it is also better understandable what the service actually does and you could also add some validation in the method changing the subjects value. However, some sources claim that asObservable has a performance impact and we should consider using public myObservable$: Observable = this.myObservable; instead. [source](https://stackoverflow.com/questions/48935584/observable-vs-subject-and-asobservable) – skye93 Feb 21 '20 at 19:48
  • 1
    Interesting that people recommend against using `asObservable`. We're still writing javascript, and there's nothing to stop other code accessing the underlying subject. I'd be interested to know what the actual performance hit is. Thanks for getting back! – Kurt Hamilton Feb 22 '20 at 06:53