How to organize Angular data service

2020-08-01 12:09发布

问题:

I have become slightly confused over Observables, Observers, Subjects, BehaviorSubjects, Services, injections, etc.

I am trying to make a simple finance app. I have a transactions service that grabs the data from a REST server and then provides it to other services that can update it if they need to. I can get the list transactions component (which gets its data from getTransactions())to update with the service, but I don't know how to get the edit component (which gets its data from "getTransaction(id)" to work.

What is the best way to let components play with data from the entire array (read/write access) as well as those that just want to play with a single element of the array?

Here's what I have right now: (I've changed it a ton so it's currently broken, but you can see what I was doing)

export class DataBrokerService {
  public transactions:Transaction[];

  constructor() {}

   ngOnInit(){
   }

  getTransactions(): Observable<Transaction[]>{
    if (needUpdate){
      // Download from REST API, subscribe result to transactions
      this.updateTransactions();
    }
    return of(this.transactions);
  }

  getTransaction(id:number): Observable <Transaction>{
    let transaction:Transaction;
    this.getTransactions().subscribe(txns => transaction = txns.find(txn => txn.id === id))
    });
    return transaction$;
  }

I also made this demo showcasing what I am going for: https://stackblitz.com/edit/angular-stackblitz-demo-dftmn3

This is a followup question from: Get one value from observable array

回答1:

It's a good question. I know it's kind of confusing to work with RxJs at first but when you get the hang of it, it's quite powerful.

What you want can be achieved with BehaviorSubject.

Let's find out what Subject is and move onto BehaviorSubject

Subject: is the equivalent to an EventEmitter, and the only way of multicasting a value or event to multiple Observers

For more info, take a look at here

So, we know that Subject is like an EventEmitter. When someone subscribes to it, whenever you call subject.next(value), every subscriber will get the new value. However, late subscribers (meaning subscribing to the Subject after next method is called) won't get the previous values. This is where BehaviorSubject will come into the scene.

It is just like Subject, but when a new subscriber subscribes to it, it'll emit the previous value. Let's use it in your code.

export class DataBrokerService {
    // do not expose your subject to other classes
    private _transactions$: BehaviorSubject<Transaction[]> = new BehaviorSubject([]);
    // instead provide it as an observable
    public readonly transactions: Observable<Transaction[]> = this._transactions$.asObservable();

    needUpdate = true;

    constructor(private http: HttpClient) { }

    ngOnInit() {
    }

    // just return the observable
    getTransactions(): Observable<Transaction[]> {
        if (needUpdate) {
            this.updateTransactions();
        }
        return this.transactions;
    }

    // you can iterate over your transactions and filter them,
    // but DO NOT ever subscribe to it within a service. 
    // Otherwise, other classes won't be able to retrieve the value.
    getTransaction(id: number): Observable<Transaction> {
        return this.getTransactions().pipe(
            map(txns => txns.find(txn => txn.id === id))
        );
    }

    // when you need to update the transactions,
    // simply call this method
    updateTransactions() {
        this.http.get('transactions').subscribe(txns => {
            this.needUpdate = false;
            this._transactions$.next(txns);
        });
    }      
}