tillias
tillias

Reputation: 1265

Angular 6 call subscribe() for Observable multiple times

I have two components: NewItemComponent and ListComponent. When I create new item inside corresponding component I notify ListComponent so it can refresh it's data model:

export class NewItemComponent implements OnInit {

  constructor(private itemService: ItemService, private notificationService: NotificationService) {
  }

  ngOnInit() {
  }

  createNewItem(item: Item) {
    this.itemService.persist(item).subscribe((response: Item) => {
      console.log(response);
      this.notificationService.notifyNewItemHasBeenCreated(response);
    });
  }
}


export class ListComponent implements OnInit {

  items: Item[];

  constructor(private listService: ListService, private notificationService: NotificationService) {
  }

  ngOnInit() {
    this.loadItems();

    this.notificationService.item$.subscribe((item) => {
      if (item != null) {
        this.loadItems();
      }
    })
  }

  loadItems(){
    this.istService.getItems().subscribe((data: Item[]) => {
      this.items= data;
      console.log(this.items);
    });
  }
}


@Injectable({
  providedIn: 'root'
})
export class NotificationService {

  private _item: BehaviorSubject<Item> = new BehaviorSubject<Item>(null);
  public  item$ = this._item.asObservable();

  constructor() {
  }

  notifyNewItemHasBeenCreated(item: Item) {
    this._item.next(item);
  }
}

What makes me worried is that loadItems() calls subscribe() multiple times. Is this ok or there is better way to refetch items based on notification?

  loadItems(){
    this.listService.getItems().subscribe((data: Item[]) => {
      this.items= data;
      console.log(this.items);
    });
  }

ListService returns Observable:

export class ListService {

  basePath = 'my-api.com';
  apiPath = "item";

  constructor(private httpClient: HttpClient) {
  }

  getItems(): Observable<Item[]> {
    return this.httpClient.get<Item[]>(this.basePath + '/' + this.apiPath);
  }
}

Thanks in advance and any help will be really appreciated.

Upvotes: 3

Views: 29366

Answers (5)

Joseph Webber
Joseph Webber

Reputation: 2173

Edit:

After looking around I found these posts:
Is it necessary to unsubscribe from observables created by Http methods?
Prevent memory leaks in Angular 2?

The comments and answers explain HttpClient.get() subscriptions clean up after themselves so you don't need to unsubscribe from them. That means calling .subscribe() on them multiple times is fine.

Upvotes: 1

Eluzive
Eluzive

Reputation: 99

loadItems() calls subscribe only once.
What actually happens is that you call loadItems() multiple times in notificationService.item$'s subscription.
If you need NotificationService only for this purpose I would suggest you little refactoring.

new-item.component.ts

export class NewItemComponent {

  constructor(private itemService: ItemService, private listService: ListService) {
  }

  createNewItem(item: Item) {
    this.itemService.persist(item).subscribe((response: Item) => {
      this.listService.addItem(item);
    });
  }
}

list.service.ts

export class ListService {

  basePath = 'my-api.com';
  apiPath = 'item';

  private itemsSubject: BehaviorSubject<Item[]> = new BehaviorSubject<Item[]>([]);
  private items$: Observable<Item[]>;

  constructor(private httpClient: HttpClient) {
  }

  getItems(): Observable<Item[]> {
    const http$ = this.httpClient.get<Item[]>(this.basePath + '/' + this.apiPath);
    return combineLatest(http$, this.items$).pipe(
      map(([httpResponse: Item[], localItems: Item[]]) => httpResponse.concat(localItems)),
    );
  }

  addItem(item: Item) {
    this.itemsSubject.next([...this.itemsSubject.value, item]);
  }

}

Upvotes: 1

Mark
Mark

Reputation: 137

What you're doing right now is:

  1. Make new item, send to server
  2. Server sends success response with item to item service. It notifies notification service. You don't actually do anything with this item but verify it.
  3. Your notification service gets item, notifies your list component.
  4. Your list component checks if the item is valid, then sends another request to the server for the updated list
  5. List component subscribes to the list service for the updated list. Updates when it comes.

So in a nutshell, for every new item you make you make 2 requests to the server and get 2 responses.

You can see that there's alot of redundancy here. Let's try to simplify things:

  1. Make new item, send to server
  2. Server sends success response with updated item list. It will only send this item list if the item received was valid, otherwise send back an error response.
  3. Your notification service is subscribed to this and gets item list that is already verified by the server, sends your list service this new item list.
  4. List component updates view with new data.

Now you only make one request each time instead of two. And you don't have to deal with so many Observables anymore.

Upvotes: 0

Shashikant Devani
Shashikant Devani

Reputation: 2455

If you subscribe call multiple time, than unsubscribe it when you component destroy.

Do change in your component like this :

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

export class ListComponent implements OnInit, OnDestroy {

  items: Item[];

  constructor(private listService: ListService, private notificationService: NotificationService) {
  }

  subscriptions: Subscription[] = [];

  ngOnInit() {
     this.subscriptions.push(this.loadItems());

     this.subscriptions.push(this.notificationService.item$.subscribe((item) => {
       if (item) {
         this.loadItems();
       }
     }));
  }

  ngOnDestroy() {
    this.subscriptions.forEach(x => x.unsubscribe());
  }

  loadItems(){
    this.istService.getItems().subscribe((data: Item[]) => {
      this.items= data;
      console.log(this.items);
  });
 }
}

Upvotes: 1

Michael
Michael

Reputation: 2454

As an experiment, if you do the following:

this.httpClient.get("<some url>")
  .subscribe({
    next: () => {
      console.log("received response")
    },
    error: err => {
      console.log("error occurred")
    },
    complete: () => {
      console.log("subscription completed")
    },
  })

You should see:

received response
subscription completed

This means that the observable for each web request is being completed after the request finishes, and therefore it is safe to not unsubscribe, as this is automatically done at the completion of an observable.

Upvotes: 9

Related Questions