In this article I will present how to reuse a common logic in an Angular application. I will examine a typical live search case.
At least it works!
The easiest way to accomplish the goal (implement a live search) is to provide the necessary code within a component’s class, namely:
with a simple template:
In the above example, the flow is the following:
- an input event is triggered,
- the input’s value is pushed into the carsQuerySubject,
- the subjects acts as the source stream for the cars$ observable emitting a matching cars array,
- the cars$ stream is created with the aid of standard operators used for the live search case (debounceTime, distinctUntilChanged, switchMap).
The carsRemoteService is responsible only for fetching cars data from the backend (here mocked with the aid of of creational function):
The above solution works, however there is a room for improvement. Firstly, the component’s class contains some non-trivial logic, hence you can argue that the separation of concerns rule does not apply. Secondly, if you want to write a unit test for the live search behavior (e.g. check if debouncing works as expected), you need to create a component’s instance. You cannot test this piece of code standalone (imagine that the component may have many dependencies which will have to be mocked). Last but not least, if some other component needs to have the same functionality, you will have to copy-paste the appropriate code 😞
Service is the key!
The good news is that you can do better! 😃 The key is to extract the live search logic into a service:
The difference is that the live search logic is contained in the service and the component simply passes the current query to the service:
Now, the component’s class is much cleaner and you can write unit tests for the service which covers the live search behavior. Moreover, you can access filtered cars stream from any other component — no need to copy-paste 😃
However, there is a small gotcha, namely there is only a single instance of the service by default (if you create the service using Angular CLI, it is registered at the root level). One solution is to simply register the service at the component level, so that the exposed cars$ stream is created per each component. Alternatively, you can keep the singleton and refactor the service so that it has the stream factory function:
With the above service you need to call the factory function and pass a subject acting as a source observable:
The component owns the subject, however the live search logic is still kept in the service.
Custom operators to the rescue!
Chances are that in order to accomplish a given goal you rely on the RxJS library as in the example. In addition, the live search case is quite popular and you can have many places in your application where you want to search different entities (cars, dogs, cats). The solutions from the previous paragraph work fine but they directly rely on a given data endpoint. It would be nice to somehow share the live search logic between all the services which need such feature. Good news is that you can create a custom, configurable RxJS operator:
and simply use it in any service:
It also makes it super simple to write unit tests for the live search logic, since you can easily define a source stream emissions with the aid of jasmine marbles.
It’s always desirable to write a code that can be easily shared between different services in your application or even between several projects (as with the search operator). It’s not only about writing the code once, but more importantly it’s testing that matters. You only need to test it once and if you have done it properly 😄 you can simply reuse it with no worries. Remember that making it work is not the last step of the development process. If there is a room for improvement, definitely go for it!
I hope you liked the post and learned something new 👍 If so, please give me some applause 👏