Last week I wrote an article about how I handle client-side caching with Angular:
Article No Longer Available
But let's say we just released the first version of out app and we're retrieving a list of products in the home page. Currently, our products have the following properties:
- name
- description
- price
So, our cached query results look like this:
[
{
"name": "product 1",
"description": "description for product 1",
"price": 100
},
{
"name": "product 2",
"description": "description for product 2",
"price": 150
},
{
"name": "product 3",
"description": "description for product 3",
"price": 200
}
]
Now, let's say we realised that we were missing a required property called "available" (it's a boolean).
We update our angular component to include the new property (I'm assuming that our API was updated too and it's retrieving the new property as well).
Finally, we publish the new version of our app.
Problem
One common problem we could face when working with cached data is that some of our clients will still have the old version of the products query being retrieved from localStorage. This could lead to unexpected errors because we're assuming that the new property will always be available (as it's required).
Solution
In this article I'm gonna share my approach to cleanup the localStorage every time I release a new version of my angular apps. In that way, my clients will always get a valid version of my queries without loosing our cache capabilities.
This solution have 3 steps:
1 - Create a list of cached queries we want to clean after each release
2 - Check if our user has an older version of our app
3 - Go through each cached query (using the list created in the first step above) and remove it from localStorage.
All this steps will be handled by our brand new System Service:
import { Injectable } from '@angular/core'
import { CacheService } from './cache.service'
import { environment } from 'src/environments/environment'
@Injectable()
export class SystemService {
// List of cached queries that'll removed from localStorage after each new release
cachedQueries = {
PRODUCT_LIST: `${environment.API_DOMAIN}/product`,
CATEGORY_LIST: `${environment.API_DOMAIN}/category`,
}
versionCookie = "[AppName]-version"
constructor(
private _cacheService: CacheService
) { }
checkVersion() {
if (this.userHasOlderVersion()) {
// Set new version
this._cacheService.save({ key: this.versionCookie, data: environment.VERSION })
// Cleanup cached queries to avoid inconsistencies
this._cacheService.cleanCachedQueries(this.cachedQueries)
}
}
userHasOlderVersion(): boolean {
const userVersion = this._cacheService.load({ key: this.versionCookie })
if (userVersion === null) {
return true
}
return userVersion !== environment.VERSION
}
}
As you can see, I'm using the Cache service I created in my last article. But I'm also adding a new method called cleanCachedQueries:
import { Injectable } from '@angular/core'
@Injectable()
export class CacheService {
constructor() { }
// If you need the full version of this service, please checkout my previous article.
cleanCachedQueries(queries: Object) {
queries = Object.values(queries)
for (const query of queries) {
localStorage.removeItem(query)
}
}
}
One more thing to notice is that I'm getting the version of my app from my environment file:
// environment.ts
import { version } from '../../package.json'
export const environment = {
production: false,
API_DOMAIN: 'https://example.com/api',
VERSION: version
}
Important
As you can see, I'm getting the current version of my app from the package.json
file. So it's important that you remember to update your app version before each new release.
We'll also need to add the new typescript compiler option called resolveJsonModule in our tsconfig.app.json
file to be able to read our package.json
file to get the version of our app:
"compilerOptions": {
"resolveJsonModule": true
}
Checking the app version
Last but not least, we'll add just one line of code in our app.component.ts to check the app version and remove our old cached queries:
import { Component, OnInit } from '@angular/core'
import { SystemService } from './services/system.service'
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
title = 'Your App'
showNavbar = true
constructor(
private _systemService: SystemService,
) { }
ngOnInit(): void {
this._systemService.checkVersion()
}
}
That's it. Now, every time you release a new version of your app, you'll only have to remember to update your app version in the package.json
file and keep you cachedQueries list up to date. The System service will take care of the rest.
Top comments (2)
Have you considered this scenario?
This way, you have three separate releases instead of one, but the technique can be applied to many kinds of breaking change.
I'm not sure I'm understanding your approach. When you talk about having a temporal handling do you mean adding a temporal logic in the components to ask if the value is present?