68

I have a Service and a component that uses it:

  • PagesService
  • PagesListComponent

In the PagesService I have an array of Pages. I notify changes in the array via a BehaviorSubject which both of them are subscribed to.

The PagesService are provided at bootstrap, to have just one instance shared. That's because I need to keep the array, instead of downloading the pages everytime they are needed.

The code is the following:

pages.service.ts

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/Rx';
import { Http, Response } from '@angular/http';

import { Page } from './../models/page';

@Injectable() export class PagesService {

    public pages$: BehaviorSubject<Page[]> = new BehaviorSubject<Page[]>([]);
    private pages: Page[] = [];

    constructor(private http: Http) { }

    getPagesListener() {
        return this.pages$;
    }
    getAll() {
        this.http.get('/mockups/pages.json').map((res: Response) => res.json()).subscribe(
            res => { this.resetPagesFromJson(res); },
            err => { console.log('Pages could not be fetched'); }
        );
    }

    private resetPagesFromJson(pagesArr: Array<any>) {
        // Parses de Array<any> and creates an Array<Page>
        this.pages$.next(this.pages);
    }
}

pages_list.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router-deprecated';
import { BehaviorSubject } from 'rxjs/Rx';

import { PagesService } from '../../shared/services/pages.service';
import { GoPage } from '../../shared/models/page';

@Component({
    moduleId: module.id,
    selector: 'go-pages-list',
    templateUrl: 'pages_list.component.html',
    styleUrls: ['pages_list.component.css']
})
export class PagesListComponent implements OnInit {
    pages$: BehaviorSubject<GoPage[]>;
    pages: GoPage[];
    constructor(private pagesService: PagesService, private router: Router) { }

    ngOnInit() {
        this.pages$ = this.pagesService.getPagesListener();
        this.pages$.subscribe((pages) => { this.pages = pages; console.log(pages) });
        this.pagesService.getAll();
    }
    ngOnDestroy() {
        this.pages$.unsubscribe();
    }
}

This works fine the first time, both the subscription onInit and de unsubscription onDestroy. But when I return to the list and try to subscribe again (to fetch the current value of pages[] and listen for future changes), it fires the error EXCEPTION: ObjectUnsubscribedError.

If I don't unsubscribe, everytime I enter to the list, a new subscription is stacked, and all of them are fired when a next() is received.

AlvYuste
  • 915
  • 1
  • 6
  • 14

2 Answers2

122

I would get the subscription and unsubscribe on it this way and not on the subject directly:

ngOnInit() {
  this.pages$ = this.pagesService.getPagesListener();
  this.subscription = this.pages$.subscribe((pages) => { // <-------
    this.pages = pages; console.log(pages);
  });
  this.pagesService.getAll();
}

ngOnDestroy() {
    this.subscription.unsubscribe(); // <-------
}
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • 12
    can you mention why did directly calling unsubscribe on the injected subject reference didn't work ? – Vishal Nair May 04 '17 at 20:41
  • 6
    "The unsubscribe method in the Subject class doesn’t actually unsubscribe anything. Instead, it marks the subject as closed and sets its internal array subscribed observers  to null" More in detail: https://blog.angularindepth.com/rxjs-closed-subjects-1b6f76c1b63c – István Békési Aug 08 '18 at 13:22
  • i've just tried on an eventEmitter that i need to unsubscribe and re subscribe and it works perfectly... but i still have a question: why this.subscription can be unsubscribed and re subscribed without problems meanwhile if you do it directly to the event/observable cannot be re subscribed? – Takatalvi Jan 03 '19 at 14:36
  • 1
    Conclusion: Save subscription in VARIABLE, and call unsubscribe method on it (not on function that you are subscribed to)! Man thank you a lot! – Dacili May 11 '20 at 13:29
28

.subscribe() returns a Subscription

  • You should use this to unsubscribe


e.g. Parent has a reloadSubject: Subject;

  • child1 -> subscribes
  • child2 -> subscribes

child1 - "WORKS" -> unsubscribe's his subscription

ngOnInit{ 
  sub: Subscription = parent.subscribe();
}
onDestroy{
  this.sub.unsubscribe();
}


child2 - "DOES NOT WORK" -> unsubscribe's the whole parent

ngOnInit{ 
  parent.subscribe();
}

onDestroy{
  parent.unsubscribe();
}

If you call unsubscribe on the parent both children are gone.
If you unsubscribe the Subscription that you get from the .subscribe() then only one child is unsubscribed.

Please correct me if I am wrong!

WorstCoderEver
  • 414
  • 1
  • 5
  • 15