Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Not able to cache using HttpInterceptor in angular 4.3

Tags:

angular

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?
like image 765
Sagar Ganesh Avatar asked Aug 03 '17 09:08

Sagar Ganesh


2 Answers

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;
        }
    });
}
like image 112
Will Shaver Avatar answered Nov 15 '22 09:11

Will Shaver


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.

like image 27
rmlarsen Avatar answered Nov 15 '22 09:11

rmlarsen