1

A technique of getting data from a an API is to declare it like this in the service:

getItems$ = this.httpClient.get<Item[]>(this.url);

then either subscribe to it or utilize async in the consuming component.

How would you do this if the get call requires parameters like get by id?

EDIT: The above is an example of a declarative approach to working with RxJS streams. The getItems$ is a property.

So the question is, when using a declarative approach with a property defining the stream instead of calling a getItems(itemId) method, how to you pass in parameters?

DeborahK
  • 57,520
  • 12
  • 104
  • 129
ScubaSteve
  • 7,724
  • 8
  • 52
  • 65

3 Answers3

2

To handle any "parameters", you create another action stream using Subject or BehaviorSubject. The "parameter" is then emitted into the stream.

Here is an example from one of my applications.

  // Handle product selection action
  private productSelectedSubject = new BehaviorSubject<number>(0);
  productSelectedAction$ = this.productSelectedSubject.asObservable();

  product$ = this.productSelectedAction$
    .pipe(
      filter(id => !!id),
      switchMap(selectedProductId =>
        this.http.get<Product>(`${this.productsUrl}/${selectedProductId}`)
      ));

  // Selected product was changed
  changeSelectedProduct(selectedProductId: number): void {
    this.productSelectedSubject.next(selectedProductId);
  }

Here we create a BehaviorSubject with an initial value of 0. (You could instead use a Subject with no initial value.)

When the user picks a product (or however the specific product is determined) the id of that product is emitted into the productSelectedSubject

This code then uses a pipeline to react every time there is a new product Id emitted into the stream. The first operator in the pipeline filters out any invalid Ids. The switchMap then uses the emitted product Id to issue the http get.

I have a complete example of this that also sets up additional "parameters" for pagination here: https://github.com/DeborahK/Angular-ActionStreams

DeborahK
  • 57,520
  • 12
  • 104
  • 129
2

I like your question. I too always try to define my observables in a declarative way. However, when it comes to methods that take parameters (especially in Angular services), I tend to just use a just use functions that return observables:

class ItemService {
  public getItem(id: string) {
    return this.http.get<Item>(`${this.apiRoot}/items/${id}`);  
  }
}

If the service itself is responsible for managing the state of these "parameters", then it makes sense to use subjects like @DeborahK has mentioned, to provide a facility for consumers to pass in values.

However, if the component is responsible for these parameters, then it's not usually necessary to have Subjects because those param values usually come from other observable sources like reactive form controls or route params.

For example:

class ItemComponent {
  private id$ = this.route.paramMap.pipe(params => params.get('id'));

  public item$ = this.id$.pipe(
    switchMap(id => this.itemService.getItem(id))
  );
  
  constructor(
    private route: ActivatedRoute,  
    private itemService: ItemService
  ) { }
}

Notice in the component, item$ is still defined in that declarative way that you like. Also notice, I do what @DeborahK mentioned and define it from another stream (the other stream just happens to be a different observable, instead of a subject).

BizzyBob
  • 12,309
  • 4
  • 27
  • 51
0

That depends on the http api you are requesting to. As a get requests does not have a body the parameters are usually added to the url like so: http://example.com/customer?id=25

The get, post, put etc. methods of the HttpClient have a second options parameter, which can have a params property of the type HttpParams where you can set the parameters data$ = this.http.get<item[]>(this.url,{params: new HttpParams().set('id',25)}) `

This will handle the adding to the actual requested url.

David B.
  • 695
  • 5
  • 10