Skip to content

Instantly share code, notes, and snippets.

@theer1k
Created July 10, 2023 13:23
Show Gist options
  • Save theer1k/d079c31073d556a97d9bbf4e22c2eaa3 to your computer and use it in GitHub Desktop.
Save theer1k/d079c31073d556a97d9bbf4e22c2eaa3 to your computer and use it in GitHub Desktop.
Angular cache interceptor
import {
HttpEvent,
HttpHandler,
HttpHeaders,
HttpRequest,
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs';
import { CacheInterceptor } from './cache.interceptor';
describe('CacheInterceptor', () => {
let interceptor: CacheInterceptor;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [CacheInterceptor],
});
interceptor = TestBed.inject(CacheInterceptor);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
function createRequest(shouldCache: boolean): HttpRequest<any> {
const headers = shouldCache
? new HttpHeaders()
: new HttpHeaders({ 'Cache-Expiration': 'no-cache' });
return new HttpRequest<any>('GET', 'https://test.com/api/test', {
headers,
});
}
it('should be created', () => {
expect(interceptor).toBeTruthy();
});
it('should not cache the response if Cache-Expiration header is set to no-cache', () => {
const request = createRequest(false);
const next: HttpHandler = {
handle: jest.fn().mockReturnValue(new Observable<HttpEvent<any>>()),
};
interceptor.intercept(request, next);
expect(interceptor.getCachedResponse(request)).toBeNull();
});
it('should cache the response if Cache-Expiration header is not set to no-cache', (done) => {
const request = createRequest(true);
const next: HttpHandler = {
handle: jest.fn().mockReturnValue(
new Observable<HttpEvent<any>>((subscriber) => {
subscriber.next({ type: 0 });
subscriber.complete();
})
),
};
interceptor.intercept(request, next).subscribe(() => {
expect(interceptor.getCachedResponse(request)).toBeTruthy();
done();
});
});
it('should return cached response if not expired', (done) => {
const request = createRequest(true);
const next: HttpHandler = {
handle: jest.fn().mockReturnValue(
new Observable<HttpEvent<any>>((subscriber) => {
subscriber.next({ type: 0 });
subscriber.complete();
})
),
};
interceptor.intercept(request, next).subscribe(() => {
jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 20000);
expect(interceptor.getCachedResponse(request)).toBeTruthy();
done();
});
});
it('should not return cached response if expired', (done) => {
const request = createRequest(true);
const next: HttpHandler = {
handle: jest.fn().mockReturnValue(
new Observable<HttpEvent<any>>((subscriber) => {
subscriber.next({ type: 0 });
subscriber.complete();
})
),
};
interceptor.intercept(request, next).subscribe(() => {
jest.spyOn(Date, 'now').mockReturnValue(Date.now() + 31000);
expect(interceptor.getCachedResponse(request)).toBeNull();
done();
});
});
});
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private cache: Map<
string,
{ timestamp: number; response$: Observable<HttpEvent<any>> }
> = new Map();
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const shouldCache = request.headers.get('Cache-Expiration') !== 'no-cache';
if (shouldCache) {
const cachedResponse$ = this.getCachedResponse(request);
if (cachedResponse$) {
return cachedResponse$;
}
}
const response$ = next.handle(request).pipe(shareReplay());
if (shouldCache) {
this.cache.set(request.urlWithParams, {
timestamp: Date.now(),
response$,
});
}
return response$;
}
getCachedResponse(
request: HttpRequest<unknown>
): Observable<HttpEvent<unknown>> | null {
const cached = this.cache.get(request.urlWithParams);
if (!cached) {
return null;
}
console.warn('cached', cached);
const cacheAge = Date.now() - cached.timestamp;
const maxCacheAge = 30000;
if (cacheAge > maxCacheAge) {
this.cache.delete(request.urlWithParams);
return null;
}
return cached.response$;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment