Building a Reactive Storage Service in Angular

Christophe Ramsamy
6 min readJan 8, 2022

--

During our Angular training at Techsphere Labs, one of the topics we cover is how to create a service to interface with the browser’s localStorage in a simple and consistent way. “But”, I hear you ask, “local storage is straightforward enough to use! Why in the heavens would we need a service?

And I will fully agree! Using local storage is indeed simple and straightforward, true. There are however a few reasons why we have chosen our current approach:

  • Firstly, it allows us to avoid repeating actions such as stringifying / parsing our json objects to and from the storage. We can do it once in the service and forego that (albeit small) cognitive overload;
  • As will be shown a little later in this article, using a service allows us to use the local storage in a much more reactive manner with the help of RxJs
  • Services are awesome

So, how do we do this? Let’s see! So first of all, you will find below the code or our basic storage service.

Right away you will notice that this is a pretty straightforward and minimal service. There are just 3 methods here: set, get and remove. Let’s break it down.

The Set method

This method takes 2 parameters. A key of type string representing our localstorage key, and a value, which could be of any type since we are not limiting what can be stored in the storage.

So as mentioned previously, we begin by using JSON.stringify() on the value in case it is an object / array, and then store it, and we’re done with the set method :)

The Get method

This method is a little simpler. We only take the key as a parameter, retrieve the value from storage, and parse it before returning it to the caller.

The Remove method

Remove is the simplest method of the three. Again we take the key as a parameter, and just pass it to localStorage.removeItem(). This will delete the key from our local storage.

And here we have it! A simple service allowing us to store / retrieve data from local storage. We could use it as is, but we could also go one step further and make it a bit more reactive. After all, RxJs is one of the major part of the frameworks, so we can take advantage of that :)

So, first, we will import Observable and BehaviorSubject from rxjs.

We will then proceed by creating a map that will contain behavior subjects for each of the values stored in our storage. “But hold on, Map? Behavior Subjects? What are those?

Well to put it simply, a map is an object that will hold key/value pairs. You can read more about them on MDN.

As for Behavior Subjects, they are a special type of subject in rxjs. Their main difference with normal subjects / observables is that upon subscription they will always push the a value (either the last value or a default one if no value was emitted) to the subscribers.

So now we will be creating our map, where we will be matching the keys from our local storage to behavior subjects.

Our storage service now looks like this:

So far so good! Now we will need a method that other parts of our application will call to subscribe to one of the behavior subjects in our map. Let’s call this method watch, and have it accept the key to watch as an parameter.

In this method, we want to provide the caller with an observable they can use to receive updates whenever a specific key (the one passed in as an argument) is updated in the storage.

First, we are checking if there is already a BehaviorSubject for that key in our map. If not we need to create it.

After that, we are checking if this key actually has a value inside the browser localStorageobject. The item variable will contain the value in storage if there actually is one, otherwise it will contain “undefined”.

And finally, we are taking the BehaviorSubject for the key from our map, (whether it already existed before or was just created above), and we will push an update with the value of our item using the next() method. We can then return the subject as a normal observable, that can be subscribed to in our component (or anywhere else we want to call the watch method from.)

Perfect! Now we have a list of BehaviorSubject that can be subscribed to in order to receive updates when values in our local storage change. However, we now need to change some of our methods so that they can now update our map when there are updates in our localstorage.

Since the get() method is only reading from the storage, we do not need to add anything there as we don’t need to update the map on read operations.

The set() and remove() methods on the other hand are actually changing data in the storage, and should as such interact with our map when items are either added, modified or deleted. Let’s begin with the set() method.

The first two lines of the set method do not change at all, we are still usingstringify() on the value before saving it to our storage. Things become more interesting after that though. We need to check whether a behavior subject already exists for that key. If it doesn’t we are creating it, with an initial value of whatever we just put in our storage.

If it does exist though, we will just use the subject as normal, and push the new value using the next() method, so any subscribers to that specific key will get an update;

That’s it for our set() method. Now let’s look at the remove().

Once again, we are checking if the observable already exists, and if it doesn’t we create it with an initial value of null. If it does exist however, we will push a new value of null so that the subscribers get the update.

So now we actually have a service that allows us to watch specific keys in the browser’s localStorage, and react accordingly when certain data change, brilliant! If this is all we need then it is perfect, and we could stop here and call it a day.

However, as we learned during our experimenting with this technique, there is one very specific use case that we haven’t taken into account yet. In its current implementation, we need to interact with the service for the behavior subjects to be created. Meaning that, upon page load / refresh, our map is empty until we actually watch the storage or set something in there. We could make that work, but it would be nice if we could implement an automatic way of populating our BehaviorSubject map for all the data our app stores in the storage on first load.

As luck would have it, we can! So first, let’s add an init() method in our service.

The init() method is pretty straightforward. We are just checking all the keys that exist in our localStorage, and creating a BehaviorSubject for each of them in our map. Please note that using this method, ALL the data that our app has stored will be available in the map. We can also change the logic here in order to automatically only watch certain specific keys.

So here is our completed storage.service.ts.

We now have our full service, including our init() method, great! However, we still need to call it somewhere. In our specific case, we want our map of subjects to be an accurate representation of our storage from the moment our app starts. As such we will run our service method in the ngOnInit() method of our base app component.

After injecting the service and calling the init method, we now have a complete map of subjects corresponding to our localStorage object. Our storage service is now complete!

We can now inject and use the service anywhere we need access to our localstorage, using the watch method to listen to any subsequent updates to the storage.

In our follow-up article, we will look at an example of how to use the service we just created to access our localstorage reactively.

You can also find a live demo here.

We’d be delighted to hear any comments or questions from you, and we hope you learned a few things from this article :)

--

--