Skip to main content

Angular Caching Strategies

Introduction

Caching is a fundamental technique for creating high-performing Progressive Web Applications (PWAs) in Angular. An effective caching strategy can significantly improve your application's load time, reduce server requests, and enable offline functionality. In this guide, we'll explore various caching strategies available in Angular's service worker implementation and learn how to implement them in your PWA.

What is Caching?

Caching is the process of storing copies of resources (like HTML, CSS, JavaScript, images) locally so they can be accessed quickly without making repeated network requests. In Angular PWAs, caching is primarily managed through the Angular service worker, which intercepts network requests and applies the appropriate caching strategy.

Why Caching Matters in PWAs

  • Improved performance: Cached resources load faster than those fetched from the network
  • Reduced server load: Fewer requests to your servers means lower costs and better scalability
  • Offline capabilities: Users can still access your application without an internet connection
  • Enhanced user experience: Quicker load times and offline functionality create a more app-like experience

Setting up Angular Service Worker

Before implementing caching strategies, you need to add the Angular service worker to your project:

bash
ng add @angular/pwa

This command:

  1. Adds the @angular/service-worker package
  2. Enables service worker build support in Angular CLI
  3. Imports and registers the service worker in the app module
  4. Creates a default ngsw-config.json configuration file
  5. Creates icons for the application
  6. Creates a web app manifest file

Angular's Built-in Caching Strategies

Angular's service worker provides several built-in caching strategies that you can configure in the ngsw-config.json file. Let's explore each one:

1. Performance Strategy (Cache-First)

The performance strategy prioritizes speed by serving content from the cache first, falling back to the network if the resource isn't in the cache.

json
{
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
}
]
}

When to use: Best for static assets that don't change frequently, like application bundles, CSS, and images.

2. Freshness Strategy (Network-First)

The freshness strategy prioritizes up-to-date content by trying the network first, then falling back to the cache if the network request fails.

json
{
"dataGroups": [
{
"name": "api-freshness",
"urls": ["/api/latest-data"],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 100,
"maxAge": "1h",
"timeout": "5s"
}
}
]
}

When to use: Best for API calls and dynamic content that changes frequently.

3. Performance with Refresh (Cache-First with Network Update)

This hybrid strategy serves content from the cache first for speed, but then checks the network for updates in the background.

json
{
"assetGroups": [
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|jpg|png|gif|json)"
]
}
}
]
}

When to use: Good for semi-dynamic content where immediate freshness isn't critical, but updates should be applied when available.

Advanced Configuration Options

Max Age and Max Size

You can control how long resources stay in the cache and how many resources to cache:

json
{
"dataGroups": [
{
"name": "api-performance",
"urls": ["/api/product-catalog"],
"cacheConfig": {
"strategy": "performance",
"maxSize": 100,
"maxAge": "1d",
"timeout": "3s"
}
}
]
}
  • maxSize: Maximum number of responses to cache
  • maxAge: How long to cache responses (e.g., "1d" = one day)
  • timeout: How long to wait for the network before using a cached response

Cache Busting with versionedFiles

To ensure users always get the latest version of your resources after an update:

json
{
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/index.html",
"/favicon.ico"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js"
]
}
}
]
}

Real-world Example: Building a News Application with Proper Caching

Let's implement a simple news application with appropriate caching strategies for different types of content:

Step 1: Define the Service Worker Configuration

json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|jpg|png)"
]
}
}
],
"dataGroups": [
{
"name": "news-headlines",
"urls": ["/api/headlines"],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 100,
"maxAge": "1h",
"timeout": "5s"
}
},
{
"name": "news-categories",
"urls": ["/api/categories"],
"cacheConfig": {
"strategy": "performance",
"maxSize": 20,
"maxAge": "7d"
}
}
]
}

Step 2: Create a Service to Interact with the API

typescript
// news.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class NewsService {
constructor(private http: HttpClient) {}

getHeadlines(): Observable<any[]> {
return this.http.get<any[]>('/api/headlines');
}

getCategories(): Observable<string[]> {
return this.http.get<string[]>('/api/categories');
}
}

Step 3: Update App Component to Display News

typescript
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { NewsService } from './news.service';

