Jadi, saya menggunakan shareReplay(1) untuk men-cache array Item dalam memori yang dikembalikan dari panggilan GET HttpClient.

if (!this.getItems$) {
    this.getItems$ = this.httpClient
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));
}
return this.getItems$;

Saya melakukan ini karena saya memiliki banyak komponen pada halaman yang memerlukan larik Item dan saya tidak ingin membuat panggilan http untuk masing-masingnya.

Saya juga membuat Item di halaman. Jadi, setelah saya melakukan panggilan ke layanan untuk memanggil API yang membuat Item dalam database dan mengembalikannya, saya ingin menambahkannya ke array Item di memori dan memperingatkan semua komponen yang berlangganan array yang diperbarui . Jadi, saya mencoba ini:

private items = new BehaviorSubject<Item[]>(null);

...

if (!this.getItems$) {
this.getItems$ = this.httpClient
    .get<Item[]>(url, options)
    .pipe(
      tap({
        next: (items) => {
          this.items.next(items);
        },
      })
    )
    .pipe(multicast(() => this.items));
}
return this.getItems$;

Kemudian dalam metode yang memanggil API:

return this.httpClient.post<Item>(url, item, options).pipe(
      tap({
        next: (item) => {
          this.items.next(this.items.value.push(item));
        },
      })
    );

Masalahnya adalah, apa pun yang berlangganan metode getItems, selalu menghasilkan nol. Ada item dalam database, jadi bahkan pada panggilan pertama, harus ada item yang dikembalikan. Ada, saat saya mengujinya dengan shareReplay(1) dan berhasil.

Bagaimana saya bisa berbagi menggunakan BehaviorSubject alih-alih ReplaySubject?

0
ScubaSteve 12 Mei 2021, 18:25

2 jawaban

Jawaban Terbaik

Untuk kode ini:

if (!this.getItems$) {                 
    this.getItems$ = this.httpClient 
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));
}
return this.getItems$;

Ini: this.httpClient.get() tidak sinkron. Jadi jika getItems$ belum disetel, itu belum disetel oleh pernyataan pengembalian.

Kode dieksekusi dalam urutan sebagai berikut:

  • Baris 1.
  • Jalur 2 & 3
  • Baris 5. (mengembalikan nol)
  • Baris 4. (sebenarnya memproses respons yang dikembalikan)

Juga, menambahkan if berlebihan dengan shareReplay yang secara otomatis akan mendapatkan data hanya jika Observable belum disetel.

Cobalah sesuatu seperti ini:

getItems$ = this.httpClient
            .get<Item[]>(url, options)
            .pipe(shareReplay(1));

EDIT: Di mana kode di atas adalah properti, bukan di dalam metode.

Untuk bagian kedua dari pertanyaan tentang membuat item baru, Anda dapat melakukan sesuatu seperti ini, yang berasal dari salah satu contoh saya. Anda dapat mengganti nama properti/antarmuka sesuai kebutuhan untuk skenario Anda.

  // Action Stream
  private productInsertedSubject = new Subject<Product>();
  productInsertedAction$ = this.productInsertedSubject.asObservable();

  // Merge the streams
  productsWithAdd$ = merge(
    this.productsWithCategory$,  // would be your getItems$
    this.productInsertedAction$
  )
    .pipe(
      scan((acc: Product[], value: Product) => [...acc, value]),
      catchError(err => {
        console.error(err);
        return throwError(err);
      })
    );

Anda dapat menemukan contoh lengkap dengan kode di atas di sini: https:// github.com/DeborahK/Angular-RxJS/tree/master/APM-Final

EDIT:

Untuk referensi, berikut adalah kode kunci dari stackblitz:

Layanan

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { merge, Observable, Subject, throwError } from 'rxjs';
import { catchError, scan, shareReplay, tap } from 'rxjs/operators';
import { Item } from './item.model';

@Injectable({
  providedIn: 'root'
})
export class ItemsService {
  url = 'https://jsonplaceholder.typicode.com/users';

  // This is a property
  // It executes the http request when subscribed to the first time
  // And emits the returned array of items.
  // All other times, it replays (and emits) the items due to the shareReplay.
  getItems$ = this.httpClient.get<Item[]>(this.url).pipe(
    tap(x => console.log("I'm only getting the data once", JSON.stringify(x))),
    tap(x => {
      // You can write any other code here, inside the tap
      // to perform any other operations
    }),
    shareReplay(1)
  );
  
    // Action Stream
  private itemInsertedSubject = new Subject<Item>();
  productInsertedAction$ = this.itemInsertedSubject.asObservable();

  // Merge the action stream that emits every time an item is added
  // with the data stream
  allItems$ = merge(
    this.getItems$,
    this.productInsertedAction$
  )
    .pipe(
      scan((acc: Item[], value: Item) => [...acc, value]),
      catchError(err => {
        console.error(err);
        return throwError(err);
      })
    );

  constructor(private httpClient: HttpClient) {}

  addItem(item) {
    this.itemInsertedSubject.next(item);
  }
}

Komponen

import { Component, OnInit } from '@angular/core';
import { Item } from './item.model';
import { ItemsService } from './items.service';

@Component({
  selector: 'app-item',
  templateUrl: './item.component.html'
})
export class ItemComponent {

  // Recommended technique using the async pipe in the template
  // With this technique, no ngOnInit is required.
  items$ = this.itemService.allItems$;

  constructor(private itemService: ItemsService) {}

  addItem() {
    this.itemService.addItem({id: 42, name: 'Deborah Kurata'})
  }
}

Templat

<h1>My Component</h1>
<button (click)="addItem()">Add Item</button>
<div *ngFor="let item of items$ | async">
  <div>{{item.name}}</div>
</div>
1
DeborahK 12 Mei 2021, 22:17

Untuk menjawab pertanyaan saya, saya dapat menemukan solusi.

Pertama, saya harus mendapatkan rxjs versi terbaru:

npm install rxjs@latest

Kemudian, saya membuat kelas shareBehavior:

export function shareBehavior<T>(
  behaviorSubject: BehaviorSubject<T>
): MonoTypeOperatorFunction<T> {
  return share<T>({
    connector: () => behaviorSubject,
    resetOnError: true,
    resetOnComplete: false,
    resetOnRefCountZero: false,
  });
}

Kemudian, dalam layanan:

private items = new BehaviorSubject<Item[]>([]);

...

if (!this.getItems$) {
  this.getItems$ = this.httpClient
  .get<Item[]>(url, options)
  .pipe(shareBehavior(this.items));
}
return this.getItems$;

Ini bekerja cukup baik. Namun, masalah dengan ini adalah karena perilaku memiliki nilai default, semua pelanggan dikirimi nilai awal []. Mereka diperbarui segera setelah panggilan API selesai. Tapi, saya lebih suka menggunakan ReplaySubject sehingga pelanggan hanya mendapatkan nilai ketika http mendapatkan pengembalian. Lakukan ini, lihat: https://stackoverflow.com/a/67508863/787958

0
ScubaSteve 12 Mei 2021, 23:24