Pierre
Pierre

Reputation: 630

Should Angular services' methods always return observables only?

I am new to observables and I'm having trouble understanding how to use them properly.

Say I have this service:

class MyService {
  query() {
    return from(myApiCall()).pipe(
      pluck('data'),
      mergeMap(apiResponse => {
        const obj = apiResponse.foo.map(el => {
          return {
            bar: el.bar,
            otherProp: el.baz,
            // how do I get the litteral value here and not an observable?
            builtProp: this.buildProp(el.myProp)
          }
        })
        
        return obj
    )
  }
​
  buildProp(value) {
    if(!value) {
      return throwError('value missing')
    }

    return of(`the value is: ${value}`)
  }
}

buildProp is a public method that I'd like to have the option to call from elsewhere outside the MyService class. There is nothing async about what it does, so it might as well return the value straightaway rather than an observable. But that might be inconsistent and annoying to use if some methods of MyService return observables and others don't.

I thought of having a private method that returns the plain string to use when building the object, and another public method that returns of(buildProp(val)), but that feels clunky (nevermind the difficulty of finding good names for these two separate methods now).

Should any public method on an Angular service always return an observable, even if there is nothing async about what that method does?

And in that particular case, how do I get the literal string rather than an observable for builtProp in the returned object in an elegant and idiomatic Angular way?

Upvotes: 0

Views: 1566

Answers (1)

Picci
Picci

Reputation: 17762

The way I like to think of services in Angular is that they are the place where to put any kind of business logic, from complex async stuff to simple sync transformations of data.

Why put the logic in a service? One good reason is that this make the code much simpler to test and forces you to keep in Components only what is strictly related to visualization and managing user interactions.

Having said that, there is no reason to think that services must return Observables. They must return what makes sense to return: Observables in case of async logic, anything else appropriate in other sync cases.

Here some thinking around your specific case.

First of all I question why to use mergeMap? You should probably be able to achieve the same result using map, which is an operator that performs a transformation on the data emitted by the source Observable. In other words I expect this logic, which uses map and does not require buildProp to return an Observable, to work.

class MyService {
  query() {
    return from(myApiCall()).pipe(
      pluck('data'),
      map(apiResponse => {
        const obj = apiResponse.foo.map(el => {
          return {
            bar: el.bar,
            otherProp: el.baz,
            builtProp: this.buildProp(el.myProp)
          }
        })
        
        return obj
    )
  }
​
  buildProp(value) {
    if(!value) {
      return throwError('value missing')
    }

    return `the value is: ${value}`
  }
}

The second point is about when to use mergeMap and other similar operators such as switchMap, exaustMap, concatMap, which are all operators which accept as input functions returning another Observables.

Such operators are use to "flatten" the inner Observables and create eventually an Observable that emits the data emitted by the inner Observable. It is maybe easier to explain this concept using an example. Consider you have to call a first Rest API (async call over http) and use the data returned as input to call a second Rest API. To manage this case you should code something like this

firstApi().pipe(
  concatMap(firstResult => secondApi(firstResult)
).subscribe(
  // secondResult is the result returned by the invocation of secondApi
  secondResult => {// do stuff with secondResult}
)

In this case, if you use map rather than concatMap (which is somehow similar to your mergeMap) you would see that an Observable is emitted instead of the result of secondApi, which is not what you want.

If you want to read some more details about typical patterns of use of RxJs in http scenarios, you can look at this article.

Upvotes: 2

Related Questions