Angular 通用传输状态未按预期工作
Angular Universal Transfer State not working as expected
我有一个问题,我的 API 中的数据没有显示在我的项目的视图源中。
我做了一些研究并发现了 TransferState 所以我创建了一个 class:
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 中看到它,但是如果它必须将它插入到缓存中,那么它不在页面源中。
当我第一次测试它时,一切似乎都很好。我明白了:
在我的项目控制台中,我可以看到:
太棒了。
但它似乎并不总是有效。
如果我刷新页面,有时它会起作用,但大多数时候它不会:
在这种情况下,导航和页脚都被缓存,但页面没有,这意味着它不在查看页面源。
有时情况更糟,所有项目都没有缓存:
在这种情况下,查看页面源代码中没有任何内容。
如何确保始终缓存请求?我需要创建一个解析器还是更简单的东西?
这是由两个问题引起的。第一个是因为我使用的是 contentfuls SDK and out of the box it uses HttpClient
, which actually needed overriding to use the TransferHttpService
(see )
第二个问题是 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);
}
}
哪个版本更好,现在一切正常
我有一个问题,我的 API 中的数据没有显示在我的项目的视图源中。 我做了一些研究并发现了 TransferState 所以我创建了一个 class:
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 中看到它,但是如果它必须将它插入到缓存中,那么它不在页面源中。
当我第一次测试它时,一切似乎都很好。我明白了:
在我的项目控制台中,我可以看到:
太棒了。 但它似乎并不总是有效。 如果我刷新页面,有时它会起作用,但大多数时候它不会:
在这种情况下,导航和页脚都被缓存,但页面没有,这意味着它不在查看页面源。
有时情况更糟,所有项目都没有缓存:
在这种情况下,查看页面源代码中没有任何内容。
如何确保始终缓存请求?我需要创建一个解析器还是更简单的东西?
这是由两个问题引起的。第一个是因为我使用的是 contentfuls SDK and out of the box it uses HttpClient
, which actually needed overriding to use the TransferHttpService
(see
第二个问题是 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);
}
}
哪个版本更好,现在一切正常