Angular PWA 安装和配置
本篇文章是关于如何在Angular应用中开启PWA功能的完整介绍, 一步步带你从初始化项目到完整的PWA功能实现。
什么是PWA
渐进式 Web 应用程序(PWA) 是一种使用 Web 平台技术构建的应用程序,但它提供类似于平台特定应用程序的用户体验。
与网站一样,PWA 可以通过单一代码库在多个平台和设备上运行。与平台专用应用一样,它可以安装在设备上,可以在离线和后台运行,并且可以与设备以及其他已安装的应用集成。
本篇文章主要讲解如何在Angular项目中使用PWA.
在Angular项目中使用PWA
按照以下步骤在 Angular 中创建 PWA。
1.创建 Angular 应用程序
2.添加@angular/pwa包
3.@angular/pwa了解包中添加/修改的文件
4.在本地运行应用程序
5.将 PWA 部署到生产环境
创建一个 Angular 应用程序
1.首先我们使用Angular Cli来创建一个新的Angular项目
cmd
ng new angular-pwa
项目初始化后通过快捷命令打开项目
cmd
cd angular-pwa && code .
2.添加@angular/pwa包
git bash
ng add @angular/pwa
上述快捷指令可以帮助我们添加PWA必备的文件和相关的配置。
3.了解 @angular/pwa 包添加/修改的文件
ngsw-config.json
PWA最核心的 JSON 格式的配置文件。这个文件主要负责 (serviceworker.js) 的生成。您不必为此编写代码,只需设置某个配置即可完成。配置特定的缓存策略缓存资源/资产。
index
— 指定入口 HTML文件
assetGroups
— 可以指定需要缓存的资产或资源,并指定缓存策略,是应为网络优先、缓存优先,还是两者的组合。
当然还有 dataGroups
,默认配置文件没有帮我们生成,后续我们会详细介绍。
manifest.webmanifest
这个文件用来配置 PWA 应用打开时的显示效果。可以在这里设置启动画面图标、背景颜色、显示等选项。
app.config.ts
注册了ServiceWorker 服务,ServiceWorker也是实现PWA的核心。
注意代码中enabled: !isDevMode()
,PWA是在生成环境下生效的。需要注意的是由于本项目使用新版的Angular Cli初始化,所以配置在app.config.ts
文件。如果你使用的是旧版的Angular项目,默认配置在,App.Module.ts
文件中。
angular.json
在production 的配置中添加了"serviceWorker": "ngsw-config.json"
接下来我们用一个完整的例子来看一下PWA的效果。
使用express起一个服务
新建一个server.js文件。
javascript
import express from 'express';
import cors from 'cors';
// mock data
const categories = [
{ id: 1, name: 'skills' },
{ id: 2, name: 'life' },
{ id: 3, name: 'notes' },
{ id: 4, name: 'others' }
];
const posts = [
{
id: 1,
title: 'Angular',
content: 'Angular is a framework for building client applications...',
categoryId: 1,
createdAt: '2024-03-20'
},
{
id: 2,
title: 'React',
content: 'React is a library for building user interfaces...',
categoryId: 2,
createdAt: '2024-03-19'
},
{
id: 3,
title: 'Vue',
content: 'Vue is a progressive framework for building user interfaces...',
categoryId: 3,
createdAt: '2024-03-18'
}
];
const app = express();
app.use(cors({
origin: 'http://localhost:4200',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
app.get('/api/posts', (req, res) => {
res.send(posts);
});
app.get("/api/categories", (req, res) => {
res.send(categories);
});
app.listen(8080, () => {
console.log('Server is running on port 8080');
});
我们创建了一个简单的接口服务.
修改前端页面
创建接口文件
typescript
import { HttpClient } from "@angular/common/http";
import { inject, Injectable } from "@angular/core";
import { Category, Post } from "../models/blog";
@Injectable({
providedIn: 'root'
})
export class DataService {
private http = inject(HttpClient);
private baseUrl = 'http://localhost:8080/api';
getPosts() {
return this.http.get<Post[]>(`${this.baseUrl}/posts`);
}
getCategories() {
return this.http.get<Category[]>(`${this.baseUrl}/categories`);
}
}
创建组件
首先我们在app目录下新建components文件夹。
git bash
cd src/app/components
ng g component post
cli会帮我们创建post组件,修改post组件
typescript
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from "@angular/core";
import { Category, Post } from "../../models/blog";
import { Subscription } from "rxjs";
import { CommonModule, isPlatformBrowser } from "@angular/common";
import { DataService } from "../../services/data.service";
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.scss'],
imports: [CommonModule],
standalone: true
})
export class PostComponent implements OnInit, OnDestroy {
posts: Post[] = [];
categories: Category[] = [];
subs = new Subscription();
offline = true;
constructor(private dataService: DataService,
@Inject(PLATFORM_ID) private platformId: Object
) {
if (isPlatformBrowser(platformId)) {
this.offline = !navigator.onLine;
window.addEventListener("online", () => {
this.offline = false;
this.loadData();
})
window.addEventListener("offline", () => {
this.offline = true;
})
}
}
ngOnInit(): void {
this.loadData();
}
loadData() {
this.subs.add(this.dataService.getPosts().subscribe((posts) => {
this.posts = posts;
}));
this.subs.add(this.dataService.getCategories().subscribe((categories) => {
this.categories = categories;
}));
}
ngOnDestroy() {
this.subs.unsubscribe();
}
}
由于组件代码过多,只展示了部分代码,如果想看完整代码请下载完整code
配置路由
typescript
import { Routes } from '@angular/router';
import { PostComponent } from './components/post/post.component';
export const routes: Routes = [
{
path: '',
component: PostComponent
}
];
配置缓存接口
打开ngsw-config.json
文件,新增配置
json
"dataGroups": [
{
"name": "api-freshness",
"urls": ["/api/posts", "/api/categories"],
"cacheConfig": {
"strategy": "freshness",
"maxSize": 100,
"maxAge": "1h",
"timeout": "10s"
}
}
]
以上配置我们指定了需要缓存的 API 路径(["/api/posts", "/api/categories"]
)。所有匹配这些路径的网络请求都会被 Service Worker 按照下面的 cacheConfig 策略处理。
"strategy": "freshness"
采用了网络优先的策略。先尝试从网络获取数据,如果网络响应在 timeout 时间内返回,则使用网络数据并更新缓存。如果网络超时或失败,则使用缓存中的数据.
"maxSize": 100
, 缓存中最多存储 100 条请求的响应。超过后会自动移除最旧的缓存项.
"maxAge": "1h"
, 缓存的最大有效期为 1 小时。超过 1 小时的数据会被视为过期,不再使用。
"timeout": "10s"
, 网络请求的超时时间为 10 秒。如果 10 秒内没有响应,就会回退到缓存。
基本的准备工作我们已经做完了,接下来就可以测试我们的PWA了。
启动项目
首先我们先启动我们的服务,运行已经编写好的启动脚本
git bash
npm run pwa-server
我们的服务就在8080端口跑起来了,接着启动我们的angular项目
git bash
ng serve
// npm run start
然后我们打开浏览器控制台,关掉网络,刷新页面
发现PWA缓存并没有生效,再次印证了在开发模式下是不生效的。
接下来我们关掉我们的前端进程,在生产模式下测试。
git bash
npm run pwa
我们可以看到我们的两个接口都被service worker 拦截和请求了。我们再次将网络置为offline并刷新页面。
与之前不同的是我们的页面仍然可以正常显示, 尽管接口的请求都是失败的,但是还有缓存的数据兜底。
此外我们的浏览器多出了一个安装PWA应用的按钮,点击安装就可以将我们的网页应用安装到本地像app那样打开使用。至此关于Angular PWA应用的介绍就告一段落了。
