Encapsulating LocalStorage Access In AngularJS
From a practical standpoint, encapsulating the localStorage API will shield your application against inconsistent support for localStorage. Granted, all modern browsers now support the localStorage API; however, it clearly isn't available in Safari under all circumstances. So, encapsulating the localStorage implementation means that your consuming components don't have to worry about such inconsistencies.
Aside from this Safari edge-case, I believe that encapsulating the localStorage API has other benefits. For starters, localStorage only deals with simple values. So, if you want to store an Object, you have to serialize it first. Conversely, if you want to retrieve a value, you have deserialize it before you use it. This is frustrating. Encapsulating the localStorage API means that you can provide an easier-to-consume interface that deals with values in their natural state.
Another benefit of encapsulation is that you can manage the lifecycle around the use of the localStorage cache. In addition to requiring serialized data, I have also been told (but have not personally tested) that localStorage is relatively slow. So, by encapsulating it, you can minimize the points-of-interaction during the application lifecycle. For example, you have the option to defer persistence until the application unloads.
Finally, by encapsulating the localStorage API, you will be implicitly creating an injectable object. And, any object - once injectable in an AngularJS application - is swappable, which means that integration testing, in relation to such an object, becomes easier.
To experiment with this in an AngularJS context, I've created a simple application which allows me to curate a list of friends. The friend data is persisted to the localStorage which means that it will be available across page reloads. The data will be managed through the Data Access Object (DAO), friendService. This DAO requires the "storage" service, which will encapsulate the localStorage:
Controller -> friendService -> storage -> localStorage.
The storage service provides an in-memory cache that only deals with localStorage at two points in the application lifecycle:
This requires more active memory consumption; but, memory is cheap. And it means that storage interactions, during the life of the application, are very fast.
CAUTION: The underlying assumption in the last statement is that copying objects in memory is faster that pushing them into localStorage, pulling them out of localStorage and, then, deserializing them. That said, this performance assumption is based primarily on hearsay and intuition, not measurements.
As you may have noticed, the storage service provides registration for a "before persist" event. This event hook allows other services to interact the storage system at the end of the application lifecycle (which is triggered by the window's beforeunload event). This is helpful because the other services, like friendService, may have their own internal cache and don't need to interact with the storage system at all times. The "beforeuplaod" event allows the consuming services to push data into the storage system before the storage system persists to disk.
I like the persist-event idea because it still hides the localStorage implementation. But, it gives the consuming services a bit more flexibility in how they interact with storage. And, there's nothing to say that you can't have multiple persist events during the life time of the app - I just happend to have a single event in my demo.
Anyway, just some thoughts on why it might be a good idea to encapsulate the localStorage API. If nothing else, it just means that your AngularJS application won't break when people try to use it in Safari's Private mode (for those of you that still use Safari).
Want to use code from this post? Check out the license.
I like the idea of encapsulating the logic of LS, however I dont see the need to clear out LS on page load and rewrite again onUnload. If for some reason your browser crashes (or a new window with same page is opened) the data is gone.
I think persisting should be done at interaction-time (or close to). There is a well-supported storage-event that you could listen for in your service - which should trigger a re-read from LS.
What are your thoughts on going that route? Of cause there is a possible performance-problem since LS is sync - not worth it?
Part of it is probably just a little "premature optimization." Writing to localStorage requires serialization which seems like unnecessarily slow work to do during a user-interaction. Of course, we're talking about computers that do like billions of operations a second :D So, a little serialization probably doesn't hurt.
I'm not sure if I can come up an answer that doesn't just feel "emotional". That said, I do like the idea that this makes it easier to disable the flushing of data to the localStorage API if you don't want it to, such as with a "logout" request.
But, ultimately, it probably won't make much difference one way or the other.
Great article . So Local storage encapsulation (storage API) provides in memory access to data .
I have extracted the try-catch block at the end of the persistData() function into its own flush() function, and I exported it. Thus, I can update the localStorage when I need to, while also leaving the current flow intact.
I am not able to see the localStorage being set in the browser,tried displaying them using window.localStorage in firebug,why is that?