Caching  Strategy
Angular · ASP.NET Core · AWS
Frontend
API
Edge
Database
Multi-Layer Architecture

A Complete Caching Strategy
for Angular on AWS

Minimize latency and database load across every layer of the request lifecycle — from the browser edge to SQL Server — using a coordinated multi-layer caching approach.

Layer 1
Angular + RxJS
Zero network latency for repeat UI views
Layer 2
CloudFront
Serve data closer to the user at the edge
Layer 3
API Gateway
Bypass backend processing entirely
Layer 4
ElastiCache
Reduce heavy SQL Server query load
01
Distributed SQL Caching
Amazon ElastiCache
For high-performance data retrieval from SQL Server, use a distributed cache to store frequently accessed query results. Amazon ElastiCache (Redis or Memcached) is the primary managed AWS service for this layer.
AWS Service
🔧
API Implementation
In your ASP.NET Core API, use Microsoft.Extensions.Caching.StackExchangeRedis to connect to ElastiCache. Alternatively, use Microsoft.Extensions.Caching.SqlServer for a purely SQL-backed distributed cache.
.NET Package
Program.cs — ElastiCache Setup
C# · Program.cs
// Register Redis distributed cache in Program.cs
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-elasticache-endpoint:6379";
    options.InstanceName  = "MyApp:";
});

// Inject IDistributedCache in your service / controller
public class ProductService(IDistributedCache cache, AppDbContext db)
{
    public async Task<Product> GetByIdAsync(int id)
    {
        var key  = $"product:{id}";
        var json = await cache.GetStringAsync(key);

        if (json is not null)
            return JsonSerializer.Deserialize<Product>(json)!;

        var product = await db.Products.FindAsync(id);
        await cache.SetStringAsync(key, JsonSerializer.Serialize(product),
            new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });

        return product!;
    }
}
02
Response Caching
🌐
AWS API Gateway Caching
Reduce server processing by caching the entire HTTP response at the API Gateway level. Enable API Caching in the AWS Console to store responses for a configurable Time-to-Live (TTL), bypassing the backend entirely on cache hits.
AWS API Gateway
📦
[ResponseCache] Attribute
Use the [ResponseCache] attribute on controller actions to emit HTTP cache headers. This instructs clients and proxies when and how to cache the response data — setting Duration, Location, and VaryByQueryKeys.
Web API Middleware
Controller — [ResponseCache] Usage
C# · Controller
// Cache this response for 60 seconds, vary by query string
[HttpGet]
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any,
               VaryByQueryKeys = new[] { "category", "page" })]
public async Task<IActionResult> GetProducts(string? category, int page = 1)
{
    var products = await _service.GetProductsAsync(category, page);
    return Ok(products);
}
03
Angular RxJS & Interceptor Caching
🔄
shareReplay — Service-Level Caching
The most effective way to cache a single observable response in an Angular service is RxJS shareReplay(1). It multicasts the HTTP request and replays the last emitted value to any late subscriber — preventing duplicate network calls for the same resource.
TypeScript · Angular Service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class DataService {
  private cache$: Observable<Product[]> | null = null;

  constructor(private http: HttpClient) {}

  getData(): Observable<Product[]> {
    if (!this.cache$) {
      this.cache$ = this.http.get<Product[]>('/api/products').pipe(
        shareReplay(1)  // Caches the last emitted value for all subscribers
      );
    }
    return this.cache$;
  }

  clearCache(): void { this.cache$ = null; }
}
🛡️
HTTP Interceptor — Request-Level Cache
Create a custom HTTP Interceptor to intercept all outgoing GET requests, check an in-memory Map for a cached response, and return it immediately if available. This works across all services without any per-service code.
TypeScript · HTTP Interceptor
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler,
         HttpResponse } from '@angular/common/http';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  private cache = new Map<string, HttpResponse<unknown>>();

  intercept(req: HttpRequest<unknown>, next: HttpHandler) {
    if (req.method !== 'GET') return next.handle(req);

    const cached = this.cache.get(req.url);
    if (cached) return of(cached.clone());  // ← cache hit

    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse)
          this.cache.set(req.url, event);
      })
    );
  }
}
04
CloudFront Content Delivery
🌍
Static Assets via S3 + CloudFront
Store your Angular build output in Amazon S3 and serve it through CloudFront to cache static files at edge locations globally. Users load the app from the nearest POP rather than a distant origin server.
  • Angular dist/ folder → S3 bucket (static website hosting)
  • CloudFront Distribution → S3 origin with long TTL for hashed assets
  • index.html → short TTL or Cache-Control: no-cache
🔀
API Routing via CloudFront Behaviors
Configure a CloudFront Behavior to route /api/* requests to your EC2 or API Gateway origin. Apply a specific Cache Policy that varies by query string and Authorization header where needed.
  • Path pattern: /api/* → EC2 / API Gateway origin
  • Cache based on: query strings, selected headers
  • Default TTL: 0 for authenticated routes; 60s+ for public data
Deployment tip — If your Angular app calls your API, update environment.prod.ts with the EC2 instance's public DNS or your CloudFront distribution URL before running the build pipeline, so the production bundle points to the correct API endpoint.