Какие знаешь способы кеширования данных в Angular?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы кеширования данных в Angular
В Angular существует несколько подходов к кешированию данных, каждый из которых решает определенные задачи и подходит для разных сценариев. Кеширование — это критически важная техника для оптимизации производительности, уменьшения количества HTTP-запросов и улучшения пользовательского опыта.
1. Сервисы с внутренним кешем (In-Memory Caching)
Самый простой и распространенный способ — создание сервиса, который хранит данные в приватном свойстве. Этот подход идеален для кеширования на уровне приложения в течение сессии.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { shareReplay, tap } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class DataService {
private cache = new Map<string, any>();
private cacheTime = new Map<string, number>();
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 минут
constructor(private http: HttpClient) {}
getProducts(): Observable<Product[]> {
const cacheKey = 'products';
const cachedData = this.cache.get(cacheKey);
const cachedTime = this.cacheTime.get(cacheKey);
// Проверка актуальности кеша
if (cachedData && cachedTime &&
(Date.now() - cachedTime) < this.CACHE_DURATION) {
return of(cachedData);
}
return this.http.get<Product[]>('/api/products').pipe(
tap(data => {
this.cache.set(cacheKey, data);
this.cacheTime.set(cacheKey, Date.now());
}),
shareReplay(1) // Дополнительное кеширование для поздних подписчиков
);
}
invalidateCache(key?: string): void {
if (key) {
this.cache.delete(key);
this.cacheTime.delete(key);
} else {
this.cache.clear();
this.cacheTime.clear();
}
}
}
2. RxJS операторы для кеширования
RxJS предоставляет мощные операторы для реализации кеширования в реактивном стиле:
shareReplay()— кеширует последнее значение и отдает его новым подписчикамpublishReplay()+refCount()— более гибкая альтернативаshareReplay()ReplaySubject— специальный тип Subject, который хранит историю значений
import { ReplaySubject, Observable } from 'rxjs';
import { take } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class UserService {
private usersCache$: ReplaySubject<User[]>;
constructor(private http: HttpClient) {
this.usersCache$ = new ReplaySubject<User[]>(1);
}
loadUsers(): void {
this.http.get<User[]>('/api/users').pipe(
take(1)
).subscribe(users => {
this.usersCache$.next(users);
});
}
getUsers(): Observable<User[]> {
// Если кеш пуст — загружаем данные
if (!this.usersCache$.observers.length) {
this.loadUsers();
}
return this.usersCache$.asObservable();
}
}
3. HTTP Interceptors для автоматического кеширования
HTTP Interceptor позволяет реализовать кеширование на уровне всех HTTP-запросов:
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler,
HttpResponse, HttpEvent } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
private cache = new Map<string, {
data: any;
timestamp: number
}>();
private readonly MAX_CACHE_AGE = 300000; // 5 минут
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Кешируем только GET-запросы
if (req.method !== 'GET') {
return next.handle(req);
}
const cachedResponse = this.cache.get(req.urlWithParams);
// Проверяем актуальность кеша
if (cachedResponse &&
(Date.now() - cachedResponse.timestamp) < this.MAX_CACHE_AGE) {
return of(new HttpResponse({
body: cachedResponse.data,
status: 200
}));
}
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
this.cache.set(req.urlWithParams, {
data: event.body,
timestamp: Date.now()
});
}
})
);
}
}
4. Локальное хранилище браузера
Для долговременного кеширования между сессиями можно использовать:
localStorage— сохраняет данные бессрочноsessionStorage— сохраняет данные только на время сессии- IndexedDB — для больших объемов структурированных данных
@Injectable({ providedIn: 'root' })
export class PersistentCacheService {
private readonly CACHE_PREFIX = 'app_cache_';
set(key: string, data: any, ttl?: number): void {
const cacheItem = {
data,
expiry: ttl ? Date.now() + ttl : null
};
localStorage.setItem(
`${this.CACHE_PREFIX}${key}`,
JSON.stringify(cacheItem)
);
}
get(key: string): any {
const item = localStorage.getItem(`${this.CACHE_PREFIX}${key}`);
if (!item) return null;
const cacheItem = JSON.parse(item);
// Проверка срока действия
if (cacheItem.expiry && Date.now() > cacheItem.expiry) {
this.remove(key);
return null;
}
return cacheItem.data;
}
remove(key: string): void {
localStorage.removeItem(`${this.CACHE_PREFIX}${key}`);
}
}
5. Специализированные библиотеки
Для сложных сценариев можно использовать специализированные решения:
- Angular Service Worker (
@angular/pwa) — для кеширования API-запросов и статических ресурсов - NGX-Cache — библиотека с расширенными возможностями кеширования
- Akita / NgRx — управление состоянием с встроенными механизмами кеширования
6. Стратегии инвалидации кеша
Инвалидация (сброс) кеша — не менее важная часть, чем само кеширование:
- TTL (Time To Live) — автоматическое удаление по истечении времени
- Ручная инвалидация — при изменении данных через POST/PUT/DELETE
- Зависимые ключи — сброс связанных данных при изменении основного ресурса
- Версионирование — добавление версии к ключу кеша
@Injectable({ providedIn: 'root' })
export class CacheManagerService {
private dependencies = new Map<string, string[]>();
addDependency(parentKey: string, childKey: string): void {
if (!this.dependencies.has(parentKey)) {
this.dependencies.set(parentKey, []);
}
this.dependencies.get(parentKey)!.push(childKey);
}
invalidateWithDependencies(key: string): void {
// Удаляем основной ключ
this.cache.delete(key);
// Удаляем зависимые ключи
const deps = this.dependencies.get(key);
if (deps) {
deps.forEach(depKey => this.cache.delete(depKey));
}
}
}
Критерии выбора стратегии кеширования
При выборе подхода учитывайте:
- Время жизни данных — как часто они изменяются
- Объем данных — сколько памяти готовы выделить
- Критичность актуальности — можно ли показывать устаревшие данные
- Архитектура приложения — используются ли state-менеджеры
- Тип данных — статические справочники vs динамический контент
Оптимальный подход — комбинирование нескольких методов: in-memory кеш для часто используемых данных, HTTP Interceptor для повторяющихся запросов и localStorage для пользовательских настроек или редко изменяющихся справочников. Главное — реализовать четкую стратегию инвалидации, чтобы пользователь всегда видел актуальные данные, когда это необходимо.