'TransferHttpCacheModule doesn't prevent duplicate HTTP Calls in Angular 8

I've set up an Angular 8 Project with Angular Universal. To prevent duplicate HTTP Calls, Angular offers TransferHttpCacheModule.

I followed the official Documentation to add TransferHttpCacheModule to Angular (https://github.com/angular/universal/blob/master/docs/transfer-http.md)

I also tried to add the BrowserTransferStateModule (https://www.twilio.com/blog/faster-javascript-web-apps-angular-universal-transferstate-api-watchdog), but this doesn't work either.

app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {TransferHttpCacheModule} from "@nguniversal/common";

@NgModule({
  declarations: [
    AppComponent,
    ...
  ],
  imports: [
    BrowserModule.withServerTransition({appId: 'serverApp'}),
    TransferHttpCacheModule, // <-
    ...
    HttpClientModule
  ]
  bootstrap: [AppComponent]
})
export class AppModule {
}

app.server.module.ts

import {NgModule} from '@angular/core';
import {ServerModule, ServerTransferStateModule} from '@angular/platform-server';

import {AppModule} from './app.module';
import {AppComponent} from './app.component';
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule,
    ServerTransferStateModule // <-
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {
}

main.ts

document.addEventListener('DOMContentLoaded', () => {
  platformBrowserDynamic()
    .bootstrapModule(AppModule)
  .catch(err => console.error(err));
});

My Api Service api.service.ts

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from "rxjs";

@Injectable({
  providedIn: 'root'
})
export class ApiService {

  private API_URL = '/api/';

  constructor(private http: HttpClient) {
  }

  public get<T>(url: string): Observable<T> {
    return this.http.get<T>(this.API_URL + url);
  }

  public post<T>(url: string, payload: T): Observable<T> {
    return this.http.post<T>(this.API_URL + url, payload);
  }

  ...

}

HTTP Call home.component.ts

import {Component, OnInit} from '@angular/core';
import {ApiService} from "../../api.service";
import {Offer} from "../../offer-preview/offer.model";

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.sass']
})
export class HomeOffersComponent implements OnInit {
  latestOffers: Offer[];

  constructor(private apiService: ApiService) {
  }

  ngOnInit() {
    this.apiService.get<Offer[]>("offer")
      .subscribe(data => this.latestOffers = data);
  }

}

According to the official TransferHttpCacheModule Docs, the Browser should not make an XHR to /api/offer, but the i see the XHR Call in the Developer Network Tools.

What am i doing wrong? Did I miss anything?



Solution 1:[1]

This is because of a mismatch between the absolute URLs the TransferHttpCacheModule is using as cache key for each request. For example:

Server Client
baseUrl localhost:4200 domain.com
ressource /api/people /api/people
Absolute URL http://localhost:4200/api/people https://example.com/api/people

Only if both Absolute URLs are the same the request is successfully cached. To circumvent this shortcoming you can implement your own caching via the BrowserTransferStateModule and Interceptors, e.g. as described here.

Just be sure to only use the relative URL:

const url = new URL(req.url);
const rel = url.toString().substring(url.origin.length);
this.transferState.set(makeStateKey(rel), event.body);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daniel Habenicht