Angular 项目第三版(一)
原文:
zh.annas-archive.org/md5/a5e00c8078625cc7b0d1e54adc65ace7译者:飞龙
前言
Angular 是一个流行的 JavaScript 框架,可以在包括 Web、桌面和移动在内的广泛平台上运行。它提供了一系列丰富的功能,并拥有广泛的工具,使其在开发者中非常受欢迎。本更新的第三版 Angular Projects 将教你如何使用 Angular 构建高效和优化的 Web 应用程序。
你将从创建 10 个不同的真实世界 Web 应用程序开始,探索框架的基本特性。每个应用程序都将展示如何将 Angular 与不同的库和工具集成。随着你的进步,你将学习如何在构建问题跟踪系统、PWA 天气应用程序、移动照片地理标记应用程序、组件 UI 库等多个令人兴奋的项目的同时,实现流行的技术,如 Angular Router、Scully、Electron、Angular 的服务工作者、Nx 的单仓库工具、NgRx 等。在结论章节中,你将学会使用 schematics 自定义 Angular CLI 命令。
在本书结束时,你将具备根据你或你的客户需求使用各种不同技术构建 Angular 应用程序所需的技能。
本书面向对象
如果你是一位具有 Angular 入门级经验的开发者,并且希望熟练掌握处理 Angular 可能遇到的各种用例的基本工具,那么这本 Angular 开发书籍就是为你准备的。假设你具备 Web 应用程序开发的基础知识,以及使用 ES6 或 TypeScript 的基本经验。
本书涵盖内容
第一章,在 Angular 中创建你的第一个 Web 应用程序,探讨了 Angular 框架的主要特性,并教你了解构成典型 Angular 应用程序的基本构建块。你将研究 Angular 生态系统中可用的不同工具和 IDE 扩展,以增强开发者的工作流程和体验。
第二章,使用 Scully 和 Angular Router 构建 SPA 应用程序,探讨了 Angular 应用程序基于单页应用(SPA)架构,通常我们有多页内容,这些内容由不同的 URL 或路由提供服务。另一方面,Jamstack 是一种新兴的热门技术,它允许你构建快速、静态的网站,并直接从 CDN 上提供服务。在本章中,我们将使用 Angular Router 在 Angular 应用程序中实现路由功能。我们还将使用 Scully,这是 Angular 最佳静态站点生成器,来创建一个采用 Jamstack 架构的个人博客。
第三章,使用响应式表单构建问题跟踪系统,介绍了我们如何构建问题跟踪管理系统,并使用 Angular 响应式表单向系统中添加新问题。我们将使用来自 VMware 的 Clarity Components 设计我们的表单,并包含内置和自定义验证。我们还将对表单中的值变化做出反应,并据此采取行动。
第四章,使用 Angular Service Worker 构建 PWA 天气应用程序,讨论了 Web 应用程序的用户体验对于所有用户来说并不相同,尤其是在网络覆盖和连接性较差的地方。当我们构建 Web 应用程序时,我们应该考虑到所有类型的网络。在本章中,我们将创建一个使用 OpenWeather API 显示指定地区天气的应用程序。我们将学习如何将应用程序部署到 Firebase Hosting。我们还将探索使用 Angular service worker 的 PWA 技术,以在离线时提供无缝的用户体验。
第五章,使用 Electron 构建桌面 WYSIWYG 编辑器,这是一个跨平台的 JavaScript 框架,用于使用 Web 技术构建桌面应用程序。当与 Angular 结合使用时,它可以产生真正高性能的应用。在本章中,我们将创建一个可以在桌面上运行的 WYSIWYG 编辑器。我们将构建一个 Angular 应用程序,并将其与流行的 WYSIWYG Angular 库 ngx-wig 集成,然后使用 Electron 将其打包为桌面应用程序。数据将通过 Node.js API 在文件系统中本地持久化。
第六章,使用 Capacitor 和 3D 地图构建移动照片地理标记应用程序,介绍了 Capacitor,这是 Ionic 框架提供的一项服务,可以将任何 Web 应用程序(如使用 Angular 创建的应用程序)转换为原生应用程序。其主要优势是我们可以使用相同的代码库构建原生移动应用程序和 Web 应用程序。Cesium 是一个流行的 JavaScript 框架,用于构建 3D 地图。在本章中,我们将使用 Capacitor 为我们拍摄的照片构建一个地理标记的移动应用程序。我们将使用各种 Ionic 插件在指定位置拍照并将其持久化到 Cloud Firestore。然后,我们将在 Cesium 3D 查看器中显示所有拍摄照片的列表。
第七章,使用 Angular 为 GitHub 个人资料构建 SSR 应用程序,深入探讨了搜索引擎优化(SEO),这是任何网站当今的一个关键方面。谁不想在通过社交媒体分享时让他们的网站看起来很好看?客户端 Web 应用程序的真正挑战是优化它,这可以通过在服务器上渲染内容来实现。在本章中,我们将学习如何使用 GitHub API 创建 GitHub 个人资料应用程序。然后,我们将在服务器上渲染它,并学习如何将状态传输到浏览器。我们还将看到如何动态设置页面标题和附加元数据。
第八章,使用 Nx Monorepo 工具和 NgRx 构建 Enterprise Portal,介绍了 monorepo 架构,这是一种在单个存储库下处理多个应用程序时流行的技术,它为开发过程提供了速度和灵活性。在本章中,我们将使用 Nx monorepo 开发工具创建两个门户:一个用于最终用户,他们可以在地图上选择一个兴趣点(POI)并访问它;另一个用于管理员检查特定 POI 的访问统计信息。应用程序状态使用 NgRx 进行管理。
第九章,使用 Angular CLI 和 Angular CDK 构建组件 UI 库,讨论了企业组织通常需要跨不同 Web 应用程序使用的自定义 UI 库。Angular CDK 提供了创建可访问性和高性能 UI 组件的广泛功能。在本章中,我们将使用 Angular CDK 和 Bulma CSS 框架创建两个不同的组件。我们还将将它们打包成一个单一的 Angular 库,并学习如何在 npm 上发布它们,以便在不同的应用程序中重用。我们还将研究如何将每个组件用作 Angular 元素。
第十章,使用 Schematics 自定义 Angular CLI 命令,介绍了组织在创建 Angular 实体(如组件或服务)时通常遵循不同的指南。Angular Schematics 可以通过扩展 Angular CLI 命令和提供自定义自动化来帮助他们。在本章中,我们将学习如何使用 Angular Schematics API 来构建我们自己的命令集,用于生成组件和服务。我们将构建一个用于创建包含 Tailwind CSS 框架的 Angular 组件的 schematic,我们还将构建一个默认使用内置 HTTP 客户端的 Angular 服务。
为了充分利用本书
您需要在您的计算机上安装 Angular 16 的版本,最好是最新版本。所有代码示例都已在 Windows 操作系统上的 Angular 16.0.0 上进行过测试,但它们也应该适用于 Angular 16 的任何未来版本。
下载示例代码文件
本书代码包托管在 GitHub 上,网址为 github.com/PacktPublishing/Angular-Projects-Third-Edition。我们还有其他来自我们丰富图书和视频目录的代码包,可在 github.com/PacktPublishing/ 找到。查看它们吧!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/UbmtQ。
使用的约定
本书使用了多种文本约定。
CodeInText: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。例如:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”
代码块按以下方式设置:
getWeather(city: string): Observable<Weather> {
const options = new HttpParams()
.set('units', 'metric')
.set('q', city)
.set('appId', this.apiKey);
return this.http.get<Weather>(this.apiUrl + 'weather', { params: options });
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
**import** **{** **HttpClientModule** **}** **from****'@angular/common/http'****;**
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
任何命令行输入或输出都按以下方式编写:
ng generate service weather
粗体: 表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词在文本中显示如下。例如:“从管理面板中选择系统信息。”
警告或重要提示看起来像这样。
小贴士和技巧看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈: 发送电子邮件至feedback@packtpub.com,并在邮件主题中提及本书的标题。如果您对本书的任何方面有疑问,请通过questions@packtpub.com与我们联系。
勘误: 尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/submit-errata,点击提交勘误,并填写表格。
盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过copyright@packtpub.com与我们联系,并提供材料的链接。
如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《Angular Projects,第三版》,我们很乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。
下载本书的免费 PDF 副本
感谢您购买本书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走?您的电子书购买是否与您选择的设备不兼容?
不要担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何地点、任何设备上阅读。直接从您喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠不会就此停止,您还可以获得独家折扣、时事通讯和每日收件箱中的精彩免费内容。
按照以下简单步骤获取好处:
- 扫描二维码或访问下面的链接
packt.link/free-ebook/9781803239118
-
提交您的购买证明
-
就这样!我们将直接将您的免费 PDF 和其他优惠发送到您的邮箱
第一章:在 Angular 中创建您的第一个 Web 应用程序
Angular 是一个流行的现代 JavaScript 框架,可以在不同的平台上运行,包括网络、桌面和移动。Angular 应用程序是用 TypeScript 编写的,它是 JavaScript 的超集,提供了诸如强类型和面向对象技术之类的语法糖。
Angular 应用程序是使用 Angular 团队制作的命令行工具 Angular CLI 创建和开发的。它自动化了许多开发任务,如脚手架、测试和部署 Angular 应用程序,这些任务手动配置将花费大量时间。
Angular 框架的流行度在很大程度上反映了其广泛的工具支持。Visual Studio Code (VS Code) 编辑器包含各种扩展,这些扩展在处理 Angular 时可以增强开发体验。
在本章中,我们将涵盖以下主题:
-
Angular CLI 简介
-
探索 VS Code 中丰富的 Angular 工具生态系统
-
创建我们的第一个 Angular 应用程序
-
与 Angular 框架交互
-
使用 Nx Console 自动化 Angular CLI 命令
必要的背景理论和上下文
Angular 框架是一个跨平台 JavaScript 框架,可以在各种环境中运行,包括网络、服务器、移动和桌面。它由一系列 JavaScript 库组成,我们可以使用这些库来构建高性能和可扩展的 Web 应用程序。Angular 应用程序的架构基于组件的分层表示。组件是 Angular 应用程序的基本构建块。它们代表并控制网页的特定部分,称为 视图。以下是一些组件的示例:
-
一系列博客文章
-
一个问题报告表单
-
一个天气显示小部件
Angular 应用程序组件可以按逻辑组织成树状结构:
图 1.1 – 组件树
按照惯例,Angular 应用程序通常有一个主组件,称为 AppComponent。树中的每个组件都可以通过应用程序编程接口与其兄弟组件进行通信和交互,该接口由每个组件定义。
Angular 应用程序可以有许多被称为 模块 的功能。每个模块对应于特定应用程序领域或工作流程的单个功能块。Angular 模块用于将具有相似功能的 Angular 组件分组:
图 1.2 – 模块层次结构
在前面的图中,虚线圆圈代表 Angular 模块。按照惯例,Angular 应用程序通常有一个主模块,称为 AppModule。如果模块希望使用其功能的一部分,它们可以导入 Angular 应用程序中的其他模块。
模块的功能可以进一步分析为特性的展示逻辑和业务逻辑。Angular 组件应仅处理展示逻辑,并将业务逻辑任务委托给服务。Angular 框架通过内置的**依赖注入(DI)**机制为组件提供 Angular 服务。
Angular DI 框架使用特殊用途的对象,称为注入器,来隐藏向 Angular 应用程序提供依赖项的大部分复杂性。组件不需要知道 Angular 服务的实际实现。它们只需要从注入器请求即可。
一个 Angular 服务应遵循单一职责原则,并且不要跨越不同模块之间的边界。以下是一些服务的示例:
-
使用 HTTP 协议从后端 API 访问数据
-
与浏览器本地存储的交互
-
错误日志记录
-
数据转换
在构建 Angular 应用程序时,Angular 开发者不需要记住如何创建组件、模块和服务。幸运的是,Angular CLI 可以通过提供命令行界面来帮助我们完成这些任务。
Angular CLI 简介
Angular CLI 是 Angular 团队创建的一个工具,它改善了构建 Angular 应用程序的开发者体验。它隐藏了搭建和配置 Angular 应用程序的复杂性,同时允许开发者专注于他们最擅长的事情——编码!在我们开始使用 Angular CLI 之前,我们需要在我们的系统中设置以下先决条件:
-
Node.js:建立在 Chrome v8 引擎之上的 JavaScript 运行时。您可以从
nodejs.org下载任何**长期支持(LTS)**版本。 -
npm:Node.js 运行的包管理器。
然后,我们可以使用命令行中的 npm 安装 Angular CLI:
npm install -g @angular/cli
我们使用 -g 选项全局安装 Angular CLI,因为我们希望从任何操作系统路径创建 Angular 应用程序。
在某些操作系统中安装 Angular CLI 可能需要管理员权限。
要验证 Angular CLI 是否已正确安装,我们可以在命令行中运行以下命令:
ng version
之前的命令将报告我们系统中安装的 Angular CLI 版本。Angular CLI 通过 ng 命令提供命令行界面,这是 Angular CLI 的二进制可执行文件。它可以接受各种选项,包括以下内容:
-
serve:构建并服务 Angular 应用程序。 -
build:构建 Angular 应用程序。 -
test:运行 Angular 应用程序的单元测试。 -
generate:生成新的 Angular 实体,例如组件或模块。 -
add:安装与 Angular 框架兼容的第三方库。 -
new:创建新的 Angular 应用程序。
之前提到的选项是最常见的。如果您想查看所有可用的命令,请在命令行中执行以下命令:
ng help
之前的命令将显示 Angular CLI 支持的所有命令列表。
Angular 工具生态系统充满了扩展和实用工具,可以在我们开发 Angular 应用程序时帮助我们。在下一节中,我们将了解其中一些与 VS Code 一起工作的扩展。
探索 VS Code 中 Angular 工具的丰富生态系统
在VS Code Marketplace中有许多扩展可用,可以增强 Angular 工具生态系统。在本节中,我们将了解其中最受欢迎的扩展,这些扩展可以显著帮助我们进行 Angular 开发:
-
Nx Console
-
Angular 语言服务
-
Angular Snippets
-
Angular Evergreen
-
Material Icon Theme
前面的列表并不全面;一些扩展已经包含在Angular Essentials扩展包中。然而,您可以在marketplace.visualstudio.com/search?term=angular&target=VSCode上浏览更多 VS Code 的 Angular 扩展。
Nx Console
Nx Console 是由 Nrwl 团队开发的一个 VS Code 扩展,它提供了一个图形用户界面,用于覆盖 Angular CLI。它包含大多数 Angular CLI 命令,并使用 Angular CLI 内部执行每个命令。我们将在“使用 Nx Console 构建我们的应用程序”部分了解更多关于这个扩展的信息。
Angular Language Service
Angular 语言服务扩展在编辑 Angular 应用程序中的 HTML 模板时提供了各种增强功能,包括以下内容:
-
代码自动补全
-
编译错误信息
-
跳转到定义技术
代码自动补全是帮助我们找到在输入 HTML 内容时使用正确属性或方法的功能。它通过在我们开始输入时显示建议列表来实现:
图 1.3 – 代码补全
在前面的屏幕截图中,当我们开始输入单词descr时,Angular 语言服务建议使用description组件属性。请注意,代码补全仅适用于组件中的公共属性和方法。
在开发 Web 应用程序时,最常见的问题之一是在应用程序达到生产状态之前检测到错误。这个问题可以通过 Angular 编译器部分解决,该编译器在构建用于生产的 Angular 应用程序时启动。此外,Angular 语言服务可以通过在应用程序达到编译过程之前显示编译错误信息来进一步解决这个问题:
图 1.4 – 编译错误信息
例如,如果我们不小心拼错了组件的属性或方法名称,Angular 语言服务将显示适当的错误信息。
Angular Snippets
Angular Snippets扩展包含了一组 Angular 代码片段,用于 TypeScript 和 HTML。在 TypeScript 中,我们可以使用它在一个空白 TypeScript 文件中创建组件、模块或服务:
图 1.5 – 新 Angular 组件片段
在 HTML 模板中,我们可以使用此扩展来创建有用的 Angular 元素,例如 *ngFor 指令,以在 HTML 中循环列表:
图 1.6 – *ngFor 碎片
由于 Angular CLI 的广泛流行和功能,使用它来在 TypeScript 中生成 Angular 元素看起来更方便。然而,Angular Snippets 在 HTML 部分做得很好,那里有更多需要记住的内容。
Angular Evergreen
使 Angular 框架如此稳定的一个主要因素是它遵循基于语义版本控制的定期发布周期。如果我们希望我们的 Angular 应用程序充满最新功能和修复,我们必须定期更新它们。但如何最有效地保持更新呢?我们可以使用 Angular Evergreen 扩展!
它比较 Angular CLI 项目的 Angular 和 Angular CLI 版本与最新版本,并提醒您是否需要更新:
图 1.7 – Angular Evergreen
它提供了一个易于使用的用户界面来执行以下命令:
-
将 Angular 依赖项升级到 最新 版本
-
将 Angular 依赖项升级到 下一个 版本
-
升级所有 npm 依赖项
Angular Evergreen 是始终与您的 Angular 项目保持更新的完美扩展。
材料图标主题
列表中最后一个扩展在提高开发者生产力方面添加的价值很小。相反,它通过修改 VS Code 的图标主题来关注可发现性和美学观点。
材料图标主题包含大量基于 Google 材料设计的图标。它可以理解项目中每种文件类型并自动显示相关图标。例如,Angular 模块用红色 Angular 图标表示,而组件则用蓝色 Angular 图标表示。
VS Code 有一个默认的文件图标主题,称为 Seti。一旦您安装了材料图标主题,它将提示您选择您想要激活的主题:
图 1.8 – 选择文件图标主题
选择 材料图标主题将自动更新当前 Angular 项目的图标。
材料图标主题已安装并全局应用于 VS Code,因此您无需为每个 Angular CLI 项目单独激活它。
现在,当您打开您的 Angular 项目时,您将一眼就能理解每个文件的类型,即使其名称没有完全显示在屏幕上。
项目概述
在这个项目中,我们将使用 Angular CLI 从头开始创建一个新的 Angular 应用程序。然后,我们将与 Angular 框架的核心功能交互,对我们的应用程序进行简单的更改。最后,我们将学习如何使用 Nx Console 扩展来构建和托管我们的应用程序。
构建时间:15 分钟。
入门
完成此项目所需的软件工具如下:
-
Git:一个免费且开源的分布式版本控制系统。您可以从
git-scm.com下载它。 -
VS Code:一个您可以从
code.visualstudio.com下载的代码编辑器。 -
Angular CLI:我们在 必要背景理论及环境 部分介绍了 Angular 的命令行界面。
-
GitHub 资源:本章的代码,您可以在
github.com/PacktPublishing/Angular-Projects-Third-Edition的Chapter01文件夹中找到。
创建我们的第一个 Angular 应用程序
要创建一个全新的 Angular 应用程序,我们必须执行 Angular CLI 的 ng new 命令,并将应用程序名称作为选项传递:
ng new my-app
ng new 命令用于创建新的 Angular 应用程序或新的 Angular 工作空间。Angular 工作空间是一个包含一个或多个 Angular 应用程序的 Angular CLI 项目,其中一些可以是 Angular 库。因此,当我们执行 ng new 命令时,我们默认创建一个包含 Angular 应用程序的 Angular 工作空间。
在前面的命令中,我们的 Angular 应用程序名称是 my-app。执行命令后,Angular CLI 将提出一些问题,尽可能收集有关我们想要创建的应用程序性质的信息:
-
初始时,它会询问我们是否想启用 Angular 分析:
Would you like to share pseudonymous usage data about this project with the Angular Team at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more details and how to change this setting, see https://angular.io/analytics. (y/N)Angular CLI 只会在我们创建第一个 Angular 项目时询问一次前面的问题,并将其全局应用于系统。然而,我们可以在特定的 Angular 工作空间中稍后更改设置。
-
接下来,它将询问我们是否想在 Angular 应用程序中启用路由:
Would you like to add Angular routing? (y/N)Angular 中的路由全部关于使用 URL 在 Angular 应用程序组件之间导航。我们在这个项目中不关心路由,所以按 Enter 接受默认值。
-
然后,Angular CLI 会提示我们选择我们想要在 Angular 应用程序中使用的样式格式:
Which stylesheet format would you like to use? (Use arrow keys)
从可用的样式表列表中选择一个格式并按 Enter。
Angular CLI 启动您的 Angular 应用程序的创建过程,该过程包括以下步骤:
-
为典型的 Angular CLI 项目搭建必要的文件夹结构
-
安装所需的 npm 依赖项和 Angular 包
-
在 Angular CLI 项目中初始化 Git
这个过程可能需要一些时间,具体取决于您的网络速度。一旦完成,您应该在运行ng new Angular CLI 命令的路径中看到一个名为my-app的新文件夹。
现在,运行我们的 Angular 应用程序并看到它实际运行的时刻终于到来了:
-
打开一个终端窗口并导航到
my-app文件夹。 -
运行以下 Angular CLI 命令:
ng serve上述命令将构建 Angular 应用程序并启动一个内置的 Web 服务器,我们可以使用它来预览应用程序。Web 服务器以监视模式启动;每当我们的代码发生变化时,它会自动重新构建 Angular 应用程序。第一次构建 Angular 应用程序时,完成需要相当长的时间,因此我们必须有耐心。当我们在终端窗口中看到以下消息时,我们知道过程已经完成且没有错误:
图 1.9 – Angular 构建输出
-
启动您喜欢的浏览器并导航到
http://localhost:4200以预览您全新的 Angular 应用程序:
图 1.10 – 最小 Angular 应用程序
Angular CLI 默认创建一个最小的 Angular 应用程序,为我们提供 Angular 项目的起点。它包含一些现成的 CSS 样式和 HTML 内容,我们将在下一节中学习如何根据我们的规格进行更改。
与 Angular 框架交互
在使用 Angular 时,真正的乐趣在于我们开始与框架本身打交道。毕竟,理解 Angular 的工作原理和编写应用程序代码才是最重要的。
应用程序源代码位于 Angular CLI 项目的根目录下的src\app文件夹中。它包含构建和测试我们的 Angular 应用程序所需的所有文件,包括一个组件和一个模块。组件是 Angular 应用程序的主要组件:
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'my-app';
}
以下属性描述了 Angular 组件:
-
selector:用于在 HTML 内容中标识和声明组件的唯一名称。它是一个 HTML 标签,就像任何原生 HTML 元素一样,例如<app-root></app-root>。Angular CLI 默认在组件选择器中提供
app-前缀。在从头创建新的 Angular CLI 应用程序时,我们可以使用--prefix选项来使用自定义前缀。自定义前缀可以基于组织名称或特定产品的名称,这有助于避免与其他库或模块冲突。 -
templateUrl:指向包含组件 HTML 内容的 HTML 文件的路径,称为组件模板。 -
styleUrls:指向包含组件 CSS 样式的样式表文件的路径列表。
前面的属性使用 @Component 装饰器定义。它是一个装饰 TypeScript 类的函数,并识别它为 Angular 组件。AppComponent 类的 title 属性是一个包含字符串值的公共属性,可以在组件模板中使用。
我们的 Angular 应用程序的主模块使用一个类似的装饰器 @NgModule 来定义其属性:
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Angular 模块的装饰器定义了一组可以用于配置模块的属性。最常见的一些如下:
-
declarations:定义属于 Angular 模块的 Angular 组件。Angular 模块中存在的每个组件必须添加到declarations数组中。 -
imports:定义包含 Angular 模块所需功能的其他 Angular 模块。
现在,让我们通过修改我们的 Angular 应用程序的代码来试试水。我们将更改以下在应用程序启动时显示的问候信息,使其更有意义:
图 1.11 – 欢迎信息
首先,我们需要找到上一张截图中的消息声明位置。Angular 应用程序的主组件是默认在应用程序启动时加载的组件。
应用程序主模块的 bootstrap 属性指示在 Angular 应用程序启动时显示的组件。我们很少需要更改此属性。该组件的选择器默认在 index.html 文件中使用。
因此,消息应该在 app.component.ts 文件中声明。让我们看一下:
-
打开 VS Code 编辑器并从主菜单中选择 文件 | 打开文件夹…。
-
找到我们创建的 Angular 应用程序的
my-app文件夹并选择它。 -
从 资源管理器 窗格导航到
src\app文件夹并选择app.component.ts文件。 -
在
AppComponent类中找到title属性并将其值更改为Angular Projects:title = '**Angular Projects**'; -
如果应用程序没有运行,请在终端窗口中运行
ng serve,然后使用浏览器导航到http://localhost:4200。我们的 Angular 应用程序现在应该显示以下问候信息:
图 1.12 – 欢迎信息
title 属性绑定到主组件的模板。如果我们打开 app.component.html 文件并转到第 344 行,我们将看到以下 HTML 代码:
<span>{{ title }} app is running!</span>
围绕 title 属性的 {{}} 语法称为插值。在插值过程中,Angular 框架读取包含的组件属性值,将其转换为文本,并在屏幕上打印出来。
Angular CLI 提供了丰富的命令集合,以协助我们在日常开发过程中。然而,许多开发者发现使用命令行很困难,更倾向于图形化方法。在下一节中,我们将学习如何使用 Nx 控制台,它是 Angular CLI 的图形用户界面。
使用 Nx Console 自动化 Angular CLI 命令
Angular CLI 是一个具有各种命令的命令行工具。每个命令可以根据我们想要完成的任务接受广泛的各种选项和参数。记住这些命令及其选项是一项艰巨且耗时的工作。在这种情况下,Angular 工具生态系统可以派上用场。VS Code 市场包含许多有用的扩展,我们可以安装它们来帮助我们进行 Angular 开发。其中之一就是 Nx 控制台,它提供了一个 Angular CLI 的用户界面。要在您的环境中安装 Nx 控制台,请按照以下步骤操作:
- 打开 VS Code 并在侧边栏中点击 扩展 菜单:
图 1.13 – VS Code 扩展
-
在出现的 扩展 面板中,键入
Nx Console。 -
在第一个项目上点击 安装 按钮来安装 Nx Console 扩展。
Nx Console 扩展现在已在全球环境中安装,因此我们可以在任何 Angular 项目中使用它。它是最常见的 Angular CLI 命令的图形表示。目前,它支持以下命令(括号中显示的是相关的 Angular CLI 命令):
-
生成:生成新的 Angular 艺术品,如组件和模块(
ng generate)。 -
运行:运行在 Angular CLI 工作区的
angular.json配置文件中定义的架构目标(ng run)。 -
构建:构建 Angular 应用程序(
ng build)。 -
运行:构建并运行 Angular 应用程序(
ng serve)。 -
测试:运行 Angular 应用程序的单元测试(
ng test)。
Nx 控制台几乎可以实现我们使用 Angular CLI 可以做到的所有事情。真正的好处是开发者不需要记住所有 Angular CLI 命令选项,因为它们都在图形界面中得到了表示。让我们看看它是如何做到的:
- 使用 VS Code 打开
my-app文件夹,并在侧边栏中点击 Nx 控制台 菜单:
图 1.14 – Nx 控制台
- 从 项目 面板中选择 运行 命令,然后点击 播放 按钮来执行它:
图 1.15 – serve 命令
- VS Code 在编辑器底部打开一个集成终端并执行 ng serve 命令:
图 1.16 – VS Code 集成终端
这是我们从终端窗口使用 Angular CLI 时运行的相同命令。
Nx 控制台内部使用 任务 来运行 Angular CLI 命令。任务是 VS Code 的内置机制,允许我们运行脚本或启动外部进程,而无需直接与命令行交互。
Nx 控制台扩展出色地减轻了记住 Angular CLI 命令的负担。VS Code 市场上有许多针对 Angular 开发者的扩展,这些扩展补充了 Nx 控制台的工作。
摘要
在本章中,我们学习了 Angular 框架的基本原则,并简要概述了 Angular 架构。我们看到了一些流行的 VS Code 扩展,我们可以使用这些扩展来增强我们在使用 Angular 进行开发时的体验。
然后,我们学习了如何使用 Angular CLI,这是 Angular 生态系统中的一个强大工具,可以从头开始构建新的 Angular 应用程序。我们还通过修改典型 Angular CLI 应用程序的 Angular 组件,首次与 Angular 代码进行了交互。最后,我们安装了 Nx 控制台扩展,并学习了如何构建我们的应用程序。
在下一章中,我们将探讨 Angular 路由,并学习如何使用它来创建个人博客,使用 Scully 静态网站生成器。
实践问题
让我们看看几个实践问题:
-
Angular 应用程序的基本构建块是什么?
-
我们如何将功能相似的组件分组?
-
在 Angular 应用程序中谁处理业务逻辑任务?
-
我们可以使用哪个 Angular CLI 命令来创建新的 Angular 应用程序?
-
我们可以使用哪个 Angular CLI 命令来提供 Angular 应用程序?
-
我们如何在 HTML 中声明 Angular 组件?
-
我们如何在模块中声明 Angular 组件?
-
我们在 HTML 模板上绑定文本时使用什么语法?
-
使用 Nx 控制台有什么好处?
-
我们在 Angular 代码中用哪个扩展来执行静态分析?
进一步阅读
这里有一些链接,可以帮助我们巩固本章所学的内容:
-
基本 Angular 概念简介:
angular.io/guide/architecture -
Nx 控制台:
nx.dev/core-features/integrate-with-editors#vscode-plugin:-nx-console -
Angular 精要:
marketplace.visualstudio.com/items?itemName=johnpapa.angular-essentials -
Angular Evergreen:
expertlysimple.io/get-evergreen
第二章:使用 Scully 和 Angular Router 构建 SPA 应用程序
Angular 应用程序遵循**单页应用程序(SPA)**架构,其中可以使用浏览器中的 URL 激活网页的不同视图。任何对该 URL 的更改都可以被 Angular 路由器拦截并转换为可以激活特定 Angular 组件的路由。
Scully是一个流行的基于Jamstack架构的静态网站生成器。它可以很好地与 Angular 路由器合作,根据每个路由预渲染 Angular 应用程序的内容。
在本章中,我们将结合 Angular 和 Scully 创建一个个人博客。以下主题将被涵盖:
-
在 Angular 应用程序中设置路由
-
创建我们博客的基本布局
-
配置我们应用程序的路由
-
使用 Scully 添加博客功能
-
在主页上显示博客文章
必要的背景理论和上下文
在 Web 开发的早期,客户端应用程序与底层服务器基础设施高度耦合。当我们想要通过 URL 访问网站页面时,涉及到许多机械操作。
浏览器会将请求的 URL 发送到服务器,服务器应该响应与该 URL 匹配的 HTML 文件。这是一个复杂的过程,会导致延迟和往返时间的差异。
现代 Web 应用程序使用 SPA 架构消除了这些问题。客户端只需要从服务器请求一次单个 HTML 文件。浏览器 URL 的任何后续更改都由客户端基础设施内部处理。在 Angular 中,路由器负责拦截应用程序内的 URL 请求并根据定义的路由配置处理它们。
Jamstack 是一种热门的新兴技术,允许我们创建快速和安全的 Web 应用程序。它可以用于任何应用程序类型,从电子商务网站到**软件即服务(SaaS)**Web 应用程序,甚至个人博客。Jamstack 的架构基于以下支柱:
-
性能:页面在生产过程中生成和预渲染,消除了等待内容加载的需要。
-
扩展性:内容是静态文件,可以从任何地方提供,甚至可以从提高应用程序性能的**内容分发网络(CDN)**提供商那里提供。
-
安全性:服务器端过程的无服务器性质以及内容已经是静态的事实消除了针对服务器基础设施的潜在攻击。
Scully 是第一个采用 Jamstack 方法的 Angular 静态网站生成器。它本质上在构建时生成 Angular 应用程序的页面,以便在请求时立即可用。
项目概述
在这个项目中,我们将使用 Angular 框架构建一个个人博客,并使用 Scully 网站生成器增强其 Jamstack 特性。最初,我们将构建一个新的 Angular 应用程序并启用其路由功能。然后,我们将通过添加一些基本组件来创建我们应用程序的基本布局。一旦我们有一个可工作的 Angular 应用程序,我们将使用 Scully 为其添加博客支持。然后,我们将使用 Markdown 文件创建一些博客文章,并在我们应用程序的首页上显示它们。以下图表展示了项目的架构概述:
图 2.1 – 项目架构
构建时间:1 小时。
入门
完成此项目所需的以下软件工具:
-
Angular CLI:Angular 的命令行界面,您可以在
angular.io/cli找到。 -
GitHub 资料库:本章的相关代码,您可以在
github.com/PacktPublishing/Angular-Projects-Third-Edition的Chapter02文件夹中找到。
在 Angular 应用程序中设置路由
我们将通过从头开始创建一个新的 Angular 应用程序来启动我们的项目。在终端窗口中执行以下 Angular CLI 命令以创建一个新的 Angular 应用程序:
ng new my-blog --routing --style=scss
我们使用 ng new 命令创建一个新的 Angular 应用程序,传递以下选项:
-
my-blog: 我们想要创建的 Angular 应用程序的名称。Angular CLI 将在执行命令的路径中创建一个my-blog文件夹。在终端窗口中运行的每个命令都应该在这个文件夹内执行。
-
--routing: 启用 Angular 应用程序中的路由功能。 -
--style=scss: 配置 Angular 应用程序在处理 CSS 样式时使用 SCSS 样式表格式。
当我们在 Angular 应用程序中启用路由时,Angular CLI 将从 @angular/router npm 包中导入几个工件到我们的应用程序中:
-
它创建了
app-routing.module.ts文件,这是我们的应用程序的主要路由模块:import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } -
它将
AppRoutingModule导入我们的应用程序的主要模块app.module.ts:import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; **import** **{** **AppRoutingModule** **}** **from****'./app-routing.module'****;** import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, **AppRoutingModule** ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
我们配置了我们的应用程序以使用 SCSS 样式表格式。我们不会手动创建应用程序的样式,而是将使用 Bootstrap CSS 库:
-
在终端窗口中执行以下命令以安装 Bootstrap:
npm install bootstrap在前面的命令中,我们使用
npm可执行文件从 npm 注册表中安装bootstrap包。 -
在我们 Angular 应用程序的
src文件夹中存在的styles.scss文件顶部添加以下import语句:@import "bootstrap/scss/bootstrap";
@import CSS rule accepts the absolute path of the bootstrap.scss file as an option without adding the extension.
在以下部分,我们将学习如何通过创建组件(如页眉和页脚)来创建我们博客的基本布局。
创建我们博客的基本布局
一个博客通常包含一个包含所有主要网站链接的标题,以及一个包含版权信息和其他有用链接的页脚。在 Angular 的世界里,这两个都可以表示为单独的组件。
标题组件仅使用一次,因为它在我们应用程序启动时添加,并且始终作为网站的主菜单进行渲染。在 Angular 中,我们通常创建一个名为core的模块,按照惯例,以保持此类组件或服务在我们的应用程序中的中心位置。要创建模块,我们使用 Angular CLI 的generate命令:
ng generate module core
之前的命令将在我们的应用程序的src\app\core文件夹中创建模块。要创建标题组件,我们将使用相同的命令,传递不同的选项集:
ng generate component header --path=src/app/core --module=core --export
之前的命令将在src\app\core\header文件夹内创建所有必要的组件文件。它还会在core.module.ts文件中声明HeaderComponent,并将其添加到exports属性中,以便其他模块可以使用它:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
**import** **{** **HeaderComponent** **}** **from****'./header/header.component'****;**
@NgModule({
declarations: [
**HeaderComponent**
],
imports: [
CommonModule
],
**exports****: [**
**HeaderComponent**
**]**
})
export class CoreModule { }
标题组件应显示我们博客的主要链接。打开标题组件的header.component.html模板文件,并用以下片段替换其内容:
<nav class="navbar navbar-expand navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand">Angular Projects</a>
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link">Articles</a>
</li>
<li class="nav-item">
<a class="nav-link">Contact</a>
</li>
</ul>
</div>
</nav>
页脚组件可以在 Angular 应用程序中使用多次。目前,我们希望在应用程序的主页上显示它。在未来,我们可能还希望在可供博客访客使用的登录页上显示它。在这种情况下,页脚组件应该是可重用的。当我们想要将将在整个应用程序中重用的组件分组时,我们通常按照惯例创建一个名为shared的模块。使用 Angular CLI 的generate命令来创建模块:
ng generate module shared
之前的命令将在src\app\shared文件夹中创建shared模块。现在,可以使用以下命令创建页脚组件:
ng generate component footer --path=src/app/shared --module=shared --export
之前的命令将在src\app\shared\footer文件夹内创建页脚组件的所有必要文件。它还会在shared.module.ts文件的declarations和exports属性中添加FooterComponent:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
**import** **{** **FooterComponent** **}** **from****'./footer/footer.component'****;**
@NgModule({
declarations: [
**FooterComponent**
],
imports: [
CommonModule
],
**exports****: [**
**FooterComponent**
**]**
})
export class SharedModule { }
页脚组件的内容应包含关于我们博客的版权信息。
让我们看看如何将此信息添加到我们的组件中:
-
打开
footer.component.ts文件,在FooterComponent类中添加一个currentDate属性,并将其初始化为一个新的Date对象:currentDate = new Date(); -
打开页脚组件的
footer.component.html模板文件,并用以下内容替换其内容:<nav class="navbar fixed-bottom navbar-light bg-light"> <div class="container-fluid"> <p>Copyright @{{currentDate | date: 'y'}}. All Rights Reserved</p> </div> </nav>
之前的代码使用插值来在屏幕上显示currentDate属性的值。它还使用内置的date管道来仅显示当前日期的年份。
管道是 Angular 框架的内置功能,它对组件属性的可视表示形式应用转换。属性的底层值保持不变。
我们已经创建了博客的必要组件。现在,是时候在屏幕上显示它们了:
-
打开应用程序的主模块,即
app.module.ts文件,并将CoreModule和SharedModule添加到@NgModule装饰器的imports属性中:@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, **CoreModule****,** **SharedModule** ], providers: [], bootstrap: [AppComponent] }) -
在文件的顶部为每个模块添加适当的
import语句:import { CoreModule } from './core/core.module'; import { SharedModule } from './shared/shared.module'; -
打开主组件的
app.component.html模板文件,并用以下 HTML 片段替换其内容:<app-header></app-header> <app-footer></app-footer>
在前面的片段中,我们通过使用它们的 CSS 选择器添加了标题和页脚组件。
如果我们运行 Angular CLI 的 serve 命令来预览应用程序,我们应该得到以下结果:
图 2.2 – 基本布局
我们已经完成了我们博客应用程序的基本布局,看起来很棒!但是标题中包含两个我们尚未覆盖的额外链接。我们将在下一节中学习如何使用路由来激活这些链接。
为我们的应用程序配置路由
我们在前一节中创建的标题组件包含两个链接:
-
文章: 显示博客文章列表
-
联系: 显示博客所有者的个人信息
之前的链接也将成为我们应用程序的主要功能。因此,我们需要为每个功能创建一个 Angular 模块。
当你设计你的网站并需要决定将使用哪些 Angular 模块时,查看网站的主菜单。菜单中的每个链接都应该是一个不同的功能,因此是一个不同的 Angular 模块。
按照惯例,包含特定功能功能的 Angular 模块被称为 功能模块。
创建联系页面
让我们从创建我们的联系功能开始:
-
创建一个将成为我们联系功能家的模块:
ng generate module contact -
创建一个将成为
contact模块主要组件的组件:ng generate component contact --path=src/app/contact --module=contact --export --flat我们将
--flat选项传递给generate命令,这样 Angular CLI 就不会为我们的组件创建一个单独的文件夹,就像之前的例子一样。由于contact组件将是我们的模块中唯一的组件,所以没有必要单独创建它。 -
打开
contact.component.html文件并添加以下 HTML 内容:<div class="card mx-auto text-center border-light" style="width: 18rem;"> <img src="img/angular.png" class="card-img-top" alt="Angular logo"> <div class="card-body"> <h5 class="card-title">Angular Projects</h5> <p class="card-text"> A personal blog created with the Angular framework and the Scully static site generator </p> <a href="https://angular.io/" target="_blank" class="card-link">Angular</a> <a href="https://scully.io/" target="_blank" class="card-link">Scully</a> </div> </div>
在前面的代码中,我们使用了 angular.png 图像,你可以在随附的 GitHub 仓库项目的 src\assets 文件夹中找到它。
Angular CLI 项目的 assets 文件夹用于静态内容,如图像、字体或 JSON 文件。
我们已经创建了我们的联系功能。下一步是将它添加到我们的 Angular 应用程序的主页上:
-
打开
app-routing.module.ts文件,并在routes属性中添加一个新的路由配置对象:import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; **import** **{** **ContactComponent** **}** **from****'****./contact/contact.component'****;** const routes: Routes = [ **{** **path****:** **'contact'****,** **component****:** **ContactComponent** **}** ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }上述代码表明,当浏览器的 URL 指向
contact路径时,我们的应用程序将激活并在屏幕上显示ContactComponent。路由模块的routes属性包含相应功能模块的路由配置。它是一个路由配置对象数组,其中每个对象定义了组件类和激活它的 URL 路径。 -
在
AppModule的@NgModule装饰器的imports数组中添加ContactModule,以便能够使用它:@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AppRoutingModule, CoreModule, SharedModule, **ContactModule** ], providers: [], bootstrap: [AppComponent] })不要忘记在文件顶部添加对
ContactModule的相应import语句。 -
路由组件,就像
ContactComponent一样,需要一个可以加载的地方。打开app.component.html文件,并添加<router-outlet>指令:<app-header></app-header> **<****div****class****=****"****container"****>** **<****router-outlet****></****router-outlet****>** **</****div****>** <app-footer></app-footer>
现在,我们需要将我们创建的路由配置与页眉组件的实际链接连接起来:
-
打开
header.component.html文件,并将routerLink指令添加到相应的锚 HTML 元素中:<li class="nav-item"> <a **routerLink****=****"/contact"****routerLinkActive****=****"active"** class="nav-link">Contact</a> </li>
routerLink directive points to the path property of the route configuration object. We have also added the routerLinkActive directive, which sets the active class on the anchor element when the specific route is activated.
注意,routerLink指令的值包含一个前导/,而我们所定义的路由配置对象的path属性则没有。根据情况,省略/会给路由带来不同的含义。
-
routerLink和routerLinkActive指令是 Angular Router 包的一部分。我们需要在core.module.ts文件中导入RouterModule才能使用它们:import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HeaderComponent } from './header/header.component'; **import** **{** **RouterModule** **}** **from****'@angular/router'****;** @NgModule({ declarations: [ HeaderComponent ], imports: [ CommonModule, **RouterModule** ], exports: [ HeaderComponent ] }) export class CoreModule { }
现在,我们已经准备好预览我们新的联系页面!如果我们使用ng serve运行应用程序并点击联系链接,我们应该看到以下输出:
图 2.3 – 联系页面
在以下部分,我们将构建我们博客页眉中文章链接的功能。
添加文章页面
负责在我们博客中显示文章的功能将是articles模块。它也将是连接 Angular 和 Scully 之间的模块。我们将使用 Angular CLI 的generate命令来创建该模块:
ng generate module articles --route=articles --module=app-routing
在之前的命令中,我们传递了一些额外的路由选项:
-
--route:定义我们功能的 URL 路径 -
--module:指示将定义激活我们功能的路由配置对象的路由模块
当执行命令时,Angular CLI 执行了额外的操作,而不仅仅是创建模块:
-
它在
src\app\articles文件夹中创建了一个路由组件,该组件将默认由路由导航对象激活。它是我们功能的着陆页,并将显示博客文章列表,正如我们将在在主页上显示博客数据部分中看到的那样。 -
它创建了一个名为
articles-routing.module.ts的路由模块,其中包含我们模块的路由配置。 -
在主应用模块的路由配置中添加了一个新的路由配置对象,该对象激活了我们的模块。
articles-routing.module.ts文件包含articles模块的路由配置:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ArticlesComponent } from './articles.component';
const routes: Routes = [{ path: '', component: ArticlesComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ArticlesRoutingModule { }
它使用forChild方法导入RouterModule,将路由配置传递给 Angular 路由器。如果我们查看应用程序的主路由模块,我们会看到它采用了一种略有不同的方法:
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ContactComponent } from './contact/contact.component';
const routes: Routes = [
{ path: 'contact', component: ContactComponent },
**{** **path****:** **'articles'****,** **loadChildren****:** **() =>****import****(****'./articles/articles.module'****).****then****(****m** **=>** **m.****ArticlesModule****) }**
];
@NgModule({
imports: [**RouterModule****.****forRoot****(routes)**],
exports: [RouterModule]
})
export class AppRoutingModule { }
在功能模块中使用forChild方法,而forRoot方法应仅在主应用程序模块中使用仅此而已。
articles模块的路由配置只包含一个激活ArticlesComponent的路由。该路由的路径设置为空字符串,以表示它是路由模块的默认路由。这实际上意味着每当该模块被加载时,ArticlesComponent将被激活。但我们的应用程序是如何加载articles模块的呢?
主路由模块的第二路由包含一个路由配置对象,该对象不激活组件而是激活一个模块。它使用loadChildren方法在导航触发articles路径时动态加载ArticlesModule。
loadChildren属性的import函数接受 TypeScript 模块文件的相对路径,不包括扩展名。
之前的方法称为懒加载,它提高了 Angular 应用程序的启动和整体性能。它为每个懒加载的模块创建一个单独的包,在请求时加载,减少了最终包的大小和应用程序的内存消耗。让我们将新路由连接到我们的标题组件:
-
打开
header.component.html文件,并将以下routerLink和routerLinkActive指令添加到Articles锚点 HTML 元素中:<li class="nav-item"> <a **routerLink****=****"/articles"****routerLinkActive****=****"active"** class="nav-link">Articles</a> </li> -
运行
ng serve并使用您喜欢的浏览器预览您的应用程序。 -
打开您浏览器的开发者工具,点击Articles链接,并检查网络选项卡:
图 2.4 – 懒加载 Angular 模块
在其他请求中,你应该看到一个名为src_app_articles_articles_module_ts.js的请求。这是当你点击Articles链接时加载的懒加载文章模块的包。
现在我们已经准备好将我们的出色 Angular 应用程序转换成一个专业的博客网站。
在我们继续之前,让我们向app-routing.module.ts文件添加一些额外的路由:
const routes: Routes = [
{ path: 'contact', component: ContactComponent },
{ path: 'articles', loadChildren: () => import('./articles/articles.module').then(m => m.ArticlesModule) },
**{** **path****:** **''****,** **pathMatch****:** **'full'****,** **redirectTo****:** **'articles'** **},**
**{** **path****:** **'**'****,** **redirectTo****:** **'articles'** **}**
];
我们添加了一个默认路由,在访问博客时自动将博客用户重定向到articles路径。此外,我们还创建了一个新的路由配置对象,其路径设置为**,它也会导航到articles路径。**语法称为通配符路由,当路由器无法将请求的 URL 与定义的路由匹配时,它会被触发。
首先定义最具体的路由,然后添加任何通用的路由,例如默认路由和通配符路由。Angular 路由器按照我们定义的顺序解析路由配置,并遵循“首次匹配即获胜”的策略来选择一个。
我们已经在 Angular 应用程序中启用了并配置了路由。在下一节中,我们将建立添加博客功能所需的基础设施。
使用 Scully 添加博客功能
我们的应用程序目前还没有关于博客文章的任何特定逻辑。它是一个典型的 Angular 应用程序,使用路由。然而,通过添加路由配置,我们已经为使用 Scully 添加博客支持奠定了基础。
Scully 至少需要在 Angular 应用程序中定义一个路由才能正确工作。
首先,我们需要在我们的应用程序中安装 Scully。
安装 Scully 库
我们将使用 npm CLI 的 install 命令在我们的 Angular 应用程序中安装 Scully:
npm install @scullyio/init @scullyio/ng-lib @scullyio/scully @scullyio/scully-plugin-puppeteer --force
之前的命令下载并安装了 Scully 在我们的 Angular 应用程序中正确工作所需的所有必要的 npm 包。
Scully 库与 Angular 16 不完全兼容,截至本文撰写时为止。在上一个命令中,我们使用了 --force 选项来忽略来自 Angular 版本不兼容的任何警告。
打开 app.module.ts 文件并导入 ScullyLibModule:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ContactModule } from './contact/contact.module';
import { CoreModule } from './core/core.module';
import { SharedModule } from './shared/shared.module';
**import** **{** **ScullyLibModule** **}** **from****'@scullyio/ng-lib'****;**
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule,
SharedModule,
ContactModule,
**ScullyLibModule**
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
ScullyLibModule 是 Scully 库的主要模块;它包含 Scully 需要的各种 Angular 服务和指令。
在 Angular CLI 工作区的根目录中为 Scully 库创建一个配置文件,内容如下:
scully.my-blog.config.ts
import { ScullyConfig } from '@scullyio/scully';
export const config: ScullyConfig = {
projectRoot: "./src",
projectName: "my-blog",
outDir: './dist/static',
routes: {
}
};
配置文件包含 Scully 在过程中需要了解的关于我们的 Angular 应用程序的信息:
-
projectRoot:包含 Angular 应用程序源代码的路径 -
projectName:Angular 应用程序的名称 -
outDir:Scully 生成的文件输出路径Scully 的输出路径必须与 Angular CLI 为您的 Angular 应用程序捆绑输出的路径不同。后者可以在
angular.json文件中配置。 -
routes:它包含用于访问我们的博客文章的路由配置。Scully 将自动填充它,正如我们将在下一节中看到的。
由于我们已经成功地在 Angular 应用程序中安装了 Scully,我们现在可以配置它来初始化我们的博客。
初始化我们的博客页面
Scully 为初始化 Angular 应用程序(如博客)提供了一个特定的 Angular CLI 模板,通过使用 Markdown (.md) 文件:
ng generate @scullyio/init:markdown --project my-blog
之前的命令将通过一系列问题(括号内显示默认值)启动博客的配置过程:
-
将博客模块的名称输入为
posts:What name do you want to use for the module? (blog)这将创建一个名为
posts的新 Angular 模块。 -
留空 slug 选择,并按 Enter 键接受默认值:
What slug do you want for the markdown file? (id)slug 是每篇帖子的唯一标识符,它在模块的路由配置对象中定义。
-
将路径设置为
mdfiles,这是 Scully 将用于存储我们的实际博客文章文件的位置:Where do you want to store your markdown files?这将在我们的 Angular CLI 项目的根路径内创建一个
mdfiles文件夹。默认情况下,它还会为了我们的方便创建一篇博客文章。我们将在 在主页上显示博客数据 这一部分学习如何创建自己的。 -
将路由名称设置为
posts以访问我们的博客文章:Under which route do you want your files to be requested?
路由的名称是创建的路由配置对象的 path 属性。
Scully 在执行前面的命令时执行各种操作,包括创建 posts 模块的路由配置:
posts-routing.module.ts
import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {PostsComponent} from './posts.component';
const routes: Routes = [
{
path: ':id',
component: PostsComponent,
},
{
path: '**',
component: PostsComponent,
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PostsRoutingModule {}
第一条路由的 path 属性设置为 :id 并激活 PostsComponent。冒号字符表示 id 是一个路由参数。id 参数与 Scully 配置中早先定义的 slug 属性相关。Scully 通过为每篇我们创建的博客文章创建一个路由来工作。它使用 posts 模块的路线配置和主应用程序模块来构建 Scully 配置文件中的 routes 属性:
routes: {
'/posts/:id': {
type: 'contentFolder',
id: {
folder: "./mdfiles"
}
},
}
PostsComponent 是一个 Angular 组件,用于渲染每篇博客的详细信息。根据您的需求,组件的模板文件可以进一步自定义:
posts.component.html
<h3>ScullyIo content</h3>
<hr>
<!-- This is where Scully will inject the static HTML -->
<scully-content></scully-content>
<hr>
<h4>End of content</h4>
您可以自定义除 <scully-content></scully-content> 行之外的前一个模板文件中的所有内容,该行是 Scully 内部使用的。
到目前为止,我们已经完成了 Scully 在我们的 Angular 应用程序中的安装和配置。现在是项目的最后一部分!在下一节中,我们将让 Angular 和 Scully 合作,并在我们的 Angular 应用程序中显示博客文章。
在主页上显示博客文章
我们希望用户一登录我们的博客网站就能看到可用的博客文章列表。根据我们定义的默认路由路径,ArticlesComponent 是我们博客的着陆页。Scully 提供了 ScullyRoutesService,这是一个 Angular 服务,我们可以在我们的组件中使用它来获取关于它将根据博客文章创建的路由的信息。让我们在我们的着陆页上使用这个服务:
-
打开
articles.component.ts文件,并按如下修改import语句:import { Component, **OnInit** } from '@angular/core'; **import** **{** **ScullyRoute****,** **ScullyRoutesService** **}** **from****'@scullyio/ng-lib'****;** **import** **{** **Observable****, map }** **from****'rxjs'****;** -
将
OnInit接口添加到ArticlesComponent类实现的接口列表中:export class ArticlesComponent **implements****OnInit** { } -
在
ArticlesComponent类的constructor中注入ScullyRoutesService:constructor(private scullyService: ScullyRoutesService) { } -
创建以下组件属性:
posts$: Observable<ScullyRoute[]> | undefined; -
实现
ngOnInit方法:ngOnInit(): void { this.posts$ = this.scullyService.available$.pipe( map(posts => posts.filter(post => post.title)) ); } -
打开
articles.component.html文件并添加以下 HTML 代码:<div class="list-group mt-3"> <a *ngFor="let post of posts$ | async" [routerLink]="post.route" class="list-group-item list-group-item-action"> <div class="d-flex w-100 justify-content-between"> <h5 class="mb-1">{{post.title}}</h5> </div> <p class="mb-1">{{post['description']}}</p> </a> </div>
在前面的步骤中涉及了许多 Angular 技巧,所以让我们一点一点地分解它们。
当我们想在组件中使用 Angular 服务时,我们只需从 Angular 框架中请求它。如何?通过将其添加到组件的 constructor 中的属性。组件不需要了解服务是如何实现的。
ngOnInit 方法是 OnInit 接口的一部分,该接口由我们的组件实现。当 Angular 框架初始化组件时,它会被调用,并为我们提供了一个钩子来添加要执行的定制逻辑。
为组件提供初始化逻辑的 Angular 服务应该在 ngOnInit 方法中调用,而不是在 constructor 中,因为当对组件进行单元测试时,提供这些服务的模拟更容易。
ScullyRoutesService 的 available$ 属性被称为 可观察对象,当订阅它时返回所有由 Scully 生成的可用路由。为了避免显示与博客文章无关的路由,例如 contact 路由,我们从 available$ 属性中过滤掉结果。
在组件模板中,我们使用 Angular 内置的 *ngFor 指令和 async 管道来订阅 HTML 中的 posts$ 可观察对象。然后我们可以使用 post 模板引用变量访问每个项目,并使用插值来显示 title 和 description。
最后,我们为每个锚点元素添加一个 routerLink 指令,以便在点击时导航到相应的博客文章。注意,routerLink 被括号 [] 包围。这种括号语法称为 属性绑定,我们使用它来将 HTML 元素的属性绑定到一个变量。在我们的情况下,我们将 routerLink 指令绑定到 post 变量的 route 属性。
现在我们终于完成了拼图的每一块,我们可以看到我们的博客网站正在运行:
-
运行 Angular CLI 的
build命令来构建我们的 Angular 应用程序:ng build -
执行以下命令来构建 Scully 并生成我们的博客路由:
npx scully --project my-blog上述命令将在
src\assets文件夹中创建一个scully-routes.json文件。它包含我们的 Angular 应用程序的路由,并且是 Scully 运行时所需的。首次运行 Scully 可执行文件时,会提示您收集匿名错误以改进其服务。
-
运行以下命令来提供我们的博客:
npx scully serve --project my-blog
上述命令将启动两个网络服务器:一个包含使用 Scully 构建的网站的静态预渲染版本,另一个是应用程序的 Angular 实时版本:
图 2.5 – 服务器上的我们的应用程序
如果我们打开浏览器并导航到 http://localhost:1668,我们将看不到任何博客文章。使用 Scully 创建的博客文章不会在 ScullyRoutesService 的 available$ 属性中返回,除非我们发布它。要发布博客文章,我们执行以下操作:
-
导航到 Scully 创建的
mdfiles文件夹并打开你找到的唯一.md文件。该文件名和内容可能因 Scully 创建它的日期而异:--- title: 2023-06-22-posts description: 'blog description' published: false slugs: - ___UNPUBLISHED___lj738su6_7mqWyfNdmNCwovaCCi2tZItsDKMPJGcG --- # 2023-06-22-postsScully 在文件顶部关闭和结束
---行之间定义了一组属性,代表关于博客文章的元数据。您也可以添加自己的作为键值对。 -
删除
slugs属性并将published属性设置为true:--- title: 2023-06-22-posts description: 'blog description' published: true --- # 2023-06-22-posts -
运行以下命令以强制 Scully 重新生成我们应用的路线:
npx scully --project my-blog我们需要每次在博客相关文件中做出更改时都执行前面的命令。
-
执行
npx scully serve --project my-blog命令并导航到预览生成的网站。
我们现在可以看到一篇博客文章,这是当我们安装 Scully 时创建的默认文章。让我们再创建一篇:
-
运行以下 Angular CLI 的
generate命令:ng generate @scullyio/init:post --name="Angular and Scully"在前面的命令中,我们使用
@scullyio/init:post规范,传递我们想要创建的文章的名称作为选项。 -
将新博客文章的目标文件夹设置为
mdfiles:What's the target folder for this post? (blog) -
Scully 将在指定的文件夹内创建一个名为
angular-and-scully.md的 Markdown 文件。打开该文件并更新其内容,使其与以下内容相同:--- title: 'Angular and Scully' description: 'How to build a blog with Angular and Scully' published: true --- # Angular and Scully Angular is a robust JavaScript framework that we can use to build excellent and performant web applications. Scully is a popular static website generator that empowers the Angular framework with Jamstack characteristics. You can find more about them in the following links: - https://angular.io - https://scully.io - https://www.jamstack.org -
运行
npx scully --project my-blog以为新建的博客文章创建一个路由。Scully 还会更新scully-routes.json文件以包含新的路由。
如果我们现在预览我们的应用,它应该看起来像以下这样:
图 2.6 – 博客文章列表
如果我们点击其中一个博客条目,我们将导航到所选的博客文章。当前屏幕上显示的内容是博客文章路由的预渲染版本:
图 2.7 – 博客文章详情
为了验证这一点,导航到你的 Angular 项目的 dist 文件夹,在那里你会找到两个文件夹:
-
my-blog: 这包含了我们应用的 Angular 实时版本。当我们执行ng buildAngular CLI 命令时,它会构建我们的应用并将捆绑文件输出到这个文件夹中。 -
static: 这包含了一个由 Scully 生成的 Angular 应用程序的预渲染版本,当我们运行npx scully --project my-blog命令时。
如果我们导航到 static 文件夹,我们会看到 Scully 为我们的 Angular 应用程序的每个路由创建了一个文件夹。每个文件夹都包含一个 index.html 文件,它代表了从该路由激活的组件。
index.html 文件的内容是由 Scully 自动生成的,并且表现得好像我们正在实时运行我们的应用并导航到该组件。
现在,您可以将您的 Angular 应用程序上传到您选择的 CDN 或 Web 服务器,您的博客将立即准备好!接下来,您只需练习您的写作技巧,以创建优秀的博客内容。
概述
在本章中,我们学习了如何将 Angular 框架与 Scully 库结合使用来创建个人博客。
我们看到了 Angular 如何使用内置的路由包来增强 Web 应用程序的内部导航。我们还学习了如何将 Angular 应用程序组织成模块以及如何在这些模块间导航。
我们使用 Scully 库将 Jamstack 引入到我们的 Angular 应用程序中,并看到了如何轻松地将我们的应用程序转换为预渲染的博客。我们使用 Scully 界面创建了一些博客文章并在屏幕上显示它们。
在下一章中,我们将探讨 Angular 框架的另一个令人兴奋的特性——表单。我们将学习如何使用它们并构建一个问题跟踪系统。
实践问题
让我们看看几个实践问题:
-
我们在 Angular 应用程序中使用哪个库进行路由?
-
我们如何在 HTML 锚点元素中添加路由功能?
-
我们使用哪个 Angular 管道进行日期格式化?
-
Angular CLI 应用程序中的
assets文件夹的目的是什么? -
我们使用哪个路由属性来实现模块的懒加载?
-
我们使用哪个 npm CLI 命令来安装 Scully?
-
我们使用哪个服务来获取 Scully 路由?
-
属性绑定是什么?
-
我们在 HTML 中使用哪个 Angular 指令来遍历数组?
-
标准 Angular 应用程序与 Scully 应用程序之间的区别是什么?
进一步阅读
这里有一些链接,可以帮助我们巩固本章所学的内容:
-
Angular 路由:
angular.io/guide/router -
Angular 功能模块:
angular.io/guide/module-types -
Angular 内置管道:
angular.io/api?type=pipe -
Bootstrap CSS:
getbootstrap.com -
Jamstack:
jamstack.org -
Scully:
scully.io -
掌握 Markdown:
guides.github.com/features/mastering-markdown
第三章:使用反应式表单构建问题跟踪系统
Web 应用程序使用 HTML 表单从用户那里收集数据并进行验证,例如在登录应用程序、执行搜索或完成在线支付时。Angular 框架提供了两种类型的表单,反应式和模板驱动型,我们可以在 Angular 应用程序中使用。
在本章中,我们将构建一个用于管理和跟踪问题的系统。我们将使用 Angular 反应式表单来报告新问题。我们还将使用来自 VMware 的 Clarity 设计系统来设计我们的表单和显示我们的问题。
我们将涵盖以下主题:
-
在 Angular 应用程序中安装 Clarity 设计系统
-
显示问题概述
-
报告新问题
-
标记问题为已解决
-
启用新问题建议
必要的背景理论和上下文
Angular 框架提供了两种类型的表单,我们可以使用:
-
模板驱动型:在 Angular 应用程序中设置它们非常简单。模板驱动的表单扩展性不好,且难以测试,因为它们是在组件模板中定义的。
-
反应式:它们基于反应式编程方法。反应式表单在组件的 TypeScript 类中操作,并且比模板驱动型表单更容易测试和扩展。
在本章中,我们将亲身体验反应式表单方法,这是 Angular 社区中最受欢迎的方法。
Angular 组件可以从外部源(如 HTTP 或其他 Angular 组件)获取数据。在后一种情况下,它们通过公共 API 与具有数据的组件交互:
-
@Input():用于向组件传递数据。 -
@Output():用于接收通知或从组件获取数据。
Clarity 是一个包含构建 Web 应用程序的一套 UX 和 UI 指南的设计系统。它还包含一个包含这些指南的专有 HTML 和 CSS 框架。幸运的是,我们不需要使用这个框架,因为 Clarity 已经提供了各种基于 Angular 的 UI 组件,我们可以在我们的 Angular 应用程序中使用。
项目概述
在此项目中,我们将使用反应式表单和 Clarity 构建 Angular 应用程序来管理和跟踪问题。最初,我们将以表格形式显示问题列表,我们可以对其进行排序和筛选。然后我们将创建一个表单,允许用户报告新问题。最后,我们将创建一个模态对话框来解决问题。我们还将更进一步,在报告问题时启用建议,以帮助用户避免重复输入。以下图表展示了项目的架构概述:
图 3.1 – 项目架构
构建时间:1 小时
开始
完成此项目所需的以下软件工具:
-
Angular CLI:Angular 的命令行界面,您可以在
angular.io/cli找到 -
GitHub 材料:本章的相关代码,您可以在
github.com/PacktPublishing/Angular-Projects-Third-Edition的Chapter03文件夹中找到
在 Angular 应用程序中安装 Clarity
让我们通过搭建一个新的 Angular 应用程序来开始创建我们的问题跟踪系统:
ng new issue-tracker --defaults
我们使用 Angular CLI 的 ng new 命令创建一个具有以下特性的新 Angular 应用程序:
-
issue-tracker:Angular 应用程序的名称。 -
--defaults:此选项禁用应用程序的 Angular 路由,并将样式表格式设置为 CSS。
现在,我们需要在我们的 Angular 应用程序中安装 Clarity 库:
-
导航到创建的
issue-tracker文件夹,并运行以下命令来安装它:npm install @cds/core @clr/angular @clr/ui --save -
打开
angular.json文件,并在styles数组中添加 Clarity CSS 样式:"styles": [ **"node_modules/@clr/ui/clr-ui.min.css"****,** "src/styles.css" ] -
最后,在主应用程序模块
app.module.ts中导入ClarityModule和BrowserAnimationsModule:import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; **import** **{** **ClarityModule** **}** **from****'@clr/angular'****;** **import** **{** **BrowserAnimationsModule** **}** **from** **'@angular/platform-browser/animations'****;** @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, **ClarityModule****,** **BrowserAnimationsModule** ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
现在我们已经完成了 Clarity 在我们的应用程序中的安装,我们可以开始使用它构建美观的设计。在接下来的部分,我们将首先创建一个用于显示我们问题的列表。
显示问题概述
我们的 Angular 应用程序将负责管理和跟踪问题。当应用程序启动时,我们应该显示系统中所有待处理问题的列表。待处理问题定义为那些尚未解决的问题。我们将遵循的过程可以进一步分析如下:
-
获取待处理问题
-
使用数据网格可视化问题
获取待处理问题
首先,我们需要创建一个机制来获取所有待处理问题:
-
使用 Angular CLI 的
generate命令创建一个名为issues的 Angular 服务:ng generate service issues之前的命令将在我们的 Angular CLI 项目的
src\app文件夹中创建一个issues.service.ts文件。 -
每个问题都将具有定义类型的特定属性。我们需要使用以下 Angular CLI 命令创建一个 TypeScript 接口:
ng generate interface issue之前的命令将在项目的
src\app文件夹中创建一个issue.ts文件。 -
打开
issue.ts文件,并在Issue接口中添加以下属性:export interface Issue { **issueNo****:** **number****;** **title****:** **string****;** **description****:** **string****;** **priority****:** **'low'** **|** **'high'****;** **type****:** **'Feature'** **|** **'Bug'** **|** **'Documentation'****;** **completed?:** **Date****;** }completed属性是一个问题被解决时的日期。我们将其定义为可选的,因为新问题将不会设置此属性。 -
打开我们在第一步中创建的 Angular 服务,并添加一个
issues属性来存储我们的数据。同时,创建一个getPendingIssues方法,它将返回所有未完成的问题:import { Injectable } from '@angular/core'; **import** **{** **Issue** **}** **from****'./issue'****;** @Injectable({ providedIn: 'root' }) export class IssuesService { **private****issues****:** **Issue****[] = [];** constructor() { } **getPendingIssues****():** **Issue****[] {** **return****this****.****issues****.****filter****(****issue** **=>** **!issue.****completed****);** **}** }在前面的代码中,我们将
issues属性初始化为一个空数组。如果您想从示例数据开始,可以使用本章 GitHub 材料中存在的src\assets文件夹中的mock-issues.ts文件,并按以下方式导入:import { issues } from '../assets/mock-issues';
在接下来的部分,我们将创建一个用于显示这些问题的组件。
在数据网格中可视化问题
我们将使用 Clarity 库的数据网格 UI 组件以表格格式显示数据。数据网格还提供了开箱即用的过滤和排序机制。首先,让我们创建一个将托管数据网格的 Angular 组件:
-
使用 Angular CLI 的
generate命令创建组件:ng generate component issue-list -
打开我们应用程序的主要组件模板
app.component.html,并用以下 HTML 代码替换其内容:<div class="main-container"> <div class="content-container"> <div class="content-area"> <app-issue-list></app-issue-list> </div> </div> </div>一旦 Angular 应用程序启动,问题列表将显示在主组件中。
-
目前,
<app-issue-list>组件不显示任何问题数据。我们必须将其与我们在 获取待处理问题 部分创建的 Angular 服务连接起来。打开issue-list.component.ts文件,并在IssueListComponent类的constructor中注入IssuesService:import { Component } from '@angular/core'; **import** **{** **IssuesService** **}** **from****'../issues.service'****;** @Component({ selector: 'app-issue-list', templateUrl: './issue-list.component.html', styleUrls: ['./issue-list.component.css'] }) export class IssueListComponent { **constructor****(****private** **issueService: IssuesService****) { }** } -
创建一个名为
getIssues的方法,该方法将调用注入的服务中的getPendingIssues方法,并将返回值保存在issues组件属性中:import { Component } from '@angular/core'; **import** **{** **Issue** **}** **from****'../issue'****;** import { IssuesService } from '../issues.service'; @Component({ selector: 'app-issue-list', templateUrl: './issue-list.component.html', styleUrls: ['./issue-list.component.css'] }) export class IssueListComponent { **issues****:** **Issue****[] = [];** constructor(private issueService: IssuesService) { } **private****getIssues****() {** **this****.****issues** **=** **this****.****issueService****.****getPendingIssues****();** **}** } -
最后,在
ngOnInit组件方法中调用getIssues方法,以在组件初始化时获取所有待处理问题:import { Component, **OnInit** } from '@angular/core'; import { Issue } from '../issue'; import { IssuesService } from '../issues.service'; @Component({ selector: 'app-issue-list', templateUrl: './issue-list.component.html', styleUrls: ['./issue-list.component.css'] }) export class IssueListComponent **implements****OnInit** { issues: Issue[] = []; constructor(private issueService: IssuesService) { } **ngOnInit****():** **void** **{** **this****.****getIssues****();** **}** private getIssues() { this.issues = this.issueService.getPendingIssues(); } }
我们已经在我们的组件中实现了获取问题数据的过程。现在我们只需要在模板中显示它。打开 issue-list.component.html 文件,并用以下 HTML 代码替换其内容:
<clr-datagrid>
<clr-dg-column [clrDgField]="'issueNo'" [clrDgColType]="'number'">Issue No</clr-dg-column>
<clr-dg-column [clrDgField]="'type'">Type</clr-dg-column>
<clr-dg-column [clrDgField]="'title'">Title</clr-dg-column>
<clr-dg-column [clrDgField]="'description'">Description</clr-dg-column>
<clr-dg-column [clrDgField]="'priority'">Priority</clr-dg-column>
<clr-dg-row *clrDgItems="let issue of issues">
<clr-dg-cell>{{issue.issueNo}}</clr-dg-cell>
<clr-dg-cell>{{issue.type}}</clr-dg-cell>
<clr-dg-cell>{{issue.title}}</clr-dg-cell>
<clr-dg-cell>{{issue.description}}</clr-dg-cell>
<clr-dg-cell>
<span class="label" [class.label-danger]="issue.priority === 'high'">{{issue.priority}}</span>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>{{issues.length}} issues</clr-dg-footer>
</clr-datagrid>
在前面的代码片段中,我们使用了 Clarity 库的几个 Angular 组件:
-
<clr-datagrid>: 定义一个表格。 -
<clr-dg-column>: 定义表格的一列。每一列使用clrDgField指令绑定到该列表示的问题的属性名。clrDgField指令提供了排序和过滤功能,而无需在 TypeScript 类文件中编写任何代码。排序仅适用于基于字符串的内容。如果我们想按不同的原始类型排序,我们必须使用clrDgColType指令并指定特定的类型。 -
<clr-dg-row>: 定义表格的一行。它使用*clrDgItems指令遍历问题,并为每个问题创建一行。 -
<clr-dg-cell>: 每一行包含一组<clr-dg-cell>组件,用于通过插值显示每列的值。在最后一列中,当问题具有高优先级时,我们添加label-danger类以表明其重要性。 -
<clr-dg-footer>: 定义表格的页脚。在这种情况下,它显示问题的总数。
如果我们使用 ng serve 运行我们的 Angular 应用程序,输出将如下所示:
图 3.2 – 待处理问题的概述
在前面的屏幕截图中,应用程序使用 mock-issues.ts 文件中的示例数据。
Clarity 库的数据网格组件具有丰富的功能,我们可以在我们的 Angular 应用程序中使用这些功能。在下一节中,我们将学习如何使用响应式表单来报告新问题。
报告新问题
我们问题跟踪系统的主要功能之一是能够报告新问题。我们将使用 Angular 反应式表单来创建添加新问题的表单。该功能可以进一步细分为以下任务:
-
在 Angular 应用程序中设置反应式表单
-
创建报告问题表单
-
在列表中显示新问题
-
验证问题细节
让我们从在 Angular 应用程序中介绍反应式表单开始。
在 Angular 应用程序中设置反应式表单
反应式表单定义在 Angular 框架的 @angular/forms npm 包中。要将它们添加到我们的 Angular 应用程序中:
-
打开
app.module.ts文件并导入ReactiveFormsModule:import { ReactiveFormsModule } from '@angular/forms'; -
将
ReactiveFormsModule添加到@NgModule装饰器的imports数组中:@NgModule({ declarations: [ AppComponent, IssueListComponent ], imports: [ BrowserModule, ClarityModule, BrowserAnimationsModule, **ReactiveFormsModule** ], providers: [], bootstrap: [AppComponent] })
ReactiveFormsModule 包含了我们与表单一起工作所需的所有必要的 Angular 指令和服务,正如我们将在下一节中看到的。
创建报告问题表单
现在我们已经在 Angular 应用程序中介绍了反应式表单,我们可以开始构建我们的表单:
-
创建一个名为
issue-report的新 Angular 组件:ng generate component issue-report -
打开
issue-report.component.ts文件并添加以下import语句:import { FormControl, FormGroup } from '@angular/forms';在这个语句中,
FormControl代表表单的单个控件,而FormGroup用于将单个控件组合成一个逻辑表单表示。 -
创建以下接口,它将代表我们表单的结构:
interface IssueForm { title: FormControl<string>; description: FormControl<string>; priority: FormControl<string>; type: FormControl<string>; } -
在 TypeScript 类中声明一个
issueForm属性,其类型为FormGroup<IssueForm>:issueForm = new FormGroup<IssueForm>({ title: new FormControl('', { nonNullable: true }), description: new FormControl('', { nonNullable: true }), priority: new FormControl('', { nonNullable: true }), type: new FormControl('', { nonNullable: true }) });我们将所有控件初始化为空字符串,因为表单将用于从头创建新问题。我们还通过使用
nonNullable属性明确声明所有控件默认不接受空值。 -
我们现在必须将我们创建的
FormGroup对象与相应的 HTML 元素关联。打开issue-report.component.html文件,并用以下 HTML 代码替换其内容:<h3>Report an issue</h3> <form clrForm *ngIf="issueForm" [formGroup]="issueForm"> <clr-input-container> <label>Title</label> <input clrInput formControlName="title" /> </clr-input-container> <clr-textarea-container> <label>Description</label> <textarea clrTextarea formControlName="description"></textarea> </clr-textarea-container> <clr-radio-container clrInline> <label>Priority</label> <clr-radio-wrapper> <input type="radio" value="low" clrRadio formControlName="priority" /> <label>Low</label> </clr-radio-wrapper> <clr-radio-wrapper> <input type="radio" value="high" clrRadio formControlName="priority" /> <label>High</label> </clr-radio-wrapper> </clr-radio-container> <clr-select-container> <label>Type</label> <select clrSelect formControlName="type"> <option value="Feature">Feature</option> <option value="Bug">Bug</option> <option value="Documentation">Documentation </option> </select> </clr-select-container> </form>formGroup和clrForm指令将 HTML<form>元素与issueForm属性关联,并将其标识为 Clarity 表单。formControlName指令用于通过名称将 HTML 元素与表单控件关联。每个控件也使用 Clarity 容器元素定义。例如,
title输入控件是一个包含<input>HTML 元素的<clr-input-container>组件。每个原生 HTML 元素都根据其类型附加了一个 Clarity 指令。例如,
<input>HTML 元素包含一个clrInput指令。 -
最后,给我们的
issue-report.component.css文件添加一些样式:.clr-input, .clr-textarea { width: 30%; } button { margin-top: 25px; }
现在我们已经创建了表单的基本结构,我们将学习如何提交其细节:
-
在 HTML
<form>元素的结束标签之前添加一个 HTML<button>元素:<button class="btn btn-primary" type="submit">Create</button>我们将其类型设置为
submit,以便在点击按钮时触发表单提交。 -
打开
issues.service.ts文件并添加一个createIssue方法,该方法将新问题插入到issues数组中:createIssue(issue: Issue) { issue.issueNo = this.issues.length + 1; this.issues.push(issue); }在将问题添加到
issues数组之前,我们自动为问题分配一个新的issueNo属性。当前
issueNo属性是根据issues数组的长度来计算的。一个更好的方法是实现一个生成器机制来创建唯一且随机的issueNo值。 -
返回到
issue-report.component.ts文件,并添加以下import语句:import { Issue } from '../issue'; import { IssuesService } from '../issues.service'; -
将
IssuesService类注入到 TypeScript 类的constructor中:constructor(private issueService: IssuesService) { } -
添加一个新的组件方法,该方法将调用注入的服务中的
createIssue方法:addIssue() { this.issueService.createIssue(this.issueForm.getRawValue() as Issue); }我们使用
issueForm对象的getRawValue属性传递每个表单控件值,该对象将为我们提供对底层表单模型的访问。我们还将其类型转换为Issue接口,因为我们已经知道其值将代表问题对象的属性。 -
打开
issue-report.component.html文件,并将表单的ngSubmit事件绑定到addIssue组件方法:<form clrForm *ngIf="issueForm" [formGroup]="issueForm" **(****ngSubmit****)=****"addIssue()"**>
当我们点击表单的 Create 按钮时,将触发 ngSubmit 事件。
我们已经完成了将新问题添加到系统的所有过程。在下一节中,我们将学习如何在待处理问题表中显示新创建的问题。
在列表中显示新问题
显示和创建新问题是委托给不同的 Angular 组件的两个任务。当我们使用 IssueReportComponent 创建新问题时,我们需要通知 IssueListComponent 以在表格中反映这一变化。首先,让我们看看如何配置 IssueReportComponent 以实现这种通信:
-
打开
issue-report.component.ts文件,并使用@Output()装饰器添加一个EventEmitter属性:@Output() formClose = new EventEmitter();可以从
@angular/corenpm 包中导入Output和EventEmitter符号。 -
在创建问题后,立即在
addIssue组件方法中调用formClose输出属性的emit方法:addIssue() { this.issueService.createIssue(this.issueForm.getRawValue() as Issue); **this****.****formClose****.****emit****();** } -
在组件模板中添加第二个 HTML
<button>元素,并在其click事件上调用formClose.emit方法:<button class="btn" type="button" (click)="formClose.emit()">Cancel</button>
IssueListComponent 现在可以绑定到 IssueReportComponent 的 formClose 事件,并在任何按钮被点击时收到通知。让我们来看看如何实现:
-
打开
issue-list.component.ts文件,并在IssueListComponent类中添加以下属性:showReportIssue = false;showReportIssue属性将切换报告问题表单的显示。 -
添加以下组件方法,当报告问题表单发出
formClose事件时将被调用:onCloseReport() { this.showReportIssue = false; this.getIssues(); }之前的方法会将
showReportIssue属性设置为false,这样报告问题表单就不再可见,而是显示待处理问题的表格。它还会再次获取问题以刷新表格中的数据。 -
打开
issue-list.component.html文件,并在模板顶部添加一个 HTML<button>元素。当点击按钮时,将显示报告问题表单:<button class="btn btn-primary" (click)="showReportIssue = true">Add new issue</button> -
在
<ng-container>元素内组合按钮和数据网格。如*ngIfAngular 指令所示,当报告问题表单不可见时,<ng-container>元素的 内容将被显示:**<****ng-container** *******ngIf****=****"showReportIssue === false"****>** <button class="btn btn-primary" (click)="showReportIssue = true">Add new issue</button> <clr-datagrid> <clr-dg-column [clrDgField]="'issueNo'" [clrDgColType]="'number'">Issue No</clr-dg-column> <clr-dg-column [clrDgField]="'type'">Type</clr-dg-column> <clr-dg-column [clrDgField]="'title'">Title</clr-dg-column> <clr-dg-column [clrDgField]="'description'">Description</clr-dg-column> <clr-dg-column [clrDgField]="'priority'">Priority</clr-dg-column> <clr-dg-row *clrDgItems="let issue of issues"> <clr-dg-cell>{{issue.issueNo}}</clr-dg-cell> <clr-dg-cell>{{issue.type}}</clr-dg-cell> <clr-dg-cell>{{issue.title}}</clr-dg-cell> <clr-dg-cell>{{issue.description}}</clr-dg-cell> <clr-dg-cell> <span class="label" [class.label-danger]="issue.priority === 'high'">{{issue.priority}}</span> </clr-dg-cell> </clr-dg-row> <clr-dg-footer>{{issues.length}} issues</clr-dg-footer> </clr-datagrid> **</****ng-container****>**<ng-container>元素是一个 Angular 组件,它不会在屏幕上渲染,用于组合 HTML 元素。 -
在模板末尾添加
<app-issue-report>组件,并使用*ngIf指令在showReportIssue属性为 true 时显示它。同时将其formClose事件绑定到onCloseReport组件方法:<app-issue-report *ngIf="showReportIssue === true" (formClose)="onCloseReport()"></app-issue-report>
我们已经成功连接了所有点,完成了报告问题表单与显示问题的表格之间的交互。现在,是时候将它们付诸实践了:
-
使用
ngserve运行 Angular 应用程序。 -
点击 添加新问题 按钮,并输入新问题的详细信息:
图 3.3 – 报告问题表单
- 点击 创建 按钮,新的问题应该会出现在表格中:
图 3.4 – 待处理问题
- 重复步骤 2 和 3 而不填写任何详细信息,你将注意到表格中添加了一个空的问题。
可以创建一个空的问题,因为我们没有在我们的报告问题表单上定义任何必填字段。在下一节中,我们将学习如何完成这项任务,并添加验证到我们的表单以避免意外的行为。
验证问题详情
当我们使用报告问题表单创建问题时,我们可以留空表单控件值,因为我们还没有添加任何验证规则。要在表单控件中添加验证,我们使用来自 @angular/forms npm 包的 Validators 类。在构建表单时,每个表单控件实例都会添加一个验证器。在这种情况下,我们将使用 required 验证器来表示表单控件必须有一个值:
-
打开
issue-report.component.ts文件,并从@angular/formsnpm 包导入Validators:import { FormControl, FormGroup, **Validators** } from '@angular/forms'; -
在所有控件(除了问题的
description)中设置Validators.required静态属性:issueForm = new FormGroup<IssueForm>({ title: new FormControl('', { nonNullable: true, **validators****:** **Validators****.****required** }), description: new FormControl('', { nonNullable: true }), priority: new FormControl('', { nonNullable: true, **validators****:** **Validators****.****required** }), type: new FormControl('', { nonNullable: true, **validators****:** **Validators****.****required** }) });我们可以为表单控件使用各种验证器,例如 min、max 和 email。如果我们想在表单控件中设置多个验证器,我们可以在一个数组中添加它们。
-
当我们在表单中使用验证器时,我们需要向用户提供视觉指示。打开
issue-report.component.html文件,并为每个必填表单控件添加<clr-control-error>组件:<clr-input-container> <label>Title</label> <input clrInput formControlName="title" /> **<****clr-control-error****>****Title is required****</****clr-control-error****>** </clr-input-container> <clr-textarea-container> <label>Description</label> <textarea clrTextarea formControlName="description"></textarea> </clr-textarea-container> <clr-radio-container clrInline> <label>Priority</label> <clr-radio-wrapper> <input type="radio" value="low" clrRadio formControlName="priority" /> <label>Low</label> </clr-radio-wrapper> <clr-radio-wrapper> <input type="radio" value="high" clrRadio formControlName="priority" /> <label>High</label> </clr-radio-wrapper> **<****clr-control-error****>****Priority is required****</****clr-control-error****>** </clr-radio-container> <clr-select-container> <label>Type</label> <select clrSelect formControlName="type"> <option value="Feature">Feature</option> <option value="Bug">Bug</option> <option value="Documentation">Documentation</option> </select> **<****clr-control-error****>****Type is required****</****clr-control-error****>** </clr-select-container><clr-control-error>Clarity 组件在表单中提供验证消息。当触摸无效控件时,它会显示。当至少有一个验证规则被违反时,控件是无效的。 -
用户可能只是偶尔触摸表单控件来查看验证消息。因此,我们需要在表单提交时考虑这一点并相应地操作。为了克服这种情况,我们将在表单提交时将所有表单控件标记为已触摸:
addIssue() { **if** **(****this****.****issueForm** **&&** **this****.****issueForm****.****invalid****) {** **this****.****issueForm****.****markAllAsTouched****();** **return****;** **}** this.issueService.createIssue(this.issueForm.getRawValue() as Issue); this.formClose.emit(); } markAllAsTouched method of the issueForm property to mark all controls as touched when the form is invalid. Marking controls as touched makes validation messages appear automatically. Additionally, we use a return statement to prevent the creation of the issue when the form is invalid. -
运行
ng serve以启动应用程序。在标题输入框内点击,然后移出表单控件:图 3.5 – 标题验证消息
在标题输入框下方应出现一条消息,说明我们尚未输入任何值。Clarity 库中的验证消息由文本和红色感叹号图标在验证的表单控件中表示。
-
现在,点击创建按钮:
图 3.6 – 表单验证消息
所有验证消息将同时出现在屏幕上,表单将不会提交。响应式表单中的验证确保了我们的 Angular 应用程序拥有流畅的用户体验。在下一节中,我们将学习如何使用 Clarity 创建模态对话框,并使用它来解决列表中的问题。
解决问题
建立问题跟踪系统的主要思想是问题应该在某个时候得到解决。我们将在我们的应用程序中创建一个用户工作流程来完成这项任务。我们将能够直接从待解决问题列表中解决问题。在解决问题时,应用程序将使用模态对话框向用户请求确认:
-
创建一个 Angular 组件来托管对话框:
ng generate component confirm-dialog -
打开
confirm-dialog.component.ts文件,并按以下方式修改它:import { Component, **EventEmitter****,** **Input****,** **Output** } from '@angular/core'; @Component({ selector: 'app-confirm-dialog', templateUrl: './confirm-dialog.component.html', styleUrls: ['./confirm-dialog.component.css'] }) export class ConfirmDialogComponent { **@Input****()** **issueNo****:** **number** **|** **null** **=** **null****;** **@Output****() confirm =** **new****EventEmitter****<****boolean****>();** }我们使用
@Input()装饰器来获取问题编号并在组件模板中显示它。confirm事件将发出一个boolean值,以指示用户是否确认解决了问题。 -
创建两个方法,这两个方法将调用
confirm输出属性的emit方法,要么是true,要么是false:agree() { this.confirm.emit(true); this.issueNo = null; } disagree() { this.confirm.emit(false); this.issueNo = null; }
这两个方法都将issueNo属性设置为null,因为该属性也将控制模态对话框是否打开。所以,我们希望在两种情况下都关闭对话框。
我们已经设置了对话框组件的 TypeScript 类。现在让我们通过其模板将其连接起来。打开confirm-dialog.component.html文件,并用以下内容替换其内容:
<clr-modal [clrModalOpen]="issueNo !== null" [clrModalClosable]="false">
<h3 class="modal-title">
Resolve Issue #
{{issueNo}}
</h3>
<div class="modal-body">
<p>Are you sure you want to close the issue?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="disagree()">Cancel</button>
<button type="button" class="btn btn-danger" (click)="agree()">Yes, continue</button>
</div>
</clr-modal>
Clarity 模态对话框由一个<clr-modal>组件和一组具有特定类的 HTML 元素组成:
-
modal-title:显示当前问题编号的对话框标题。 -
modal-body:对话框的主要内容。 -
modal-footer:对话框的页脚,通常用于添加该对话框的动作。我们目前添加了两个 HTML<button>元素,并将它们的click事件分别绑定到agree和disagree组件方法。
不论是打开还是关闭,对话框的当前状态由绑定到issueNo输入属性的clrModalOpen指令指示。当该属性为 null 时,对话框是关闭的。clrModalClosable指令指示对话框不能通过任何其他方式关闭,只能通过程序化地通过issueNo属性关闭。
根据我们的规范,我们希望用户能够直接从列表中解决问题。让我们找出如何将我们创建的对话框与待解决问题列表集成:
-
打开
issues.service.ts文件并添加一个新方法来设置问题的completed属性:completeIssue(issue: Issue) { const selectedIssue: Issue = { ...issue, completed: new Date() }; const index = this.issues.findIndex(i => i === issue); this.issues[index] = selectedIssue; }之前的方法首先创建了一个我们想要解决的问题的副本,并将其
completed属性设置为当前日期。然后它在issues数组中找到初始问题,并用克隆实例替换它。 -
打开
issue-list.component.ts文件并在 TypeScript 类中添加一个selectedIssue属性和一个onConfirm方法:selectedIssue: Issue | null = null; onConfirm(confirmed: boolean) { if (confirmed && this.selectedIssue) { this.issueService.completeIssue(this.selectedIssue); this.getIssues(); } this.selectedIssue = null; }只有当
confirmed参数为真时,onConfirm方法才会调用issueService属性的completeIssue方法。在这种情况下,它还会调用getIssues方法来刷新表格数据。selectedIssue属性持有我们想要解决的问题的Issue对象,并且每次调用onConfirm方法时都会重置。 -
打开
issue-list.component.html文件并在<clr-dg-row>组件内添加一个操作溢出组件:<clr-dg-row *clrDgItems="let issue of issues"> **<****clr-dg-action-overflow****>** **<****button****class****=****"action-item"** **(****click****)=****"selectedIssue = issue"****>****Resolve****</****button****>** **</****clr-dg-action-overflow****>** <clr-dg-cell>{{issue.issueNo}}</clr-dg-cell> <clr-dg-cell>{{issue.type}}</clr-dg-cell> <clr-dg-cell>{{issue.title}}</clr-dg-cell> <clr-dg-cell>{{issue.description}}</clr-dg-cell> <clr-dg-cell> <span class="label" [class.label-danger]="issue.priority === 'high'">{{issue.priority}}</span> </clr-dg-cell> </clr-dg-row>Clarity 组件
<clr-dg-action-overflow>在每一行表格中添加一个下拉菜单。该菜单包含一个按钮,当点击时,将selectedIssue属性设置为当前问题。 -
最后,在模板末尾添加
<app-confirm-dialog>组件:<app-confirm-dialog *ngIf="selectedIssue" [issueNo]="selectedIssue.issueNo" (confirm)="onConfirm($event)"></app-confirm-dialog>我们将
selectedIssue的issueNo属性传递给对话框组件的输入绑定。我们还将
onConfirm组件方法绑定到confirm事件,以便我们可以在用户同意或不同意时得到通知。$event参数是 Angular 中的一个保留关键字,包含事件绑定结果,这取决于 HTML 元素类型。在这种情况下,它包括确认的boolean结果。
我们已经将所有部件放在一起以解决问题。让我们试一试:
-
运行
ng serve并在http://localhost:4200打开应用程序。 -
如果您没有任何问题,请使用 添加新问题 按钮创建一个。
-
点击某一行的操作菜单并选择 解决。菜单是位于 问题编号 列旁边的三个垂直点图标:
图 3.7 – 操作菜单
- 在出现的对话框中,点击 是,继续 按钮:
图 3.8 – 解决问题对话框
点击按钮后,对话框将关闭,问题不应再在列表中可见。
我们为我们的应用程序用户提供了解决问题的方法。我们的问题跟踪系统现在已完整并准备好投入使用!有时,用户可能因为匆忙而报告了已经报告的问题。在下一节中,我们将学习如何利用高级响应式表单技术来帮助他们。
开启新问题建议
响应式表单 API 包含一个机制,用于在特定表单控件值发生变化时接收通知。我们将在我们的应用程序中使用它来在报告新问题时查找相关的问题。更具体地说,当用户开始在标题表单控件中输入时,我们将显示建议问题的列表:
-
打开
issues.service.ts文件并添加以下方法:getSuggestions(title: string): Issue[] { if (title.length > 3) { return this.issues.filter(issue => issue.title.indexOf(title) !== -1); } return []; }前面的方法接受一个问题的标题作为参数,并搜索包含相同标题的任何问题。当
title参数超过三个字符长时,搜索机制被触发,以限制结果到一个合理的数量。 -
打开
issue-report.component.ts文件并从@angular/corenpm 包导入OnInit实体:import { Component, EventEmitter, **OnInit**, Output } from '@angular/core'; -
创建一个新的组件属性来保存建议的问题:
suggestions: Issue[]= []; -
将
OnInit接口添加到IssueReportComponent类实现的接口列表中:export class IssueReportComponent **implements****OnInit** { -
FormGroup对象的controls属性包含所有表单控件作为键值对。键是控件的名称,值是实际的表单控件对象。我们可以通过访问其名称(在这种情况下为title)来获取关于表单控件值变化的通知,以下是这样做的:ngOnInit(): void { this.issueForm.controls.title.valueChanges.subscribe(title => { this.suggestions = this.issueService.getSuggestions(title); }); }每个控件都公开一个
valueChanges可观察对象,我们可以订阅它并获取一个连续的值流。valueChanges可观察对象在用户开始在表单的title控件中输入时立即发出新值。当发生这种情况时,我们将getSuggestions方法的结果设置在suggestions组件属性中。 -
要在组件的模板上显示建议的问题,打开
issue-report.component.html文件,并在<clr-input-container>元素之后添加以下 HTML 代码:<div class="clr-row" *ngIf="suggestions.length"> <div class="clr-col-lg-2"></div> <div class="clr-col-lg-6"> <clr-stack-view> <clr-stack-header>Similar issues</clr-stack-header> <clr-stack-block *ngFor="let issue of suggestions"> <clr-stack-label>#{{issue.issueNo}}:{{issue.title}}</clr-stack-label> <clr-stack-content>{{issue.description}}</clr-stack-content> </clr-stack-block> </clr-stack-view> </div> </div>我们使用 Clarity 库中的
<clr-stack-view>组件以键值对的形式显示建议的问题。键由<clr-stack-header>组件指示,显示问题的标题和编号。《clr-stack-content》组件指示值并显示问题描述。
只有当有可用的建议问题时,我们才会显示类似的问题。
运行 ng serve 并打开报告问题表单以创建新问题。当你开始在 标题 输入框中输入时,应用程序将建议任何与你试图创建的问题相关的任何问题:
图 3.9 – 类似的问题
用户现在将看到是否有任何类似的问题,并避免报告重复的问题。
摘要
在本章中,我们使用响应式表单和 Clarity 设计系统构建了一个 Angular 应用程序来管理和跟踪问题。
首先,我们在 Angular 应用程序中安装了 Clarity,并使用数据网格组件显示待处理问题的列表。然后,我们介绍了响应式表单,并使用它们来构建报告新问题的表单。我们在表单中添加了验证,以向用户提供必填字段的视觉指示并防止不希望的行为。
如果我们的用户能够解决问题,问题跟踪系统才会高效。我们使用 Clarity 构建了一个模态对话框来解决问题。最后,我们通过在报告新问题时建议相关问题来改进了应用程序的 UX。
在下一章中,我们将使用 Angular 服务工作者构建一个用于天气的渐进式网络应用程序。
练习
创建一个 Angular 组件来编辑现有问题的详细信息。该组件应显示问题编号,并允许用户更改标题、描述和优先级。标题和描述应为必填字段。
用户应能够通过待处理问题列表中的操作菜单访问上一个组件。向操作菜单中添加一个新按钮以打开编辑问题表单。
用户完成更新问题后,表单应关闭,并刷新待处理问题的列表。
您可以在 exercise 分支的 Chapter03 文件夹中找到练习的解决方案:github.com/PacktPublishing/Angular-Projects-Third-Edition/tree/exercise。
进一步阅读
-
Angular 表单:
angular.io/guide/forms-overview -
验证响应式表单:
angular.io/guide/form-validation#validating-input-in-reactive-forms -
向组件传递数据:
angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding -
从组件获取数据:
angular.io/guide/component-interaction#parent-listens-for-child-event -
开始使用 Clarity:
clarity.design/documentation/get-started
加入我们的 Discord 社区
加入我们社区的 Discord 空间,与作者和其他读者进行讨论: