How emit changes using RxJS and angular?

Viewed 49

I have a service for pulling and pushing goods named BasketService.

export class BasketService {
apiUrl = 'http://0.0.0.0:8000';
basket: Observable<Basket>;
constructor(private httpClient: HttpClient) {}
getBasket(): Observable<Basket> {
    this.basket = this.httpClient.get<Basket>(`${this.apiUrl}/api-auth/basket/`);
    return this.basket;
}
addToBasket(sku: Sku, count: number): Observable<Basket> {
   const oBasket = this.httpClient.post<Basket>(`${this.apiUrl}/api-auth/basket/`, {items: 
   [{sku: sku.pk, quantity: count}]});
   return oBasket;
   }
}

I subscribe to change basket in my component BasketService.getBasket().subscribe(), after that i push some goods to basket by calling addToBasket function. I want to update subscriper who subscribe to getBasket, but as expected this not doing.

export class BasketComponent implements OnInit {
  basketIcon = faShoppingBasket;
  sum = 0;
  sumVerbose = 'корзина пуста';
  basket: Basket;

  constructor(private router: Router, private basketService: BasketService) {
  }

  openBasket() {
    this.router.navigate(['basket']);
  }

  ngOnInit(): void {
    this.basketService.getBasket().subscribe(basket => {this.basket = basket; console.log(basket); });
    //console.log(this.basket);
  }

}
export class PromoSkuComponent implements OnInit {
  basketIcon = faShoppingBasket;
  @Input() title: string;
  @Input() pk: number;
  @Input() price: number;
  @Input() image: string;

  constructor(private router: Router, private basketService: BasketService) {
  }

  ngOnInit(): void {
  }

  addToBasket() {
    this.basketService.addToBasket({pk: this.pk} as Sku, 1).subscribe(basket => basket);
  }
1 Answers

The observable returned from http.get is a single-use observable.

To emit future values you will need to create a proxy subject.

In the example below, I am declaring a private Subject<Basket>. Both getBasket and addToBasket create the subject if one doesn't exist. This means that you will have either no subject or the same subject instance returned from all functions.

After http responses are received, emit the response through the subject. All subscribers will receive the new value.

export class BasketService {
  constructor(private httpClient: HttpClient) {}

  private apiUrl = 'http://0.0.0.0:8000';
  private basket$: Subject<Basket>;

  getBasket(): Observable<Basket> {
    // Basket has already been loaded. Return the subject
    if (this.basket$) {
      return this.basket$.asObservable();
    }

    // Create a replay subject to ensure late subscribers receive an initial value
    this.basket$ = new ReplaySubject<Basket>(1);

    // Subscribe to the http client and emit the response
    const url = `${this.apiUrl}/api-auth/basket/`;
    this.httpClient.get<Basket>(url)
      .subscribe(basket => this.basket$.next(basket));

    // Return the subject
    return this.basket$.asObservable();
  }

  addToBasket(sku: Sku, count: number): Observable<Basket> {
    const url = `${this.apiUrl}/api-auth/basket/`;
    const body = {items: [{sku: sku.pk, quantity: count}]};

    // Create subject if it doesn't exist.
    // It may be unlikely that you can addToBasket if you haven't loaded the basket, this is here for safety
    if (!this.basket$) {
      this.basket$ = new ReplaySubject<Basket>(1);
    }

    // Update the basket, emit the response to all current and future subscribers
    this.httpClient.post<Basket>(url, body)
      .subscribe(basket => this.basket$.next(basket));

    return this.basket$.asObservable();
  }
}

This assumes that you only want to perform the GET the first time getBasket is called. If you want to always return the latest basket value, you can create the subject at the point of declaration, and always run the http GET.

private basket$: Subject<Basket> = new ReplaySubject<Basket>(1);

getBasket(): Observable<Basket> {
  const url = `${this.apiUrl}/api-auth/basket/`;
  this.http.get(url).subscribe(basket => this.basket$.next());
  return this.basket$.asObservable();
}

addToBasket(sku: Sku, count: number): Observable<Basket> {
  const url = `${this.apiUrl}/api-auth/basket/`;
  const body = {items: [{sku: sku.pk, quantity: count}]};
  this.httpClient.post<Basket>(url, body)
    .subscribe(basket => this.basket$.next(basket));

  return this.basket$.asObservable();
}

If you are calling addToBasket repeatedly from the same component, you may prefer to not return the subject from addToBasket, but use the following pattern in your component.

basket: Basket;

ngOnInit() {
  this.basketService.getBasket().subscribe(basket => {
    this.basket = basket;
  });
}

addToBasket(product) {
  this.basketService.addToBasket(product);
}

Where addToBasket simply performs the http POST, and doesn't return the observable. The getBasket subscription here will simply receive the updated value.

Related