angularangular-event-emitter

Angular EventEmitter called multiple times


This is really strange and hard to explain. I have use EventEmitter in a few of my services and I have been using it to change data on my views. I have had an issue with change routes (via links or via history back) where it seems to be firing multiple times and because of this it is messing up my logic.

So I created a test on stackblitz to see if I could recreate it. I made a simple service:

import { Injectable, Output, EventEmitter } from '@angular/core';

@Injectable()
export class ListService {
@Output() listChanged: EventEmitter<any[]> = new EventEmitter<any[]>()

  constructor() { }

  list() {
    this.listChanged.emit([]);
  }
}

and then in one of my routes, I just do this:

import { Component, OnInit } from '@angular/core';

import { ListService } from '../list.service';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
  count: any[] = []

  constructor(
    private listService: ListService
  ) { }

  ngOnInit() {
    this.listService.listChanged.subscribe(() => {
      this.count.push('invoked');
      console.log('invoked');
      console.log('------');
    });
    this.listService.list();
  }
}

And I then created a simple template like this:

<p>
  Invoke should only be called once
</p>

<ul>
  <li *ngFor="let item of count">{{ item }}</li>
</ul>

When you navigate between routes, it looks like it is working as expected (the ngFor should only ever have one item), but if you open the console and have a look, you will see that each time you go from one view and back, it fires an extra time.

This will keep happening every time you swap views. Here is the stackblitz link so you can see it yourself.

https://stackblitz.com/edit/angular-sthags

Can anyone tell me why this is happening and how to stop it?


Solution

  • This is because you are not killing the subscription when your component destroys (causing the memory leak by creating new subscriptions everytime your Products component initializes)

    Assign the subscription to a class variable, use the ngOnDestroy() hook to kill the subscription.

    subsVar: Subscription;
    
    ngOnInit() {
        this.subsVar = this.listService.listChanged.subscribe(() => {
          this.count.push('invoked');
          console.log('invoked');
          console.log('------');
        });
    }
    
    ngOnDestroy() {
       if (this.subsVar) {
          this.subsVar.unsubscribe()
        }
    }
    

    https://stackblitz.com/edit/angular-9rvxgv?file=src/app/products/products.component.ts