我正在尝试在我的 Angular 5 项目中设置 Progressive Web App。我还使用 Angular Universal 进行服务器端渲染。
我在从 API 缓存数据时遇到问题。我做了一个看起来像 https://example.net/getContent/param_1/param_2/param_3 的 Rest API .其中 param_1 是路由参数中的页面名称,param_2 是 lang url,param_3 是 lang 代码。在 ngsw-config.json 中,我是这样做的:
"dataGroups": [{
"name": "api-performance",
"urls": [
"https://example.net/getMenus/**",
"https://example.net/getContent/**",
"https://example.net/getLayout/**",
"https://example.net/getFooter/**"
],
"cacheConfig": {
"maxSize": 10000,
"maxAge": "3d",
"strategy": "performance"
}
}]
我认为它应该缓存每个请求,如“https://example.net/getMenus/anything/anything/anything/”,但事实并非如此。我无法离线运行应用程序,Service Worker 之前没有预加载所有页面数据。如何让它发挥作用?如何预加载所有页面的所有 api 调用?也许动态 api 调用有问题?
这是我的 SW 代码和示例组件。
app.module
// Core
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
import { ServiceWorkerModule } from '@angular/service-worker';
// Guards
import { AuthGuard } from './guards/auth.guard.service';
// Resolvers
import { LayoutResolver } from './resolvers/layout.resolver.service';
// Config
import { Config } from './config';
// Compontents
import { AppComponent } from './app.component';
import { ContainerComponent } from './container/container.component'
import { FooterComponent } from './footer/footer.component'
// Modules
import { MenuModule } from './menu/menu.module';
import { ContainerModule } from './container//container.module'
// Environment
import { environment } from '../environments/environment';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: ContainerComponent,
canActivate: [AuthGuard],
},
{
path: ':lang',
component: ContainerComponent,
resolve: { layout : LayoutResolver }
},
{
path : ':lang/:index',
component: ContainerComponent,
resolve: { layout : LayoutResolver }
}
];
@NgModule({
declarations: [
AppComponent,
FooterComponent
],
imports: [
RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules}),
BrowserAnimationsModule,
BrowserModule.withServerTransition({ appId: 'main-app' }),
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
MenuModule,
ContainerModule
],
providers: [
AuthGuard,
Config,
LayoutResolver
],
bootstrap: [AppComponent]
})
export class AppModule { }
ngsw-config.json
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"favicon.ico",
"**.png"
]
}
}],
"dataGroups": [{
"name": "api-performance",
"urls": [
"https://example.org/getMenus/**",
"https://example.org/getContent/**",
"https://example.org/getLayout/**",
"https://example.org/getFooter/**"
],
"cacheConfig": {
"maxSize": 10000,
"maxAge": "3d",
"strategy": "performance"
}
}]
}
.angular-cli.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "main-app",
"ejected": false
},
"apps": [
{
"root": "src",
"outDir": "dist/browser",
"assets": [
"assets",
"manifest.json",
"favicon.ico",
"robots.txt"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"serviceWorker": true,
"styles": [
"./assets/css/bootstrap.min.css",
"./assets/css/styles.less"
],
"scripts": [
"./assets/js/jquery-1.12.4.min.js",
"../node_modules/bootstrap/dist/js/bootstrap.min.js",
"./assets/js/functions.js"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/browser/environment.ts",
"prod": "environments/browser/environment.prod.ts"
}
},
{
"root": "src",
"outDir": "dist/server",
"assets": [
"assets",
"favicon.ico",
"robots.txt"
],
"platform": "server",
"index": "index.html",
"main": "main.server.ts",
"test": "test.ts",
"tsconfig": "tsconfig.server.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/server/environment.ts",
"prod": "environments/server/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json",
"exclude": "**/node_modules/**"
},
{
"project": "src/tsconfig.spec.json",
"exclude": "**/node_modules/**"
},
{
"project": "e2e/tsconfig.e2e.json",
"exclude": "**/node_modules/**"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "less",
"component": {
}
}
例如一个组件:
新闻组件
import { Component } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Config } from "../../config";
import { ServerService } from "../../services/server.service";
import { SeoService } from "../../services/seo.service";
import { OnDestroy } from '@angular/core/src/metadata/lifecycle_hooks';
import { ISubscription } from 'rxjs/Subscription';
interface pageData {
banner: string;
data: any;
html: string;
text: string;
title: string;
}
@Component({
selector: 'app-news',
templateUrl: './news.component.html',
styleUrls: ['./news.component.less'],
providers : [Config, ServerService, SeoService],
})
export class NewsComponent implements OnDestroy {
subscription: ISubscription;
subscriptionHTTP: ISubscription;
URL: string;
langUrl: string;
active: string;
pageData: pageData;
headerText: Object;
constructor(private config: Config, private route: ActivatedRoute, private service: ServerService, private seo: SeoService) {
this.URL = this.config.impressURL;
this.langUrl = this.config.getLanguage();
this.subscription = this.route.params.subscribe( params => {
if(params.lang != this.langUrl) {
this.langUrl = params.lang;
}
let siteTitle = params.index;
if(typeof siteTitle != 'undefined') {
siteTitle = siteTitle.replace('.html', ' ');
siteTitle = siteTitle.replace(/-/g,' ');
}
this.subscriptionHTTP = this.service.getResponse(`${this.URL}/getContent/${params.index}/${this.langUrl}/0`).subscribe(
(response: any) => {
this.pageData = response;
this.seo.generateTags({
lang: this.langUrl,
title : siteTitle,
image : `${this.URL}/file/repository/${this.pageData.banner}`,
slug : params.index
})
}, (error) => {
console.log(error);
}
);
});
}
ngOnInit(): void {
}
ngOnDestroy() {
if(this.subscription) this.subscription.unsubscribe();
if(this.subscriptionHTTP) this.subscriptionHTTP.unsubscribe();
}
hideOnClick(element, target) {
element.parentNode.parentNode.classList.remove('in');
}
}
编辑 在为 Angular Universal 设置服务器传输状态后,它在缓存选项卡中可见,但仍然无法离线工作(带有缓存选项卡的屏幕)。
localForage 似乎是最好的解决方案。如果可行,将发送答复。
最佳答案
好吧,我终于找到了解决办法。
谢谢@nithalqb 的好主意。 ngforage-ng5工作正常!我添加到 API getAllPages
站点,我将在其中返回所有页面列表。然后我在后台将它插入到 IndexedDB 中,就像那样:
private async saveData(url, data) {
if (data) {
for (let element of data) {
await this.ngf.getItem(element.urlPath).then(async res => {
if (!await res) {
await this.http.get(`${url}/getContent/${element.urlPath}/${element.languageCode}/0`).toPromise().then(async response => {
await this.ngf.setItem(element.urlPath, await response);
})
await this.http.get(`${url}/getLayout/${element.urlPath}/${element.languageCode}`).toPromise().then(async response => {
await this.ngf.setItem(`${element.urlPath}/layout`, await response);
})
}
})
}
};
}
谢谢您的回答。
关于Angular Service Worker - Rest API 数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48581425/