如何在 Angular 9 中构建渐进式 Web 应用程序(PWA)?
在本文中,我们将使用 Angular 开发一个 PWA(Progressive Web App)。
什么是 PWA?
渐进式 Web 应用程序 (PWA) 是经过设计的 Web 应用程序,其功能强大、可靠且可安装。 PWA 是使用现代 API 构建和增强的,以提供增强的功能、可靠性和可安装性,同时通过单个代码库在任何设备上的任何人、任何地方使用。 PWA 不需要通过应用商店部署;相反,我们使用不同的方法并通过 URL 通过 Web 服务器部署它。但是在开发 PWA 时,我们必须注意以下因素:
- 响应能力:适用于台式机、移动设备或平板电脑等所有设备,不会出现任何损坏。
- 安全且安全:使用 HTTPS 向我们的 PWA 提供数据可确保安全性。
- 渐进式:使用现代网络功能为每个用户开发类似应用程序的体验。
- 自动更新:无需用户干预即可下载和安装更新。 (这是在 Service Worker 的帮助下)
- 可发现: PWA 应该可以通过搜索引擎进行搜索。 (使用网络应用清单)
- 可安装:可安装在用户的设备主屏幕上。
- 离线工作:它应该配置为离线工作和稀疏网络。
第 1 步:初始化新的 Angular 项目:现在,让我们首先创建一个 Angular 应用程序。在这里,我们将创建一个简单的天气应用程序。为此,首先,创建一个新的 Angular 应用程序并使用以下命令在项目目录中导航。
ng new weather-app
cd weather-app
第 2 步:添加 Bootstrap 链接:我们将在开发前端时使用 Bootstrap 进行样式设置。在项目的index.html文件中添加以下链接。
第 3 步:获取天气数据的 OpenWeatherMap API:为了获取实时天气数据,我们将使用 openweathermap API。通过创建您的帐户获取 API 密钥。
第 4 步:为 WeatherApp 开发 UI:通过运行以下命令创建一个名为weather的 Angular 组件和名为API的 Angular 服务:
ng generate component components/weather
ng generate service services/API
现在,将以下代码粘贴到各自的文件中。
weather.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService }
from 'src/app/services/weather.service';
@Component({
selector: 'app-weather',
templateUrl: './weather.component.html',
styleUrls: ['./weather.component.css'],
})
export class WeatherComponent implements OnInit {
city: any = '';
country: any = '';
weather: any = null;
constructor(private
_weatherService: WeatherService) {}
ngOnInit(): void {}
getDate(str: string) {
return str.split(' ')[0];
}
getTime(str: string) {
return str.split(' ')[1];
}
displayWeather() {
this._weatherService
.getWeather(this.city, this.country)
.subscribe(
(data) => (this.weather = data),
(err) => console.log(err)
);
}
}
weather.component.html
{{ wth.dt_txt | date: "shortTime" }}
{{ getDate(wth.dt_txt) | date }}
{{ city }},
{{ country }}
{{ wth.main.temp - 273.15 | number: "1.1-1" }}°C
{{ wth.weather[0].description | titlecase }}
Humidity {{ wth.main.humidity }}%
Wind
{{ wth.wind.speed }} km/h
Pressure
{{ wth.main.pressure }}
weather.component.css
.col-md-3 {
margin: 5px auto;
}
.input {
margin: 2% 25%;
padding: 2% 2.5%;
font-size: 16px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s;
}
input {
padding: 10px 12px;
}
.weather-info {
width: 100%;
height: 100%;
padding: 20px 20px;
border-radius: 8px;
border: 2px solid #fff;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
background: linear-gradient(to right, #00a4ff, #0072ff);
transition: transform 0.2s ease;
color: whitesmoke;
}
.info-date {
display: flex;
flex-direction: column;
justify-content: center;
}
.info-date h1 {
margin-bottom: 0.65rem;
font-size: 2rem;
letter-spacing: 2px;
}
.info-weather {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
text-align: right;
}
.weather-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}
@keyframes animation-icon {
from {
transform: scale(1);
}
to {
transform: scale(1.2);
}
}
.weather-type {
display: inline-block;
width: 48px;
height: 48px;
transition: all 0.2s ease-in;
animation: animation-icon 0.8s infinite;
animation-timing-function: linear;
animation-direction: alternate;
}
.weather-temperature {
font-size: 1.5rem;
font-weight: 800;
}
.weather-description {
margin-top: 1rem;
font-size: 20px;
font-weight: bold;
}
.weather-city {
margin-top: 0.25rem;
font-size: 16px;
}
.wind i {
margin: 10px;
}
weather.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class WeatherService {
private readonly apiKey: string = ;
constructor(private _http: HttpClient) {}
getWeather(city: string, country: string) {
const apiUrl =
`https://api.openweathermap.org/data/2.5/forecast?q=${city},${country}&appid=${this.apiKey}`;
return this._http.get(apiUrl);
}
}
Javascript
{
"name": "weather-app",
"short_name": "weather-app",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
Javascript
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
天气.component.html
{{ wth.dt_txt | date: "shortTime" }}
{{ getDate(wth.dt_txt) | date }}
{{ city }},
{{ country }}
{{ wth.main.temp - 273.15 | number: "1.1-1" }}°C
{{ wth.weather[0].description | titlecase }}
Humidity {{ wth.main.humidity }}%
Wind
{{ wth.wind.speed }} km/h
Pressure
{{ wth.main.pressure }}
天气.component.css
.col-md-3 {
margin: 5px auto;
}
.input {
margin: 2% 25%;
padding: 2% 2.5%;
font-size: 16px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
transition: 0.3s;
}
input {
padding: 10px 12px;
}
.weather-info {
width: 100%;
height: 100%;
padding: 20px 20px;
border-radius: 8px;
border: 2px solid #fff;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
background: linear-gradient(to right, #00a4ff, #0072ff);
transition: transform 0.2s ease;
color: whitesmoke;
}
.info-date {
display: flex;
flex-direction: column;
justify-content: center;
}
.info-date h1 {
margin-bottom: 0.65rem;
font-size: 2rem;
letter-spacing: 2px;
}
.info-weather {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
text-align: right;
}
.weather-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}
@keyframes animation-icon {
from {
transform: scale(1);
}
to {
transform: scale(1.2);
}
}
.weather-type {
display: inline-block;
width: 48px;
height: 48px;
transition: all 0.2s ease-in;
animation: animation-icon 0.8s infinite;
animation-timing-function: linear;
animation-direction: alternate;
}
.weather-temperature {
font-size: 1.5rem;
font-weight: 800;
}
.weather-description {
margin-top: 1rem;
font-size: 20px;
font-weight: bold;
}
.weather-city {
margin-top: 0.25rem;
font-size: 16px;
}
.wind i {
margin: 10px;
}
天气服务.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class WeatherService {
private readonly apiKey: string = ;
constructor(private _http: HttpClient) {}
getWeather(city: string, country: string) {
const apiUrl =
`https://api.openweathermap.org/data/2.5/forecast?q=${city},${country}&appid=${this.apiKey}`;
return this._http.get(apiUrl);
}
}
现在调用app.component.html中的天气组件
输出:
第 5 步:将 Angular 应用程序转换为 PWA:使用 Angular CLI 可以轻松地将 Angular 应用程序转换为 PWA。导航到您的项目文件夹。现在,运行以下命令来添加 PWA 功能。
ng add @angular/pwa
上面的命令添加了以下新文件:
- 用于 PWA 信息的名为 manifest.webmanifest 的清单文件
- 用于配置 service worker 的 ngsw-config.json 文件
- assets/icons 目录中具有多种尺寸的默认图标(这些图标可以稍后更改)
- 使用 @angular/service-worker 包的服务工作者
现在,让我们看一下每个文件的作用。
manifest.webmanifest
该文件包含应用程序的名称、主题和背景颜色,以及各种大小的图标。当您将应用程序添加到移动设备时应用此配置,它通过将名称和图标添加到应用程序列表来创建 Web 视图,并且当应用程序运行时应用背景和主题颜色。
Javascript
{
"name": "weather-app",
"short_name": "weather-app",
"theme_color": "#1976d2",
"background_color": "#fafafa",
"display": "standalone",
"scope": "./",
"start_url": "./",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
ngsw-config这个文件的存在,一个人将能够管理与 PWA 相关的各种不同的东西。这是我们缓存 API 端点的地方。
Javascript
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}
第 6 步:为生产环境构建我们的 Angular 应用程序:
ng build --prod
运行上述命令后,我们的 build 文件夹会在 dist/weather-app 中创建。现在,使用 cd dist/weather-app 移动到构建文件夹。
cd dist/weather-app
使用 NPM 全局安装http-server包。
npm install -g http-server
您可以在此处找到此天气应用程序的代码。
第 7 步:在桌面上添加我们的 Weather App 图标以启动:在浏览器中启动 Angular 应用程序后,URL 栏右侧会出现一个下载图标,如下所示:
单击安装按钮以在桌面上添加图标以启动应用程序。现在,您单击在桌面上创建的应用程序图标。您将看到以下屏幕。 (注意:当您点击图标时,它不会在浏览器中打开)
卸载 PWA 很容易。只需单击顶部导航中的三个点,然后单击“卸载天气应用程序”。