@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>Latest News</h1>

<div class="categories">
<h3>Categories</h3>
<div class="category-list">
<button *ngFor="let category of categories" (click)="selectCategory(category)">
{{ category }}
</button>
</div>
</div>

<div class="headlines">
<h3>Headlines</h3>
<div *ngIf="headlines.length === 0 && !offlineMode">Loading...</div>
<div *ngIf="headlines.length === 0 && offlineMode" class="offline-message">
You are offline. Cannot fetch the latest headlines.
</div>
<div class="news-item" *ngFor="let item of headlines">
<h4>{{ item.title }}</h4>
<p>{{ item.summary }}</p>
</div>
</div>
</div>
`
})
export class AppComponent implements OnInit {
headlines: any[] = [];
categories: string[] = [];
offlineMode = !navigator.onLine;

constructor(private newsService: NewsService) {
window.addEventListener('online', () => {
this.offlineMode = false;
this.loadHeadlines();
});

window.addEventListener('offline', () => {
this.offlineMode = true;
});
}

ngOnInit() {
this.loadCategories();
this.loadHeadlines();
}

loadHeadlines() {
this.newsService.getHeadlines().subscribe(
data => this.headlines = data,
error => console.error('Could not fetch headlines', error)
);
}

loadCategories() {
this.newsService.getCategories().subscribe(
data => this.categories = data,
error => console.error('Could not fetch categories', error)
);
}

selectCategory(category: string) {
// Implementation for filtering news by category
}
}

Step 4: Check for Service Worker Updates

It's important to inform users when a new version of your app is available:

typescript
// app.component.ts (additional code)
import { SwUpdate } from '@angular/service-worker';

// Inside the AppComponent class:
constructor(private newsService: NewsService, private swUpdate: SwUpdate) {
// Previous code...

if (this.swUpdate.isEnabled) {
this.swUpdate.available.subscribe(() => {
if (confirm('New version available. Load new version?')) {
window.location.reload();
}
});
}
}

Testing Caching Strategies

To test your caching strategies:

  1. Build your application in production mode:

    bash
    ng build --prod
  2. Serve the built application:

    bash
    npx http-server -p 8080 -c-1 dist/your-app-name
  3. Test offline functionality:

    • Open the application in Chrome
    • Open Chrome DevTools (F12)
    • Go to the Network tab
    • Check "Offline" to simulate offline mode
    • Reload the page and verify which resources are still available

Common Caching Pitfalls and Best Practices

Pitfalls to Avoid

  1. Over-caching dynamic content: Be careful about caching API responses that change frequently
  2. Under-caching static assets: Not caching static files wastes bandwidth and slows your application
  3. Not handling offline states: Always provide feedback when users are offline and data can't be fetched
  4. Ignoring cache versioning: Without proper versioning, users might get stuck with outdated assets

Best Practices

  1. Match strategies to content types: Use performance strategy for static content and freshness for dynamic data
  2. Set appropriate cache durations: Consider how often your data changes
  3. Implement update notifications: Let users know when a new version is available
  4. Test thoroughly in offline mode: Make sure your app degrades gracefully
  5. Handle failed requests gracefully: Display user-friendly messages when resources can't be loaded

Summary

Angular's service worker provides powerful caching capabilities that enable you to build responsive, offline-capable Progressive Web Applications. By choosing the appropriate caching strategies for different types of content, you can optimize both performance and freshness.

The key points to remember:

  • Use performance strategy (cache-first) for static assets
  • Use freshness strategy (network-first) for dynamic data
  • Configure maxAge and maxSize to control cache behavior
  • Implement update notifications to keep users on the latest version
  • Always provide feedback for offline states

By implementing these caching strategies, you'll create PWAs that load quickly, work offline, and provide a better user experience overall.

Additional Resources

Exercises

  1. Implement a weather app that caches the forecast for 3 hours but checks for updates in the background.
  2. Create a blog application that uses different caching strategies for the article list (freshness) and article content (performance).
  3. Modify the news application example to include a "Force Refresh" button that bypasses the cache and always fetches the latest data.
  4. Implement a detailed offline page that shows which features are available offline and which require a connection.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)