Angular 6 call subscribe() for Observable multiple times

Viewed 25603

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.

5 Answers

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.

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);
  });
 }
}

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]);
  }

}

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.

Related