You shouldn't use EventEmitters in Angular services

Cover photo by Debby Hudson on Unsplash

By reviewing Angular code daily through mentoring or community support, I happen to find EventEmitters being used in Angular services.

Here is an example with a service dedicated to broadcasting some data to other parts of the application :

export class DataService {
  data: EventEmitter<Data> = new EventEmitter<Data>();

  updateData(data: Data): void {
    this.data.emit(data);
  }
}

By using dependency injection, a component can subscribe to the EventEmitter to receive the emitted values :

export class MyComponent {
  constructor(private readonly dataService: DataService) {}

  ngOnInit() {
    this.dataService.data.subscribe(() => {
      // do whatever you want
    });
  }
}

It works: if another part of the application uses updateData to emit a value, the component will receive it.

So why shouldn't you use it?

Let's take a look at EventEmitter API.
Here is a simplified version of the original EventEmitter codebase based on its usage in the previous code samples :

class EventEmitter extends Subject<any> {
  constructor() {
    super();
  }

  emit(value?: any) {
    super.next(value);
  }

  subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription {
    const sink = super.subscribe({next: observerOrNext, error: error, complete: complete});

    return sink;
  }
}

From creating your EventEmitter to the subscription, you use nothing more than the extended class : a Subject.

The first motivation not to use an EventEmitter over a Subject is to keep it simple stupid (KISS), as a Subject already provides you all you need.

The second reason lies in the original purpose of an EventEmitter as explained in the reference API :

Use in components with the @Output directive to emit custom events synchronously or asynchronously, and register handlers for those events by subscribing to an instance.

By using it for another purpose might lead to bugs if changes occur on this API for the benefit of its original purpose.

How to refactor your codebase

A reminder of the previous usage:

export class DataService {
  data: EventEmitter<Data> = new EventEmitter<Data>();

  updateData(data: Data): void {
    this.data.emit(data);
  }
}

The required changes are:

  • the type from EventEmitter to Subject
  • the exposure of the Subject as a private resource for the service to avoid external emissions
  • the creation of a public Observable version of the Subject you can subscribe to
  • an update to match the API to emit a new value
export class DataService {
  // change the type and the visibility
  private dataSubject: Subject<Data> = new Subject<Data>();
  // create a public observable out of the subject for external usage
  data$: Observable<Data> = this.dataSubject.asObservable();

  updateData(data: Data): void {
    // update the API
    this.dataSubject.next(data);
  }
}

Such a move is also a great opportunity to explore variant Subjects: BehaviorSubject, ReplaySubject, and AsynSubject.

Happy coding!

27