首页 > 解决方案 > 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中。

有时情况更糟,所有项目都没有被缓存:

在此处输入图像描述

在这种情况下,View Page Source中没有任何内容。

我能做些什么来确保请求总是被缓存?我需要创建一个解析器还是更简单的东西?

标签: angularangular-universalangular-transfer-state

解决方案


这是由两个问题引起的。第一个是因为我使用的是contentfuls SDK 并且开箱即用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);
  }
}

这是一个更好的版本,现在一切正常


推荐阅读