Angular Universal Transfer State не работает должным образом

У меня проблема, когда данные из моего API не отображаются в источнике представления моего проекта. Я провел небольшое исследование и наткнулся на TransferState, поэтому создал класс:

import { Injectable } from '@angular/core';
import { TransferState } from '@angular/platform-browser';
import { Observable, from } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class TransferHttpService {
  constructor(
    private transferHttp: TransferState,
    private httpClient: HttpClient
  ) {}
  get(url, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.get(url, options);
    });
  }
  post(url, body, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.post(url, body, options);
    });
  }
  delete(url, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.delete(url, options);
    });
  }
  put(url, body, options?): Observable<any> {
    return this.getData(url, options, () => {
      return this.httpClient.put(url, body, options);
    });
  }
  getData(url, options, callback: () => Observable<any>): Observable<any> {
    const optionsString = options ? JSON.stringify(options) : '';
    let key = `${url + optionsString}`;
    try {
      return this.resolveData(key);
    } catch (e) {
      console.log('In catch', key);
      return callback().pipe(
        tap((data) => {
          console.log('cache set', key);
          this.setCache(key, data);
        })
      );
    }
  }
  resolveData(key) {
    let resultData: any;
    if (this.hasKey(key)) {
      resultData = this.getFromCache(key);
      console.log('got cache', key);
    } else {
      throw new Error();
    }
    return from(Promise.resolve(resultData));
  }
  setCache(key, value) {
    this.transferHttp.set(key, value);
  }
  getFromCache(key) {
    return this.transferHttp.get(key, null); // null set as default value
  }
  hasKey(key) {
    return this.transferHttp.hasKey(key);
  }
}

И в любой службе, где я использовал HttpClient, я заменил ее на TransferHttpService. Если вы посмотрите на сервис, вы увидите несколько журналов консоли. Это полезно, потому что, когда данные захватываются из кеша, я вижу их в View Page Source, но если он должен вставить их в кеш, то их нет в источнике страницы.

Когда я впервые его протестировал, все было хорошо. Получил вот что:

В моей консоли проекта я мог видеть это:

Что здорово. Но, похоже, это не всегда работает. Если я обновляю страницу, иногда это работает, в большинстве случаев - нет:

В этом случае и навигация, и нижний колонтитул кэшируются, но страницы нет, что означает, что их нет в исходном коде страницы просмотра.

Иногда бывает намного хуже, когда все элементы не кешируются:

И в этом случае в исходном коде страницы просмотра ничего нет.

Что я могу сделать, чтобы запросы всегда кэшировались? Мне нужно создать резолвер или что-то более простое?

1 ответ

Решение

Это было вызвано двумя проблемами. Первое было связано с тем, что я использовал SDK contentfuls, и из коробки он используетHttpClient, который на самом деле потребовал переопределения, чтобы использовать TransferHttpService(см. здесь)

Вторая проблема заключалась в TransferHttpServiceсам. Я изменил его на это:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import {
  TransferState,
  StateKey,
  makeStateKey,
} from '@angular/platform-browser';
import { Observable, from } from 'rxjs';
import { tap } from 'rxjs/operators';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Injectable({ providedIn: 'root' })
export class TransferHttpService {
  constructor(
    protected transferState: TransferState,
    private httpClient: HttpClient,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  request<T>(
    method: string,
    uri: string | Request,
    options?: {
      body?: any;
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      reportProgress?: boolean;
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      method,
      uri,
      options,
      (method: string, url: string, options: any) => {
        return this.httpClient.request<T>(method, url, options);
      }
    );
  }

  /**
   * Performs a request with `get` http method.
   */
  get<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'get',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.get<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `post` http method.
   */
  post<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'post',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, body: any, options: any) => {
        return this.httpClient.post<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `put` http method.
   */
  put<T>(
    url: string,
    _body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'body';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'put',
      url,
      _body,
      options,
      (_method: string, url: string, _body: any, options: any) => {
        return this.httpClient.put<T>(url, _body, options);
      }
    );
  }

  /**
   * Performs a request with `delete` http method.
   */
  delete<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'delete',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.delete<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `patch` http method.
   */
  patch<T>(
    url: string,
    body: any,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getPostData<T>(
      'patch',
      url,
      body,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (
        _method: string,
        url: string,
        body: any,
        options: any
      ): Observable<any> => {
        return this.httpClient.patch<T>(url, body, options);
      }
    );
  }

  /**
   * Performs a request with `head` http method.
   */
  head<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'head',
      url,
      options,
      (_method: string, url: string, options: any) => {
        return this.httpClient.head<T>(url, options);
      }
    );
  }

  /**
   * Performs a request with `options` http method.
   */
  options<T>(
    url: string,
    options?: {
      headers?:
        | HttpHeaders
        | {
            [header: string]: string | string[];
          };
      observe?: 'response';
      params?:
        | HttpParams
        | {
            [param: string]: string | string[];
          };
      reportProgress?: boolean;
      responseType?: 'json';
      withCredentials?: boolean;
    }
  ): Observable<T> {
    // tslint:disable-next-line:no-shadowed-variable
    return this.getData<T>(
      'options',
      url,
      options,
      // tslint:disable-next-line:no-shadowed-variable
      (_method: string, url: string, options: any) => {
        return this.httpClient.options<T>(url, options);
      }
    );
  }

  // tslint:disable-next-line:max-line-length
  getData<T>(
    method: string,
    uri: string | Request,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey = url + (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);
    try {
      return this.resolveData<T>(key);
    } catch (e) {
      //console.log('in catch', key);
      return callback(method, uri, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            //console.log('set cache', key);
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private getPostData<T>(
    _method: string,
    uri: string | Request,
    body: any,
    options: any,
    callback: (
      method: string,
      uri: string | Request,
      body: any,
      options: any
    ) => Observable<any>
  ): Observable<T> {
    let url = uri;

    if (typeof uri !== 'string') {
      url = uri.url;
    }

    const tempKey =
      url +
      (body ? JSON.stringify(body) : '') +
      (options ? JSON.stringify(options) : '');
    const key = makeStateKey<T>(tempKey);

    try {
      return this.resolveData<T>(key);
    } catch (e) {
      return callback(_method, uri, body, options).pipe(
        tap((data: T) => {
          if (isPlatformBrowser(this.platformId)) {
            // Client only code.
            // nothing;
          }
          if (isPlatformServer(this.platformId)) {
            this.setCache<T>(key, data);
          }
        })
      );
    }
  }

  private resolveData<T>(key: StateKey<T>): Observable<T> {
    const data = this.getFromCache<T>(key);

    if (!data) {
      throw new Error();
    }

    if (isPlatformBrowser(this.platformId)) {
      //console.log('get cache', key);
      // Client only code.
      this.transferState.remove(key);
    }
    if (isPlatformServer(this.platformId)) {
      //console.log('we are the server');
      // Server only code.
    }

    return from(Promise.resolve<T>(data));
  }

  private setCache<T>(key: StateKey<T>, data: T): void {
    return this.transferState.set<T>(key, data);
  }

  private getFromCache<T>(key: StateKey<T>): T {
    return this.transferState.get<T>(key, null);
  }
}

Какая версия намного лучше, и теперь все работает

Другие вопросы по тегам