5

I'm trying to implement HttpRequest caching using HttpInterceptor as per the documentation by angular 4.3. But I'm getting an error. Here is my code:

caching.interceptor.ts

import { HttpRequest, HttpResponse, HttpInterceptor, HttpHandler, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/do';
import 'rxjs/add/observable/of';

abstract class HttpCache {
  abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
  abstract put(req: HttpRequest<any>, resp: HttpResponse<any>): void;
}

@Injectable()
export class CachingInterceptor implements HttpInterceptor {
    constructor(private cache: HttpCache) {}

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

        const cachedResponse = this.cache.get(req);

        if(cachedResponse){
            return Observable.of(cachedResponse);
        }

        return next.handle(req).do(event => {
            if(event instanceof HttpResponse){
                this.cache.put(req, event);
            }
        })
    }
}

Here CachingInterceptor works as an interceptor for http request/response. And I've created module a which looks like:

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component/app.component';
import { HomePage } from './pages/home.page/home.page';
import { ProductsPage } from './pages/products.page/products.page';
import { AboutUsPage } from './pages/about-us.page/about-us.page';
import { UsersPage } from './pages/users.page/users.page';
import { DemoPage } from './pages/demo.page/demo.page';
import { appRouter } from './app.router/app.router';
import { CachingInterceptor } from './caching.interceptor/caching.interceptor';
import { AppService } from './app.service/app.service';

@NgModule({
    imports: [ BrowserModule, HttpClientModule, appRouter ],
    declarations: [ AppComponent, HomePage, ProductsPage, DemoPage, AboutUsPage, UsersPage ],
    providers: [ {
        provide: HTTP_INTERCEPTORS,
        useClass: CachingInterceptor,
        multi: true
    }, AppService ],
    bootstrap: [ AppComponent ]
})
export class AppModule {}

Token is also provided in providers[] of module. This is as per the documentation by angular 4.3. But still i'm getting error like:

error

ERROR Error: Uncaught (in promise): Error: No provider for HttpCache!
Error: No provider for HttpCache!
    at injectionError (reflective_errors.ts:71)

I have 2 questions:

  1. HttpCache is an abstract class, then why is it injected like a service?
  2. Even though I'm implementing it as per the official documentation, then why am I getting that error?
1
  • I've updated my answer to fix a multi-request bug, check it out. Commented Jan 19, 2018 at 2:19

2 Answers 2

15

If you're looking for a super-simple cache-all-the-things implementation:

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";

@Injectable()
export class CacheInterceptor implements HttpInterceptor {
    private cache: { [name: string]: AsyncSubject<HttpEvent<any>> } = {};

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.method !== "GET") {
        return next.handle(request);
    }
    const cachedResponse = this.cache[request.urlWithParams] || null;
    if (cachedResponse) {
        return cachedResponse.delay(0);
    }
    const subject = this.cache[request.urlWithParams] = new AsyncSubject<HttpEvent<any>>();
    next.handle(request).do(event => {
        if (event instanceof HttpResponse) {
            subject.next(event);
            subject.complete();
        }
    }).subscribe(); // must subscribe to actually kick off request!
    return subject;
}

Note that this has been updated from the original method. The original intercept method had a bug - if multiple requests of the identical url were attempted prior to the first returning, multiple requests would still hit the server.

This solution allows only one request to be passed through to the server.

The original solution is below for posterity. (Not recommended.)

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.method !== "GET") {
        return next.handle(request);
    }
    const cachedResponse = this.cache[request.urlWithParams] || null;
    if (cachedResponse) {
        return Observable.of(cachedResponse);
    }

    return next.handle(request).do(event => {
        if (event instanceof HttpResponse) {
            this.cache[request.urlWithParams] = event;
        }
    });
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks. Its so simple. : )
@Shree if this is what you wanted, please mark it as the accepted answer. If not, please clarify what it is missing.
Is there a way to bust the cache some of the times? Like if I make a post and expect the data to have been updated?
@dethstrobe currently it only caches GET requests. You could add an argument to the request that this interceptor looks for and bypasses the cache as well.
@WillShaver I have implemented the above caching interceptor but i am facing the problem i am not able to catch the exception in the component in request fail
|
2

From what I can tell your issue is that this is an abstract class

abstract class HttpCache {
    abstract get(req: HttpRequest<any>): HttpResponse<any>|null;
    abstract put(req: HttpRequest<any>, resp: HttpResponse<any>): void;
}

You would need to implement this class and it's methods in order to create an instance of it to use in your CachingInterceptor class

export class HttpCacheService implements HttpCache {
    get(req: HttpRequest<any>): HttpResponse<any>|null {
        // Some logic
    }
    put(req: HttpRequest<any>, resp: HttpResponse<any>): void {
       //Some logic
    }
}

Then use HttpCacheService in your CachingInterceptor class.

But why not just store the requests in some sort of array if you are trying to cache them? This article may be a good starting point on how to accomplish what you are trying to do.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.