使用 Angular 进行客户端缓存

2025-06-07

使用 Angular 进行客户端缓存

应用程序向用户显示有用信息的时间对用户体验有很大影响。因此,我认为作为软件开发者,我们有责任实现一些机制,尽可能减少加载时间。

在本文中,我将向您展示如何使用 Angular 实现客户端缓存。
读完本文后,您将能够像这样缓存您的 http 请求:

return this._http.get<Product[]>({ url: 'https://example-api/products', cacheMins: 5 })
Enter fullscreen mode Exit fullscreen mode

对于此实现,我们需要:

  • 缓存服务:此服务主要有两个用途:
    • 将数据保存在本地存储中(有有效期)
    • 从本地存储加载数据。
  • 自定义http-client 服务:此服务将在底层使用 Angular HttpClient,但也将使用上面提到的缓存服务从本地存储获取和保存数据。

缓存.服务.ts

import { Injectable } from '@angular/core'

@Injectable()
export class CacheService {
    constructor() { }

    save(options: LocalStorageSaveOptions) {
        // Set default values for optionals
        options.expirationMins = options.expirationMins || 0

        // Set expiration date in miliseconds
        const expirationMS = options.expirationMins !== 0 ? options.expirationMins * 60 * 1000 : 0

        const record = {
            value: typeof options.data === 'string' ? options.data : JSON.stringify(options.data),
            expiration: expirationMS !== 0 ? new Date().getTime() + expirationMS : null,
            hasExpiration: expirationMS !== 0 ? true : false
        }
        localStorage.setItem(options.key, JSON.stringify(record))
    }

    load(key: string) {
        // Get cached data from localstorage
        const item = localStorage.getItem(key)
        if (item !== null) {
            const record = JSON.parse(item)
            const now = new Date().getTime()
            // Expired data will return null
            if (!record || (record.hasExpiration && record.expiration <= now)) {
                return null
            } else {
                return JSON.parse(record.value)
            }
        }
        return null
    }

    remove(key: string) {
        localStorage.removeItem(key)
    }

    cleanLocalStorage() {
        localStorage.clear()
    }
}

export class LocalStorageSaveOptions {
    key: string
    data: any
    expirationMins?: number
}
Enter fullscreen mode Exit fullscreen mode

http-客户端.服务.ts

import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { CacheService } from './cache.service'
import { Observable, of } from 'rxjs'
import { switchMap } from 'rxjs/operators'

export enum Verbs {
    GET = 'GET',
    PUT = 'PUT',
    POST = 'POST',
    DELETE = 'DELETE'
}

@Injectable()
export class HttpClientService {

    constructor(
        private http: HttpClient,
        private _cacheService: CacheService,
    ) { }

    get<T>(options: HttpOptions): Observable<T> {
        return this.httpCall(Verbs.GET, options)
    }

    delete<T>(options: HttpOptions): Observable<T> {
        return this.httpCall(Verbs.DELETE, options)
    }

    post<T>(options: HttpOptions): Observable<T> {
        return this.httpCall(Verbs.POST, options)
    }

    put<T>(options: HttpOptions): Observable<T> {
        return this.httpCall(Verbs.PUT, options)
    }

    private httpCall<T>(verb: Verbs, options: HttpOptions): Observable<T> {

        // Setup default values
        options.body = options.body || null
        options.cacheMins = options.cacheMins || 0

        if (options.cacheMins > 0) {
            // Get data from cache
            const data = this._cacheService.load(options.url)
            // Return data from cache
            if (data !== null) {
                return of<T>(data)
            }
        }

        return this.http.request<T>(verb, options.url, {
            body: options.body
        })
            .pipe(
                switchMap(response => {
                    if (options.cacheMins > 0) {
                        // Data will be cached
                        this._cacheService.save({
                            key: options.url,
                            data: response,
                            expirationMins: options.cacheMins
                        })
                    }
                    return of<T>(response)
                })
            )
    }
}

export class HttpOptions {
    url: string
    body?: any
    cacheMins?: number
}
Enter fullscreen mode Exit fullscreen mode

现在,假设我们有一个产品服务,用于从 API 中检索产品列表。在此服务中,我们将使用最近创建的 http-client 服务发出请求,并将数据在 localstorage 中保存 5 分钟:

// product.service.ts

import { Injectable } from '@angular/core'
import { HttpClientService } from './http-client.service'
import { Observable } from 'rxjs'

@Injectable()
export class ProductService {

    constructor(
        private _http: HttpClientService
    ) { }

    getAll(): Observable<Product[]> {
        return this._http
            .get<Product[]>({ url: 'https://example-api/products', cacheMins: 5 })
    }
}

export class Product {
    name: string
    description: string
    price: number
    available: boolean
}
Enter fullscreen mode Exit fullscreen mode

你觉得这个策略怎么样?你还在用其他技术,比如 http-interceptor 吗?请在下方评论区留言告诉我。

文章来源:https://dev.to/mauro_codes/client-side-caching-with-angular-2i6l
PREV
动态构建 Angular 表单元数据组件使其可迭代(可重复)演示和代码
NEXT
GoF 设计模式在 Go 中仍然有意义