Angular 服务工作者 - Rest API 数据

5
我正在尝试在我的Angular 5项目中设置渐进式Web应用程序。我还使用Angular Universal进行服务器端渲染。
我遇到了来自API的缓存数据问题。我创建了一个Rest API,它看起来像https://example.net/getContent/param_1/param_2/param_3。其中param_1是路由参数中的页面名称,param_2是一个语言URL,param_3是一个语言代码。在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/"一样缓存每个请求,但它没有。我无法离线运行应用程序,服务工作者在之前不会预加载所有页面数据。如何使其起作用?如何预加载所有页面的所有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": {
    }
  }

}

例如一个组件:

news.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的服务器传输状态后,它会显示在缓存选项卡中,但仍无法离线使用(显示缓存选项卡的屏幕)。

Cache table

localForage似乎是最好的解决方案。如果它有效,将发送答案。


1
我也遇到了同样的问题,解决方法是使用ngforage,这是Angular 4和5的localForage绑定。它帮助我将API数据本地保存在浏览器中,一旦我们离线,就可以使用这些数据。如果您找到其他方法,请告诉我。谢谢。 - nithalqb
你认为它能解决动态Rest API调用吗?我认为URL只知道浏览器导航到指定页面的位置。如何跳过它并在后台下载其余数据? - Patryk Panek
这对我来说解决了问题,因为我从 API 获取数据并使用 localForage 进行本地存储,一旦应用离线,数据将从本地获取。但是我仍然对 dataGroups 中的 URL 的作用不清楚... 我想你可能是正确的。 - nithalqb
1
好的,也许可以不使用外部工具来实现。我希望这里有人知道解决方案。如果没有,我会尝试按照你们的方式来做。保持联系! - Patryk Panek
2个回答

2

好的,我终于找到了一个解决方案。 感谢@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);
                    })
                }
            })
        }
    };
}

感谢您的回答。

你能帮我解决这个问题吗 -> https://stackoverflow.com/questions/62504521/angular-pwa-how-can-i-make-http-server-running-on-localhost8080-to-work-with - Donovant

0

@patryk-panek,您可以通过避免在API路径中使用通配符来解决缓存问题。例如:

"urls": [
  "https://example.net/getMenus",
  "https://example.net/getContent",
]

服务工作者是否适用于HTTP请求(而非HTTPS)? - Soumya Gangamwar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接