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.
In such scenarios we can use RxJS take(1) operator which is great because it automatically unsubscribes after the first execution.
You always have to unsubscribe from: Any Observable or Subject you created manually.
If the interval doesn't emit up to 9 values before the AppComponent is destroyed, the observable$ subscription will still be open until the interval emits 10 before it is destroyed. So for safety, we add the ngOnDestroy hook to unsubscribe observable$ when the component is destroyed.
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(); // <------- }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With