Updated: Jun 17, 2018
Running code in a client-side environment is tricky. Some APIs are available at runtime, but other APIs are not supported. Whether APIs are available depends on the browser, but also on the browser’s configuration, like if it’s in private mode or has a blocking tools extension installed. As front end developers, we need to develop web apps to work in any environment. This blog post is about that gray area and how we handled it by implementing two services that use the same APIs but do things differently.
When we started to develop our WixStores app—an eCommerce platform—we had to decide how we would share data between our components in the client browser. We chose local storage because when a user adds a product to the cart, the data is saved, and only when the user completes the transaction is the data cleared from the cart.
Initially, everything worked like a charm, but then we hit a snag. We needed our app to work when users blocked local storage, which made the localStorage APIs unavailable. There were many ways this could happen—for example, using Safari private mode, disabling cookies and site data in the browser settings, etc. We needed to implement a different way to share our data when localStorage APIs were not available, and we also needed to disable the data consistency—after all, the user asked for some privacy.
To workaround this issue, we decided that the best solution was to use an in-memory database. When localStorage APIs were available, we’d use LocalStorageService; however, when those APIs were missing, we’d use the in-memory service.
Let’s meet the key players:
LocalStorageService – Wraps the native localStorage API
StorageService – Uses LocalStorageService to store the data
DataService – Does some manipulation on the data before sending it to StorageService
When we started implementing the solution, the first thing that came to mind was to add a public method to LocalStorageService that would check whether the localStorage API was available:
Happy with the new implementation, we turned to StorageService and started implementing the event base mechanism—until we realized it was repeating the same condition:
There must be a better way to write this code, right? Yes, there is. It’s called Angular factory.
So what is Angular factory? It’s a function with zero or more dependencies that returns the injectable value. We can use the factory to customize the service before returning it.
If we implement the service factory as we want, we can provide the right service to the right flow (with or without localStorage APIs). Angular also makes sure that the returned service is a singleton throughout our code by saving the reference of the generated service and providing it in case some other component will need its API or data.
So instead of one StorageService, we implemented two services:
PersistentStorageService – The same implementation as before, just renamed
InMemoryStorageService – Works with an in-memory array
They both have the same public methods and use the same unit tests. We registered the services in Angular as private services using ‘__’ (double underscore) annotation:
Factory – #ItsThatEasy
P.S. If you’re using some code in the constructor function of the service and don’t want the code to run when you inject the service, you can use $injector: