Marsha
Marsha

Reputation: 205

Why angular subscribes multiple times?

I have this structure:

I want to subscribe to an object in the service whenever the compare-product is loaded. It seems fine at first. But, when I hit the back button to the product-search, then load the compare-product again, it subscribes twice. When I go back and load again, it is called three times. Back, load again, called four times, and so on.

these are the codes:

service:

//product.service
.......
private checkedSrc = new BehaviorSubject([]);
currentChecked = this.checkedSrc.asObservable();

.......
setChecked(checked: string[]){
    this.checkedSrc.next(checked);
}

resetChecked(){
    this.checkedSrc.next([]);
}
.......

product-search component:

......
compare(){
    this.products_valid = true;
    this.productSvc.setChecked(this.checked);
    this.router.navigate(['compare']);
}
......

compare-product component:

...
products: string[];
isLoading: boolean = true;

constructor(
  private productSvc: ProductsService
) { }

ngOnInit() {
  this.productSvc.currentChecked.subscribe(p => {this.products = p; console.log(this.products)})
}

I have tried it without navigating to the compare component. When I first call the compare function it subscribes once, call the compare again it subscribes twice, call again it subscribes three times, and so on.

......
compare(){
    this.products_valid = true;
    this.productSvc.setChecked(this.checked);
    this.productSvc.currentChecked.subscribe(p =>{console.log(p);})
}
......

Button that calls it:

<div class="row">
  <div class="col-md-6">
    <button class="btn btn-primary" style="margin-top: 20px;" (click)="compare()">Compare</button>
  </div>
</div>

I also have tried to reset the object with resetChecked() everytime the compare method called, but still the same...

Upvotes: 5

Views: 8996

Answers (3)

Wandrille
Wandrille

Reputation: 6811

You need to unsubscribe to the observable when the component is destroyed. Each time you load the component, you have 1 subscription.

Upvotes: 9

Jacques
Jacques

Reputation: 7135

In Angular 6, if you're using the ngOnDestroy approach remember to use a reference to the subscription.

import { Subscription } from 'rxjs';
export class MyComponent implements OnInit, OnDestroy {

  private subscription: Subscription //import from rxjs 

  constructor(private myService: MyService){}

  ngOnInit() {
    this.subscription = this.myService.myMethod.subscribe(...);
  }

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

If, in ngOnDestroy, you call unsubscribe directly on this.myService.myMethod you'll end up getting a "ObjectUnsubscribedError" exception.

Upvotes: 2

a better oliver
a better oliver

Reputation: 26828

Whenever you call subscribe the source - checkSrc in this case - gets informed that a new subscriber wants to get data. The source doesn't know when you leave the page, it still keeps track of one subscriber. When you return subscribe is called again and now the source has two subscribers.

You have several options to solve the problem. The first one is to unsubscribe in the ngOnDestroy method:

subscription;

ngOnInit() {
  this.subscription = this.productSvc.currentChecked.subscribe(p => {this.products = p; })
}

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

A better option is to use the async pipe.

ngOnInit() {
 this.products = this.productSvc.currentChecked();
}

And in your HTML:

<ul>
  <li *ngFor="let product of products | async">{{product.name}}</li>
</ul>

As you can see this.products no longer refers to the products but the stream of products. In order to use in in your template you add the async pipe. On of the advantages is that you don't need to subscribe and unsubscribe yourself.

Upvotes: 8

Related Questions