PrimeNG-下个阶段的-UI-开发-全-

PrimeNG 下个阶段的 UI 开发(全)

原文:zh.annas-archive.org/md5/08636559d2a183d1de1e803d10540e28

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Angular 是一个强大的 Web 应用程序框架,当与丰富的 UI 组件 PrimeNG 结合时,它成为构建前沿 Web 应用程序的更强大工具。本书提供了在 Angular 开发背景下掌握 PrimeNG 的全面指南。

在各章节中,您将开始一段旅程,探索 PrimeNG 库,从将其集成到 Angular 项目中开始,并研究其广泛的 UI 组件和功能。在这个过程中,您将发现高级技术和最佳实践,如主题定制、性能优化和可重用组件,最终通过实际应用和案例研究展示 PrimeNG 的强大和多功能性。

本书面向对象

如果您是一位渴望提升构建稳健、视觉吸引力强且可扩展 Web 应用程序技能的 Angular 开发者或爱好者,那么这本书是为您准备的。无论您是前端开发者、全栈开发者还是重视性能的人,您都将发现关于定制主题和无缝实现响应式设计的宝贵见解。

通过阅读本书,您将能够充分利用 PrimeNG 的潜力,从而创建出与众不同的卓越 Web 体验。

本书涵盖内容

第一章《介绍 Angular 和 PrimeNG:强大的组合》中,您将了解 Angular 和 PrimeNG 的强大组合,用于开发现代 Web 应用程序。本章涵盖了 Angular 和 PrimeNG 的基础知识、它们的集成以及一起使用的优势。

第二章《设置您的开发环境》中,您将学习如何设置开发环境以使用 PrimeNG 组件构建 Angular 应用程序。本章涵盖了安装 Node.js、Angular CLI 以及创建新的 Angular 项目等主题。

第三章《利用 Angular 的功能和改进》中,您将探索最新的 Angular 功能和它们与 PrimeNG 组件的集成。

第四章《将 PrimeNG 集成到您的 Angular 项目中》中,您将了解如何将 PrimeNG 集成到 Angular 项目中,有效地结合 Angular 和 PrimeNG 的力量来创建功能丰富的应用程序。本章涵盖了添加 PrimeNG 组件、配置 PrimeNG 模块以及自定义组件样式和主题等内容。

第五章《介绍输入组件和表单控件》中,您将看到 PrimeNG 在 Angular 应用程序中提供的各种输入组件和表单控件。本章涵盖了使用文本输入、复选框、单选按钮、下拉菜单等,以及表单验证和处理用户输入等内容。

第六章《与表格、列表和卡片组件一起工作》中,你将看到 PrimeNG 数据展示组件,这些组件在 Angular 应用程序中有效地展示数据。本章涵盖了诸如使用数据表、列表和卡片、创建响应式布局、处理数据排序和实现分页等主题。

第七章《与树、树表和时序组件一起工作》中,重点将放在 PrimeNG 数据展示组件上,这些组件在 Angular 应用程序中有效地管理数据。本章涵盖了诸如与树结构、树表和时序一起工作,以及处理用户交互和事件等主题。

第八章《与导航和布局组件一起工作》中,你将了解如何使用 PrimeNG 导航和布局组件在 Angular 应用程序中创建直观且用户友好的界面。本章涵盖了诸如与菜单、面包屑、标签页和面板一起工作,以及处理导航事件等主题。

第九章《使用主题定制 PrimeNG 组件》中,你将了解如何使用主题定制 Angular 应用程序中的 PrimeNG 组件的外观。本章涵盖了诸如使用预构建主题、创建自定义主题、使用主题设计器以及覆盖组件样式等主题。

第十章《探索 Angular 应用程序的优化技术》中,你将发现针对使用 PrimeNG 组件的 Angular 应用程序性能优化的技巧和窍门。本章涵盖了诸如懒加载、变更检测策略、优化数据绑定以及使用 Angular 内置的性能工具等主题。

第十一章《创建可重用和可扩展的组件》中,你将了解如何使用 PrimeNG 在 Angular 应用程序中创建可重用和可扩展的组件。本章涵盖了诸如 StyleClass、PrimeBlocks、创建可重用的 Angular 组件以及扩展现有的 PrimeNG 组件等主题。

第十二章《与国际化本地化一起工作》中,你将发现如何使用 PrimeNG 组件为 Angular 应用程序添加国际化本地化支持。

第十三章《测试 PrimeNG 组件》中,你将获得有关测试由 PrimeNG 组件驱动的 Angular 应用程序的指导。本章还涵盖了单元测试、使用测试工具和库等主题。

第十四章“构建响应式 Web 应用程序”中,您将学习如何使用 Angular 和 PrimeNG 组件构建响应式 Web 应用程序。本章涵盖了创建项目结构、实现响应式布局、集成各种 PrimeNG 组件以及部署应用程序等主题。

要充分利用本书

在深入本书之前,您应该对 Angular 框架和 Web 开发概念的基础有扎实的理解。熟悉 HTML、CSS、JavaScript 以及 TypeScript 对于充分利用本书至关重要。使用 Angular 组件、指令和服务的经验也将极大地增强您的学习之旅。

虽然不是必需的,但基本了解 UI 设计原则以及使用其他前端库或框架的经验对于理解 PrimeNG 为 Angular 生态系统带来的价值可能有益。有了这些基础,您将准备好探索本全面指南中涵盖的 PrimeNG 强大功能和技巧。

本书涵盖的软件/硬件 操作系统要求
Angular 14+ Windows、macOS 或 Linux
TypeScript 5+
Node.js 18+

本书附有 GitHub 仓库(下一节中提供链接),其中包含代码示例。如果您在特定步骤中遇到任何困难,请参阅 GitHub 上提供的相应工作版本。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG。如果代码有更新,它将在 GitHub 仓库中更新。

我们还提供了来自我们丰富的书籍和视频目录中的其他代码包,可在github.com/PacktPublishing/找到。查看它们吧!

使用的约定

本书使用了多种文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“在运行ng new时,这些只是可用的参数中的一小部分。您可以通过运行ng new --help或参考您所使用的特定版本的官方 Angular 文档来找到更多选项和详细说明。”

代码块设置如下:

<button (click)="handleClick()">Click me!</button>
...
handleClick() {
  // handle user click event
}

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

export class UserListComponent {
  private userService = inject(UserService)
  users$ = this.userService.getUsers()
}

任何命令行输入或输出都应如下编写:

nvm install 18

粗体:表示新术语、重要词汇或您在屏幕上看到的词汇。例如,菜单或对话框中的词汇以粗体显示。以下是一个示例:“从管理面板中选择系统信息。”

小贴士或重要注意事项

它看起来像这样。

联系我们

我们读者提供的反馈总是受欢迎的。

一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。

勘误:尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在此书中发现错误,我们将不胜感激,如果您能向我们报告,请访问 www.packtpub.com/support/errata 并填写表格。

盗版:如果您在互联网上发现我们作品的任何形式的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过电子邮件发送至 copyright@packt.com 并附上材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问 authors.packtpub.com

分享您的想法

一旦您阅读了《PrimeNG 高级 UI 开发》,我们非常乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈

您的评论对我们和科技社区都非常重要,它将帮助我们确保我们提供高质量的内容。

下载此书的免费 PDF 复印本

感谢您购买此书!

您喜欢在路上阅读,但无法携带您的印刷书籍到处走?

您的电子书购买是否与您选择的设备不兼容?

别担心,现在每购买一本 Packt 书籍,您都可以免费获得该书的 DRM-free PDF 版本。

在任何地方、任何设备上阅读。直接从您喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。

优惠不会就此结束,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件。

按照以下简单步骤获取这些好处:

  1. 扫描下面的二维码或访问以下链接

packt.link/free-ebook/9781803249810

  1. 提交您的购买证明

  2. 就这样!我们将直接将您的免费 PDF 和其他好处发送到您的电子邮件。

第一部分:PrimeNG 简介

在本书的第一部分,您将全面了解 PrimeNG,这是一个强大的 Angular UI 组件库。您还将了解如何设置您的开发环境,确保它为集成做好了充分准备。之后,您将学习如何利用其丰富的组件和功能集合来增强您应用程序的用户界面和功能。

在本部分结束时,您将具备在 Angular 应用程序中有效利用 PrimeNG 的知识和技能。

本部分包含以下章节:

  • 第一章, 介绍 Angular 和 PrimeNG:一个强大的组合

  • 第二章, 设置您的开发环境

  • 第三章, 利用 Angular 的功能和改进

  • 第四章, 将 PrimeNG 集成到您的 Angular 项目中

第一章:介绍 Angular 和 PrimeNG:强大的组合

欢迎来到你网络开发之旅的第一步,一个广阔且充满活力的领域!

本章是你进入两个已经彻底改变了网络应用程序开发的强大工具的大门:Angular 和 PrimeNG。

由技术巨头 Google 开发的 Angular 是一个强大的框架,它彻底改变了网络应用程序的开发。它提供了一个构建复杂和高效网络应用程序的结构化路径。凭借双向数据绑定、依赖注入和模块化架构等强大功能,Angular 使开发者能够轻松构建复杂的网络应用程序。

与 Angular 结合,PrimeNG 提供了一系列专为 Angular 设计的预制 UI 组件。这种组合形成了一对强大的搭档,因为 PrimeNG 通过提供一套现成的 UI 组件来补充 Angular 的功能,在构建各种类型的网络应用程序时,这些组件帮助开发者打造美观且用户友好的界面,简化开发过程,并确保一致且引人入胜的用户体验。

在本章中,我们将探讨 Angular 和 PrimeNG 的基础知识,展示它们的集成,并解释联合使用它们的益处。无论你是网络开发领域的初学者还是经验丰富的专业人士,本章都承诺为你提供宝贵的见解,为你在 UI 开发领域的未来冒险铺平道路。

总结来说,我们将涵盖以下主要主题:

  • 介绍 Angular

  • 介绍 PrimeNG

  • 探索 PrimeNG 的关键特性

  • 使用 Angular 和 PrimeNG 一起

技术要求

我们将在下一章中设置开发环境;然而,目前你可以在这里找到本书的代码:github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG。请注意,你可以在 apps 文件夹中找到每个章节的代码。

介绍 Angular

Angular (angular.io/) 是由 Google 开发和维护的一个网络应用程序框架,是一个用于构建动态网络应用程序的全面框架。作为前端开发领域的一根支柱,Angular 通过其强大的功能和面向性能的特性确立了其主导地位。

当我们深入研究 Angular 时,我们可以看到它是由 TypeScript 构建的,TypeScript 是 JavaScript 的静态类型超集。TypeScript 带来了诸如类型安全性和增强的工具支持等特性,这些特性有助于提高 Angular 应用程序的稳健性。TypeScript 强大的类型和 Angular 的架构相结合,使得该框架具有高度的可扩展性,这在构建复杂和大规模应用程序时至关重要。

Angular 的架构是基于组件的。组件控制网站屏幕上的一个部分。组件是模块化和可重用的,这促进了干净且DRY不要重复自己)的代码库。随着应用程序规模和复杂性的增长,它们也使得管理和推理应用程序变得更加容易。

您可以在下面的代码块中看到一个简单的 Angular 组件示例:

// app.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { bootstrapApplication } from '@angular/platform-browser'
@Component({
   selector: 'my-app',
   standalone: true,
   imports: [CommonModule],
   template: `
      <h1>Hello from {{name}}!</h1>
      <a target="_blank" href="https://angular.io/start">
         Learn more about Angular
      </a>
   `,
})
// main.ts
export class AppComponent {
   name = 'Angular'
}
bootstrapApplication(AppComponent)

这段代码演示了一个简单的 Angular 应用程序,它使用bootstrapApplication()函数来初始化应用程序。

让我们分解代码并解释每一部分。首先,我们导入依赖项:

import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { bootstrapApplication } from '@angular/platform-browser'

让我们解释每一部分:

  • 第一行从@angular/core导入Component装饰器,该装饰器用于定义 Angular 组件

  • 第二行从@angular/common导入CommonModule,它为 Angular 应用程序提供常用的指令、管道和服务

  • 第三行从@angular/platform-browser导入bootstrapApplication函数,该函数用于启动 Angular 应用程序

下一个部分是定义一个Angular Component的方式:

@Component({
   selector: 'my-app',
   standalone: true,
   imports: [CommonModule],
   template: `
      <h1>Hello from {{name}}!</h1>
      <a target="_blank" href="https://angular.io/start">
         Learn more about Angular
      </a>
   `,
})
export class AppComponent {
   name = 'Angular'
}

让我们分解这段代码:

  • 这段代码使用@Component装饰器定义了一个名为AppComponent的组件

  • selector属性设置组件的选择器为'my-app',这意味着它可以用<my-app />标签在 HTML 中使用

  • standalone属性设置为true,表示它可以在不声明在NgModule中的情况下使用

  • imports数组包括CommonModule,它为组件提供了所需的常用指令和服务

template属性定义了组件的模板,该模板由一个显示name属性值的<h1>标题组成,值为'Angular',以及一个链接到 Angular 网站的锚标签

最后,我们启动应用程序:

bootstrapApplication(AppComponent)

bootstrapApplication()函数以AppComponent组件作为参数被调用,启动一个 Angular 应用程序实例,并将一个独立组件作为应用程序的根组件进行渲染。

注意

独立组件方法(在 Angular v14 中引入)和 NgModule 方法在启动方式上有所不同。使用 NgModule 时,启动是通过从@angular/platform-browser-dynamic包导出的bootstrapModule()函数完成的。

Angular 的一个优势在于其强大的工具和功能套件,包括以下内容:

  • Angular 的命令行界面CLI)(angular.io/cli)通过自动化各种开发任务简化了 Angular 项目的创建

  • 诸如依赖注入和装饰器等特性使开发者能够编写模块化和易于测试的代码

  • Angular 的指令允许开发者向 DOM 元素添加行为

  • 双向数据绑定使模型和视图保持同步,减少了样板代码的数量

  • 一套用于处理复杂状态管理和路由的综合工具

  • 一个用于与 RESTful 服务交互的 HTTP 客户端

  • 更多内容!

总结来说,Angular 是一个全面性的框架,它结合了性能、可扩展性和丰富的工具和功能。这些因素使得它成为许多开发者的首选,无论是构建简单的网站还是复杂的单页应用程序。

注意

Angular 是一个不断发展的框架,新功能不断被添加。如果您想了解更多关于 Angular 的信息,网上有许多资源可供参考(例如,https://angular.io)。

在下一节中,我们将进入 PrimeNG 的世界。

介绍 PrimeNG

PrimeNG (primeng.org/) 是一个功能丰富的开源 UI 组件库,专门为 Angular 应用程序设计。如图所示,PrimeNG 当前的状态提供了一个令人印象深刻的组件套件,包括 90 多个组件、200 多个图标和 400 多个现成的 UI 块,从简单的按钮和输入等小部件到更复杂和强大的组件,如数据表、图表和树。

图 1.1 – PrimeNG 概览

图 1.1 – PrimeNG 概览

PrimeNG 中的每个组件都是精心制作的。它们不仅功能强大,而且设计精美,符合现代 UI 原则,具有吸引力的外观。组件自带丰富的功能,可以根据您应用程序的具体需求进一步定制。

这是一个按钮片段的示例:

<section class="call-to-action">
   <h2>5 Things Your Spreadsheets Can't Do</h2>
   <button
      routerLink="/spreadsheets/demo"
      pButton
      pRipple
      label="Show Me Now"
      class="p-button-raised p-button-primary"
   ></button>
</section>

这段代码代表了一个具有多个属性和类的 <button> 元素。让我们逐一分析:

  • pButton: 此属性是一个指令,它为按钮添加 PrimeNG 样式和行为。它通过额外的功能和视觉增强来增强按钮。

  • pRipple: 此属性在按钮点击时启用涟漪效果,提供视觉反馈。

  • label="Show Me Now": 此属性设置按钮的标签或文本内容为Show Me Now

  • class="p-button-raised p-button-primary": 此 class 属性将两个 CSS 类应用于按钮——即 p-button-raisedp-button-primary。这些类定义了按钮的外观和样式,使其呈现凸起效果并使用主色调方案。

您可以在以下图中查看结果:

图 1.2 – PrimeNG 按钮

图 1.2 – PrimeNG 按钮

除了庞大的组件库之外,PrimeNG 还拥有强大的社区和活跃的开发团队。定期的更新使库保持新鲜和竞争力,而广泛的文档和示例使您能够轻松开始并找到问题的答案。

总之,PrimeNG 是一个强大的工具,它显著减少了在 Angular 应用程序中构建高质量、交互式用户界面所需的时间和精力。其丰富的预用组件、灵活的主题功能和对无障碍性的承诺,使其成为任何 Angular 开发者的绝佳选择。在下一节中,我们将深入了解 PrimeNG 的关键特性。

注意

虽然 PrimeNG 提供了丰富的功能,但了解 Angular 基础知识对于充分利用其潜力是必不可少的。您可以访问官方的 Angular 网站,或查看 Aristeidis Bampakos 和 Pablo Deeleman 所著的 Packt 的学习 Angular书籍。

探索 PrimeNG 的关键特性

PrimeNG 凭借其丰富的功能集和对细节的关注,在众多 UI 库中脱颖而出。这一套专为 Angular 应用程序设计的综合功能,使其成为寻求强大、易用 UI 库的开发者的首选选择。

让我们来看看 PrimeNG 的一些流行特性:

  • PrimeNG 最引人注目的特性之一是其广泛的 UI 组件。从基本的元素,如按钮和下拉菜单,到更复杂的组件,如数据表、日历和图表,PrimeNG 都能满足您的需求。每个组件都是完全可定制的,让您可以根据自己的需求调整外观和行为。

  • PrimeNG 的另一个关键特性是其主题系统。PrimeNG 附带多种预构建的主题,每个主题都为所有组件提供了独特的样式,但主题系统并不止于此——借助主题设计器,您可以轻松创建与您的品牌形象一致的自定义主题。主题设计器提供了一个用户友好的界面,用于自定义主题的颜色、字体和其他样式方面,正如您在这里所看到的:

图 1.3 – PrimeNG 设计器

图 1.3 – PrimeNG 设计器

  • PrimeNG 也优先考虑无障碍性。其许多组件都内置了对可访问的富互联网应用程序ARIAs)和键盘导航的支持,使您的应用程序对有障碍的用户更加易于访问。对无障碍性的这一承诺证明了 PrimeNG 致力于为有障碍人士创建包容性网络应用的承诺。使用具有无障碍支持的预构建组件,通过利用无障碍专家的专长,确保符合标准,并从持续维护和更新中受益。

  • PrimeIcons 是 PrimeNG 的默认图标库,由 PrimeTek 开发了超过 250 个开源图标。这些图标不仅视觉上吸引人,而且在利用预期的功能通用视觉语言和支持 用户界面体验UIX)方面高度相关。然而,值得一提的是,PrimeNG 组件在图标使用上提供了灵活性,允许使用其他流行的图标库进行模板化,例如 Material Icons (fonts.google.com/icons) 或 Font Awesome (fontawesome.com),这为开发者提供了选择与他们的设计需求和期望的用户体验最佳匹配的图标集的自由。

  • 最后,PrimeNG 是以响应性为设计理念的。组件被构建以适应不同的屏幕尺寸和分辨率,确保你的应用程序在所有设备上看起来都很棒。无论用户是在桌面、平板电脑还是移动设备上,PrimeNG 组件都将提供一致、高质量的用户体验。

总结来说,PrimeNG 提供了一套强大的功能,使其成为任何 Angular 开发者的绝佳选择。从其广泛的组件库和灵活的主题系统,到其对可访问性和响应性的承诺,PrimeNG 拥有创建高质量的交互式用户界面所需的所有工具,适用于你的 Angular 应用程序。

使用 Angular 和 PrimeNG 一起

Angular 和 PrimeNG 的结合为现代网络开发提供了一套强大的工具。它们共同创造了一个高度生产力的环境,显著简化了构建复杂、交互式网络应用程序的过程。以下是一些亮点:

  • 首先要强调的是,PrimeNG 是专门为 Angular 设计的。这意味着所有组件都是为与 Angular 的架构无缝工作而构建的。PrimeNG 组件本质上就是 Angular 组件,因此它们可以像任何其他 Angular 组件一样使用。这种与 Angular 的兼容性导致开发流程简化,你可以在 Angular 应用程序中使用 PrimeNG 组件,而无需任何额外的开销或集成工作。

  • 此外,Angular 和 PrimeNG 在功能上相互补充。Angular 提供了构建具有复杂交互和状态管理的单页应用框架,而 PrimeNG 提供了一系列预构建的 UI 组件来增强用户界面。这意味着你可以利用 Angular 的强大框架来处理应用的逻辑和结构,并使用 PrimeNG 的组件来创建引人入胜、响应式的用户界面。

  • Angular 和 PrimeNG 的集成还提高了代码质量。由于 PrimeNG 提供了现成的、可重用的组件,您可以避免重复代码,专注于实现独特功能和业务逻辑。这可以导致更干净、更易于维护的代码库,这在大型应用中尤其有益。

  • 最后,Angular 和 PrimeNG 都有活跃的社区和广泛的文档。这意味着当您需要帮助或想了解更多关于特定主题的信息时,您有丰富的资源可以利用。Angular 和 PrimeNG 的持续更新和改进也确保了您使用的是最新的工具,这些工具遵循 Web 开发的最新最佳实践。

本质上,Angular 强大的框架与 PrimeNG 丰富的 UI 组件组合提供了一套完整的解决方案,用于构建动态、美观的 Web 应用程序。这种结合不仅提高了生产力,还促进了高效、可扩展和可维护的应用程序的开发。

注意

Angular 和 PrimeNG 都是开源的,这意味着它们不仅免费使用,还可以根据独特需求进行定制,例如自定义组件或主题。

摘要

本章,我们开始了 Angular 和 PrimeNG 这两个强大工具的探索之旅,它们结合在一起,释放了创建现代和复杂 Web 应用程序的潜力。在本章中,您对这些工具有了初步的了解,并探讨了利用它们的优点。Angular 提供了底层结构,而 PrimeNG 则通过现成的 UI 组件对其进行增强。这种结合使开发者能够加速工作流程,从而缩短开发周期并编写更易于维护的代码。

本章中获得的知识对于寻求提升 Web 开发技能的专业开发者来说是无价的。随着我们深入后续章节,我们将探讨如何充分利用 Angular 和 PrimeNG,创建各种应用。无论您是初出茅庐的开发者还是经验丰富的专业人士,这次旅程都承诺将富有洞察力和丰富性。所以,系好安全带,我们准备一起探索 Angular 和 PrimeNG 在 Web 开发领域的迷人世界。

在下一章中,我们将深入探讨设置开发环境的过程。我们将探讨技术要求,包括 Node.js、Yarn/NPM、GitHub 和 VS Code,并指导您完成安装过程。此外,我们还将介绍 Angular CLI,这是一个强大的 CLI,可以简化 Angular 开发。

随着我们共同踏上这个启发性的旅程,准备好将您的开发技能提升到新的水平吧!

第二章:设置你的开发环境

在本章中,我们将深入探讨设置你的开发环境以使用 PrimeNG 组件构建 Angular 应用程序的关键任务。本章将为你提供创建无缝且高效开发环境所需的知识和工具。从安装所需的软件到理解项目结构,我们将指导你完成每个步骤,以确保设置过程顺利。

到本章结束时,你将拥有一个配置良好的开发环境,并配备开始使用 PrimeNG 构建 Angular 应用程序所需的工具。了解技术要求、设置 Angular CLI 以及熟悉项目结构将为你的 Web 开发之旅打下坚实的基础。此外,利用 VS Code 等 IDE 以及使用有用的扩展将提高你的工作效率,使开发过程更加高效。

那么,让我们深入探讨并设置你的开发环境,以获得最佳的 Angular 开发体验。在本章中,我们将涵盖以下主题:

  • 设置 Angular CLI

  • 创建新的 Angular 项目

  • 理解项目结构

  • 发现有用的 VS Code 扩展

技术要求

本章包含来自新 Angular 项目的各种代码示例。你可以在以下 GitHub 仓库的chapter-02文件夹中找到相关源代码:github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG

在深入设置过程之前,确保你的系统满足开发所需的技术要求非常重要。让我们看看你需要准备的关键组件:

  • Node.js (NVM):使用Node 版本管理器NVM)安装 Node.js,这是一个 JavaScript 运行时,以在你的系统上管理多个 Node.js 版本。你可以从官方网站下载并安装 NVM:github.com/nvm-sh/nvm。如果你的公司对 NVM 的使用有限制,请参考官方 Node.js 网站(nodejs.org)的安装说明,并遵循提供的指南。

  • npm:选择 npm 来管理 Angular 项目中的依赖项。它捆绑了 Node.js,所以如果你已经安装了 Node.js,你将能够使用 npm。如果你希望使用 npm 的替代品,你可以查看 Yarn(yarnpkg.com)或 pnpm(pnpm.io)。

  • GitHub:注册一个 GitHub 账户以利用这个基于网络的托管服务进行版本控制和协作。GitHub 允许你跟踪变更、与团队成员协作以及托管你的 Angular 仓库。在github.com注册一个账户。

  • VS Code(Visual Studio Code):安装 VS Code,这是由微软开发的一个免费且可扩展的源代码编辑器。VS Code 提供了对 Angular 的内置支持,并与 Angular CLI 无缝集成,提供代码补全和调试等功能。从官方网站下载 VS Code:code.visualstudio.com

通过确保您已安装 Node.js(NVM)、包管理器(npm、Yarn 或 pnpm)、GitHub 和 VS Code,您将为设置 Angular 开发环境打下坚实的基础。这些工具将使您能够高效地构建、管理和协作 PrimeNG 组件的 Angular 项目。

设置 Angular CLI

Angular CLI命令行界面)是一个强大的工具,它简化了创建、开发和维护 Angular 应用程序的过程。它提供了一套命令,可以自动化常见的开发任务,让你能够专注于构建应用程序,而不是手动设置项目结构。在本节中,我们将指导你完成 Angular CLI 的安装过程,并提供其核心命令的概述。

注意

在安装过程中,请确保您有一个稳定的互联网连接。根据您的网速,下载和安装所需的包可能需要一些时间。

使用 NVM 安装 Node.js v18

要安装 NVM 并将 Node.js v18 设置为默认版本,请按照以下步骤操作:

  1. 访问 GitHub 上的官方 NVM 仓库:github.com/nvm-sh/nvm

  2. 根据您的操作系统遵循特定的安装说明。这通常涉及运行一个脚本来下载和安装 NVM。以下脚本将帮助下载和安装 NVM v0.39.3:

    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
    

注意

NVM 的版本可能会更改,请访问官方网站获取最新版本和说明。

  1. 打开一个新的终端窗口或重启终端以加载 NVM。

  2. 运行以下命令以验证 NVM 是否已安装:

    nvm --version
    // result
    0.39.3
    

    你应该在终端中看到 NVM 的版本号。在这个例子中,当前版本是 0.39.3

  3. 之后,运行以下命令使用 NVM 安装 Node.js v18:

    nvm install 18
    
  4. 为了在新的终端会话中使用 Node.js v18,我们需要将其设置为默认版本。为此,请运行以下命令:

    nvm alias default 18
    
  5. 最后,运行以下命令以验证 Node.js v18 是否已安装并设置为默认:

    node --version
    npm --version
    

这些命令应显示 Node.js 和 npm 的版本号,并且它们应与已安装的 Node.js v18 相对应。

在安装了 Node.js v18 之后,让我们继续安装 Angular CLI。

安装 Angular CLI

根据您的操作系统完成以下安装 Angular CLI 的说明。

对于 Windows 计算机,请执行以下操作:

  1. 打开命令提示符或 PowerShell。

  2. 运行以下命令以全局安装 Angular CLI:

    npm install -g @angular/cli
    

对于 Linux/macOS 计算机,请执行以下操作:

  1. 打开终端。

  2. 运行以下命令以全局安装 Angular CLI:

    npm install -g @angular/cli
    

注意

如果在安装 Node.js 或 Angular CLI 时使用 npm 软件包管理器遇到权限错误,您可能需要在命令前使用 sudo 以管理员权限运行它们。

对于 macOS(使用 Homebrew),请执行以下操作:

  1. 打开终端。

  2. 通过运行以下命令安装 Homebrew:

    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)
    
  3. 一旦安装了 Homebrew,请运行以下命令来安装 Node.js:

    brew install angular-cli
    

在遵循您系统的适当安装过程之后,使用以下命令确认 Angular CLI 的版本:

ng version

您将看到如图 2.1 所示的输出。如您所见,在撰写本书时,Angular CLI 的版本为 17.0.6

图 2.1 – Angular CLI 版本

图 2.1 – Angular CLI 版本

Angular CLI 提供了一系列命令以简化开发过程。以下是一些最常用的命令及其说明:

  • ng new [project-name]: 使用指定的名称创建新的 Angular 项目。它设置项目结构,安装依赖项,并生成初始样板代码。

  • ng serve: 启动开发服务器并编译您的 Angular 应用程序。它监视文件中的更改,并在浏览器中自动重新加载应用程序。

  • ng generate [schematic] [name]: 生成您的 Angular 应用程序的不同元素,如组件、服务、模块等。它构建必要的文件并更新所需的配置。

  • ng build: 为生产构建 Angular 应用程序。它编译代码并生成可部署到 Web 服务器的优化文件。

  • ng test: 执行 Angular 应用程序的单元测试。它使用配置的测试运行器运行测试,并提供有关测试结果的详细信息。

  • ng lint: 分析代码以查找潜在错误和代码风格违规。它有助于强制执行编码标准和维护代码质量。

  • ng deploy: 将您的 Angular 应用程序部署到托管平台,例如 GitHub Pages 或 Firebase Hosting。它自动化部署过程,使您的应用程序对公众可访问。

注意

您可以运行 ng help 来查看命令列表及其用法。您还可以查看官方文档,以了解所有命令的概述,请参阅 angular.io/cli#command-overview

通过利用 Angular CLI,您可以简化开发工作流程,自动化重复性任务,并专注于构建高质量的 Angular 应用程序。在 Windows、Linux 和 macOS 上的安装过程,包括使用 Homebrew 的 macOS 的替代选项,确保您拥有利用 Angular CLI 力量的必要工具。

现在你已经设置了 Angular CLI,探索它提供的各种命令来创建、构建、测试和部署你的 Angular 应用程序。使用 Angular CLI,你将提高作为专业开发者的生产力,并解锁构建强大和可扩展的 Web 应用程序的全部潜力。在下一节中,我们将开始创建一个新的 Angular 项目。

创建一个新的 Angular 项目

创建一个新的 Angular 项目是一个简单的过程。在本节中,我们将指导你完成创建新 Angular 项目的步骤。我们还将探索新 Angular 项目中每个文件的结构和目的。

要创建一个新的 Angular 项目,请按照以下步骤操作:

  1. 打开你的命令提示符或终端。

  2. 导航到你想要创建项目的目录。

  3. 运行以下命令以生成一个新的 Angular 项目,将 my-app 替换为你项目期望的名称:

    ng new my-app
    

    ng new 命令使用默认配置和项目结构创建一个新的 Angular 项目。它安装必要的依赖项并设置应用程序的初始文件。

当运行此命令以创建新的 Angular 项目时,你可以使用几个参数(标志)来自定义项目设置。以下是一些常用参数:

  • --dry-run:在不实际创建文件的情况下执行项目生成的预览。这允许你在提交项目创建之前查看将要生成的文件。

  • --standalone:基于独立 API 创建应用程序,不使用 NgModules。

  • --inline-style--inline-template:指定是否使用内联样式或模板。默认情况下,Angular 生成单独的样式和模板文件。使用这些标志,你可以选择在组件文件中具有内联样式或模板。

  • --prefix:设置生成的组件选择器的前缀。前缀被添加到项目中生成的每个组件的选择器中。

  • --style:指定项目要使用的样式格式,例如 CSS、SCSS、Sass、Less 或 Stylus。例如,--style=scss 将配置项目使用 SCSS 作为默认样式格式。

  • --routing:为初始项目生成路由配置。

  • --skip-git:跳过在项目目录中初始化新的 Git 仓库。如果你更喜欢手动管理版本控制,这很有用。

  • --skip-tests:在创建新组件时防止生成规范文件用于单元测试。如果你不希望默认生成测试文件,请使用此标志。

  • --skip-install:在项目创建后跳过安装 npm 包。如果你更喜欢稍后手动运行 npm installyarn 来安装依赖项,请使用此选项。

  • --directory:指定创建项目的目录名称。默认情况下,项目将创建在与项目同名的文件夹中。

  • --minimal:创建一个没有任何测试框架的工作区(仅用于学习目的)。

以下是用这些选项生成新 Angular 项目的命令:

ng new primeng-demo --standalone --style=scss --inline-style --inline-template

注意

使用 --standalone 选项强烈推荐,因为它减少了样板代码,并且在 Angular 17 中成为默认行为。

这些只是运行 ng new 时可用的参数中的一部分。您可以通过运行 ng new --help 或参考您所使用的特定版本的官方 Angular 文档来找到更多选项和详细说明。

运行脚本后,Angular CLI 将询问是否启用服务器端渲染 (SSR) 和静态站点生成 (SSG/预渲染)。选择 NO 作为答案,因为目前这不相关:

? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation
(SSG/Prerendering)? (y/N)N

图 2**.2 显示了创建新 Angular 项目的最终结果:

图 2.2 – Angular CLI 结果

图 2.2 – Angular CLI 结果

之后,您可以运行 ng serve 来检查新创建的项目,如下所示:

 > ng serve
Initial Chunk Files | Names           | Raw Size
polyfills.js        | polyfills       | 82.71 kB |
main.js             | main            |  1.57 kB |
styles.css          | styles          | 96 bytes |
                    | Initial Total | 84.38 kB
Application bundle generation complete. [2.899 seconds]
Watch mode enabled. Watching for file changes...
      Local:    http://localhost:4200/

然后,您可以访问 localhost:4200/ 来检查您的 Web 应用程序 – 见 图 2**.3

图 2.3 – Angular 示例应用程序

图 2.3 – Angular 示例应用程序

现在我们已经创建了第一个 Angular 应用程序,让我们来了解一下其结构。

理解项目结构

理解您 Angular 项目中每个文件的目的对于有效地导航和开发应用程序至关重要。每个文件在 Angular 项目的整体结构和功能中都扮演着特定的角色。

以下是对 Angular 新结构的简要概述:

  • README.md: 包含 Angular 应用程序的描述。

  • .editorconfig: 包含代码编辑器的配置。

  • .gitignore: 指定 Git 应该忽略的有意未跟踪的文件。

  • angular.json: 包含 CLI 对工作区中所有项目的配置默认值,包括 CLI 使用的构建、服务和测试工具的配置选项。

  • package.json: 指定了应用程序的依赖项、开发依赖项、脚本、许可协议等内容。

  • tsconfig.json: 指定了 Angular 应用程序的 TypeScript 编译器配置。

  • tsconfig.app.json: 指定应用程序主模块的 TypeScript 编译器配置。

  • tsconfig.spec.json: 指定应用程序单元测试的 TypeScript 编译器配置。

  • src 目录包含 Angular 应用程序的源代码。它分为以下子目录:

    • main.ts: Angular 应用程序的入口点。

    • favicon.ico: 应用程序的小图标。

    • index.html: 应用程序的主要 HTML 文件。

    • styles.scss: 应用程序的主要 CSS 文件。

    • app: 包含应用程序的组件、服务、指令、管道等。

    • app.component.spec.ts: 应用程序主组件的单元测试。

    • app.component.ts: 应用程序主组件的定义。

    • app.config.ts: 应用程序主入口点的配置文件。

    • assets:包含应用程序的资产,如图片和字体。

Angular 的新结构相对于之前的结构(在 Angular 14 之前)是一个重大改进。它使开发 Angular 应用程序和维护变得更加容易,并且与其他 Web 框架的结构更加一致。

注意

Angular 发布了一个新网站,提供了更多关于最新 Angular 功能的教程和课程。你可以在 angular.dev 上了解更多信息。

现在你已经创建了你的 Angular 项目并探索了项目结构,你就可以利用 Angular 和 PrimeNG 组件的力量开始构建你的应用程序了。在开始之前,让我们了解一下一些有用的 VS Code 扩展,它们将在开发过程中帮助我们。

发现有用的 VS Code 扩展

当谈到开发 Angular 应用程序时,拥有合适的工具可以大大提高你的生产力和效率。开发者中最受欢迎的代码编辑器之一是 Visual Studio CodeVS Code)。VS Code 拥有一系列扩展,可以帮助简化你的 Angular 开发工作流程。在本节中,我们将介绍一些专门针对 Angular 开发的有用 VS Code 扩展。

Angular 语言服务

Angular 语言服务 扩展是 Angular 开发者的一个无价工具。此扩展为 Angular 模板(包括内联和外部)提供了丰富的编辑体验,包括以下内容:

  • 完成列表:为 Angular 模板语法提供建议和自动完成,帮助开发者更高效、更准确地编写代码。

  • AOT 诊断消息:显示与 Angular 模板中 Ahead-of-TimeAOT)编译相关的编译时诊断消息,帮助开发者捕获错误并提高代码质量。

  • 快速信息:当在模板中悬停时,提供有关 Angular 指令和组件的上下文信息和文档,帮助开发者有效地理解和使用 API。

  • 转到定义:允许开发者导航到模板中符号的定义,使理解组件和指令的实现更加容易,并促进代码探索和调试。

图 2.4 展示了扩展的自动完成功能的一个示例——在这里我们在模板中输入 heading,扩展会给出来自组件属性的自动完成选项:

图 2.4 – Angular 语言服务自动完成示例

图 2.4 – Angular 语言服务自动完成示例

注意

模板自动完成仅适用于组件的公共属性。

编辑器配置

你的项目中的.editorconfig文件应用了定义的规则到你的代码中。使用 Editor Config,你可以强制执行缩进样式、行结束、编码和其他格式化偏好。这个扩展在与其他开发者协作进行 Angular 项目时特别有用,因为它有助于保持统一的代码风格并最小化与风格相关的冲突。

下面是一个新创建的 Angular 项目中.editorconfig文件的示例:

# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

如你所见,规则是描述性的——例如,设置文件的字符编码为 UTF-8 或为所有 TypeScript 文件使用单引号。

Angular Schematics

Angular Schematics是一个强大的扩展,它与 Angular CLI 集成,并提供了一种生成和修改代码的脚手架机制。它允许你轻松地生成组件、模块、服务和其他 Angular 工件。使用 Angular Schematics,你可以快速创建样板代码,并在 Angular 项目中遵循一致的模式和实践。它通过自动化重复性任务来节省时间,并帮助在整个代码库中保持标准化的结构。

你可以在图 2.5中看到有一系列选项,我们可以生成一个名为about的组件,而无需记住命令详情:

图 2.5 – Angular Schematics

图 2.5 – Angular Schematics

自动重命名标签

自动重命名标签扩展是一个节省时间的工具,它会在你编辑 HTML 标签时自动重命名它们。当你修改元素的打开或关闭标签时,这个扩展会更新代码库中的相应标签,确保一致性并防止标签不匹配。它消除了手动重命名标签的需要,这在大型 Angular 项目中,尤其是具有复杂 HTML 结构的项目中,可能会出错且耗时。

Nx Console

这是一个附加部分。如果你更喜欢使用Nx Workspace (nx.dev)进行 Angular 开发,这个扩展就是为你准备的。VS Code 的Nx Console扩展简化了 Angular 开发,直接在 IDE 内提供代码生成、依赖图可视化以及提高生产力的功能。它提高了效率,增强了代码质量,并加速了开发工作流程。

摘要

本章致力于设置开发环境以使用 PrimeNG 组件构建 Angular 应用程序的关键任务。我们首先讨论了技术要求,包括 Node.js、Yarn/npm、GitHub 和 VS Code。为这些工具提供了详细的安装说明,确保你有无缝开发体验的必要先决条件。

本章接着聚焦于 Angular CLI,这是一个用于 Angular 开发的强大命令行界面。我们向您介绍了在 Windows、Linux 和 macOS 上的安装过程,使您能够利用 Angular CLI 的广泛功能,包括脚手架、构建和测试 Angular 应用程序。此外,我们还涵盖了创建新的 Angular 项目、探索最新的独立组件选项和 Angular 项目模板。现在,您应该已经拥有了一个配置良好的开发环境,并配备了 Angular CLI,准备好开始使用 Angular 和 PrimeNG 构建现代网络应用的激动人心的旅程。

此外,我们还向您介绍了几个专为 Angular 开发量身定制的不可或缺的 VS Code 扩展。这些扩展,如 Angular 语言服务、编辑器配置、Angular 模式和自动重命名标签,显著提升了您的编码体验并提高了生产力。这些扩展具有智能代码补全、格式化辅助和工作区管理等功能,确保您保持一致的编码风格,并在 VS Code 环境中简化开发工作流程。通过利用这些扩展的力量,您将充分准备去应对 Angular 开发的挑战,并在整个开发过程中最大限度地提高效率。

在您的开发环境完全设置和优化后,您现在可以深入下一章,探索 Angular 和 PrimeNG 在创建卓越网络应用中的全部潜力。具体来说,在下一章中,我们将介绍 Angular 在最新版本中发布的核心功能和改进。

第三章:利用 Angular 的功能和改进

欢迎来到激动人心的现代 Angular 开发世界。凭借其快速的六个月发布周期,Angular 发生了巨大的变化,引入了新的功能和更新,以非常有趣的方式改变了我们对待应用程序的方法!

在本章中,我们将探讨 Angular 17,这是撰写本文时的最新 Angular 框架版本,并了解其新功能和改进如何赋予开发者构建前沿 Web 应用程序的能力。我们还将深入研究 Angular 的核心功能,了解它们如何增强开发过程。

在本章中,我们将涵盖以下主题:

  • 介绍现代 Angular

  • 了解 Angular 的核心功能和改进

  • 组织 Angular 项目

技术要求

本章包含 Angular 核心功能和概念的多种代码示例。您可以在以下 GitHub 仓库的 chapter-03 文件夹中找到相关源代码:github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-03

介绍现代 Angular

在我们开始本章之前,让我们花一点时间探索 Angular 框架的历程,并了解它是如何随着时间的推移而演变,成为 Web 开发领域的一股强大力量。从其诞生到最新版本,Angular 不断进化,以满足开发者不断变化的需求,并为构建复杂的 Web 应用程序提供坚实的基础。

Angular 框架最初由 Google 于 2010 年推出,名为 AngularJS(使用 JavaScript)。它在 Web 开发领域是一次变革,提供了一种声明性和强大的方法来构建动态用户界面。然而,随着 Web 技术的进步和开发者对可扩展性和性能的需求增加,Angular 经历了重大转型。

Angular 发展历程中的一个关键时刻是 2016 年发布的 Angular 2(使用 TypeScript)。Angular 2 引入了框架的全面重写,采用了基于组件的架构和响应式编程等现代概念。这一转变为 Angular 的未来增长奠定了基础,并为后续版本做好了准备。

在随后的版本中,Angular 继续优化其功能并引入了新的功能,如包大小优化、向后兼容性、动画、摇树优化和服务器端渲染。值得注意的是,Angular 版本 14 对框架进行了重大改进。它通过引入独立组件强调了更模块化的方法,使开发者能够创建可重用和封装的代码。这种模块化架构彻底改变了开发者对待 Angular 开发的方式,提高了代码的可维护性和可重用性。

理解这些更改可以帮助您利用最新的特性和最佳实践,从而构建更高效和可扩展的应用程序。在下一节中,让我们概述 Angular 最近版本中引入的现有功能和新的改进。

了解 Angular 的核心功能和改进

在本节中,让我们探讨一些 Angular 带来的特性和改进,包括数据绑定、组件、服务、指令、管道、信号和控制流。理解 Angular 的一些核心概念非常重要,这样我们才能更好地理解 Angular 和 PrimeNG 在以下章节中的协同工作。

Angular 数据绑定

数据绑定是 Angular 中的一个基本概念,它使组件和视图之间的数据同步成为可能。它允许您在组件中的数据与模板中的 HTML 元素之间建立连接。数据绑定确保组件中的任何更改都会自动反映在视图中,反之亦然。

Angular 支持多种类型的数据绑定。让我们探索每种类型:

  • {{}},用于将组件属性绑定到视图。以下是一个示例:

    <p>Welcome, {{ username }}!</p>
    

    在代码中,组件的 username 属性被插值到 <p> 元素中,如下所示:

图 3.1 – 插值示例

图 3.1 – 插值示例

  • [ ]. 以下是一个示例:

    <input [value]="username" />
    

    在前面的代码中,<input> 元素的价值绑定到组件的 username 属性。

  • (). 以下是一个示例:

    <button (click)="handleClick()">Click me!</button>
    ...
    handleClick() {
      // handle user click event
    }
    

    在代码中,组件的 handleClick() 方法绑定到 <button> 元素的 click 事件。

  • [(ngModel)] 指令,它将模板中表单控件的价值与组件类中的一个属性同步。以下是一个示例:

    // FormsModule needs to be imported for this work on form input
    import { FormsModule } from '@angular/forms'
    <input [(ngModel)]="username" />
    

    在代码中,<input> 元素的价值绑定到组件的 username 属性。任何输入字段中的更改都将更新组件属性,反之亦然。

要在 Angular 中使用数据绑定,您需要定义组件属性并将其绑定到模板中适当的 HTML 元素。让我们看看使用插值和属性绑定的示例:

@Component({
  selector: 'app-greeting',
  template: `
    <p>Welcome, {{ username }}!</p>
    <input [value]="username" />
  `
})
export class GreetingComponent {
  username = 'John Doe';
}

在这里,GreetingComponent 有一个设置为 'John Doe'username 属性。<p> 元素中的 {{ username }} 实例使用插值在模板中显示 username 的值。<input> 元素中的 [value]="username" 实例使用属性绑定来设置输入字段的初始值。

Angular 组件

当使用 Angular 构建应用程序时,组件在定义应用程序不同部分的用户界面和行为中起着核心作用。在 Angular 中,组件封装了渲染用户界面特定部分所需的 HTML 模板、CSS 样式和 TypeScript 代码。通过将应用程序划分为更小、自包含的单元,它促进了可重用性、可维护性和模块化。

在 Angular 的早期版本中,组件通常使用 @NgModule 装饰器在 Angular 模块内注册和管理。@NgModule 装饰器用于定义一个模块,它是相关组件、指令、服务和其他实体的容器。在 @NgModule 中,您可以使用 declarations 属性指定属于它的组件。以下是一个示例:

@NgModule({
  declarations: [AppComponent, HeaderComponent, FooterComponent],
  // Other module properties...
})
export class AppModule { }

在此代码片段中,declarations 属性包括三个组件:AppComponentHeaderComponentFooterComponent。这些组件可以在模块中使用,也可以导出以在其他模块中使用。

自 Angular v14 以来,我们还有一个新的概念,即独立组件。在 @NgModule 下的 declarations 数组。让我们从头开始创建一个新的独立组件。

让我们使用 Angular CLI 生成一个新的独立组件:

ng g c alert
...
CREATE src/app/alert/alert.component.scss (0 bytes)
CREATE src/app/alert/alert.component.html (20 bytes)
CREATE src/app/alert/alert.component.spec.ts (547 bytes)
CREATE src/app/alert/alert.component.ts (294 bytes)

注意

如果你使用的是低于 v17 的 Angular 版本,确保在生成独立组件时附加 --standalone

命令成功地在指定目录中生成了警报组件,以及与其相关的样式、模板、测试和组件文件:

  • alert.component.scss:此文件旨在定义针对警报组件的特定样式

  • alert.component.html:此文件旨在包含警报组件的模板代码

  • alert.component.spec.ts:此文件用于编写警报组件的测试

  • alert.component.ts:此文件是实现警报组件逻辑和行为的主要文件

注意

您可以通过使用 --inline-style--inline-template 选项来进一步增强应用程序的大小和可维护性,这些选项允许您合并内联样式和模板。这种方法减少了文件依赖性,从而使得应用程序更加精简和易于管理。这种方法还将鼓励您编写更小、更易于维护的组件,因为一个常见的反模式是拥有单一复杂且庞大的组件。

现在我们来详细看看警报组件:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
  selector: 'app-alert',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './alert.component.html',
  styleUrls: ['./alert.component.scss']
})
export class AlertComponent {}

提供的代码在 Angular 应用程序中定义了一个 AlertComponent 实例。让我们分析一下代码:

  • @Component({ ... }):这是一个用于定义 Angular 组件的装饰器。它提供了有关组件的元数据。

  • selector: 'app-alert':此属性指定了该组件的 HTML 标签选择器。在这种情况下,该组件可以使用 <app-alert /> 标签在模板中使用。

  • standalone: true:这表示该组件是自包含的,可以通过导入到另一个独立组件或NgModule中使用。

  • imports: [CommonModule]:此属性指定了应导入此组件的模块或组件。在这种情况下,它导入了CommonModule,这是使用通用 Angular 指令所必需的。

  • templateUrl: './alert.component.html':此属性指定了与该组件关联的外部 HTML 模板文件的 URL。

  • styleUrls: ['./alert.component.scss']:此属性指定了与该组件关联的外部样式表 URL 数组。

建议在开始新项目时,我们应该利用独立组件,因为它将有助于简化我们构建 Angular 应用程序的方式。对于未来的迁移来说,这也是更好的选择,因为当在 Angular 17 中创建新应用程序时,独立组件将是默认选项。

注意

Angular 仍然支持NgModule,你也可以混合使用NgModule和独立组件而不会出现任何问题。不仅如此,我们还可以创建独立的指令或管道。

总结来说,Angular 组件是 Angular 应用程序的构建块,提供封装的功能和 UI 表示。通过利用组件的力量,你可以创建可重用和模块化的代码,这有助于促进可维护性和代码组织。现在,让我们探索 Angular 开发中的另一个基本概念:依赖注入。

依赖注入

在其核心,依赖注入(DI)是一种设计模式,它允许对象从外部源接收其依赖,而不是内部创建。在 Angular 中,DI 系统负责为组件、服务和其他 Angular 构造提供所需的依赖。DI 在解耦组件、促进代码重用、可维护性和可测试性方面发挥着关键作用。

在 Angular 中使用 DI(依赖注入),依赖通常定义为类所依赖的服务或其他组件。这些依赖在类的构造函数中声明,当类实例化时,Angular 的 DI 系统会自动解析并注入适当的实例。

在使用 DI 时,以下是一些最佳实践:

  • 单例服务:默认情况下,Angular 服务是单例的,这意味着在整个应用程序中只有一个服务实例。这是一个好习惯,因为它确保了数据的一致性和优化了内存使用。

  • providedIn属性值为root。这确保了服务在应用程序范围内可用,并且如果未使用,则会被摇树优化。

  • 分层注入器:了解 Angular 有一个分层注入器系统。虽然根提供的服务在应用程序范围内可用,但组件级别提供的服务仅在该组件及其子组件内可用。

  • 基于接口的注入:有时,根据接口而不是具体类注入服务是有益的。这使得系统更加灵活,并允许更容易地进行测试和模拟。

  • 避免在构造函数中包含复杂逻辑:由于构造函数是注入的主要位置,请保持其简洁。避免放置可能阻塞组件初始化的复杂逻辑或操作。

在下一节中,我们将深入探讨实际中依赖注入的工作原理。

Angular 服务

在 Angular 中,服务是一个类,它为应用程序中的多个组件提供特定的功能或数据。服务充当组件之间的桥梁,促进数据的共享、与外部 API 的通信以及执行各种业务逻辑操作。它们促进了应用程序中的代码重用、模块化和关注点分离。

要在 Angular 中使用服务,我们首先需要创建服务类。我们可以使用ng generate service Angular CLI 命令生成一个新的服务。一旦创建了服务,我们就可以使用依赖注入将其注入到任何组件或另一个服务中。

让我们以一个管理用户相关操作(如从 API 获取用户数据)的UserService实例为例。以下是创建UserService实例的方法:

import { Injectable } from '@angular/core'
@Injectable({
  providedIn: 'root'
})
export class UserService {
  getUsers(): Observable<User[]> {
    // Fetch user data from API
  }
}

在此代码中,UserService是通过使用@Injectable装饰器创建为一个可注入的服务,这表明这个类有资格进行 DI。然后providedIn: 'root'选项确保 Angular 创建一个在整个应用程序中共享的单例服务。

现在,让我们看看如何在组件中使用UserService

import { Component, inject } from '@angular/core'
import { UserService } from './user.service'
@Component({
  selector: 'app-user-list',
  template: `
    <ul>
      <li *ngFor="let user of users$ | async">{{ user.name }}</li>
    </ul>
  `
})
export class UserListComponent {
  private userService = inject(UserService)
  users$ = this.userService.getUsers()
}

提供的代码演示了在 Angular 组件中(特别是在UserListComponent中)使用UserService的 DI。让我们分解它并解释其功能:

  • private userService = inject(UserService): 这个inject函数手动注入UserService的一个实例,因此UserListComponent可以访问其属性和方法。您也可以在constructor中运行inject

  • users$ = this.userService.getUsers(): 这行代码声明了users$属性,其值来自UserService实例的getUser()方法的结果。

  • *ngFor="let user of users$ | async": 这行代码允许遍历users$集合,并为每个项目生成 HTML 元素。async管道与订阅users$可观察对象结合使用,以处理数据的异步特性。async管道在组件销毁时自动取消订阅可观察对象,防止内存泄漏。

注意

users$ 中使用 $ 的用法是一个模式,表示该属性是一个可观察的。可观察的 是来自 RxJS 库的核心功能,它是一种处理随时间异步事件或数据流的机制。你可以在 rxjs.dev/guide/observable 上了解更多信息。

Angular 指令

指令 是 Angular 中的一项强大功能,它允许你扩展 HTML 元素的功能。它们用于操作 DOM、应用自定义行为以及动态更改元素的外观或行为。Angular 提供了三种类型的指令:

  • <app-my-component>,Angular 解释为创建相应组件的实例,封装其行为、视图和数据交互。组件指令与其他指令的区别在于组件包含模板。

  • ngStyle 指令可以同时更改多个样式。

  • *ngFor 指令可以用来渲染项目列表,而 *ngIf 可以根据布尔表达式有条件地显示或隐藏元素。

要在 Angular 中使用指令,你可以利用 Angular 提供的内置指令,或者创建你自己的自定义指令。让我们看看使用内置的 ngIf 指令有条件地显示元素的例子:

<div *ngIf="showMessage">
  <p>This message is shown conditionally.</p>
</div>

在代码中,ngIf 指令被应用于 <div> 元素。showMessage 属性被评估,如果它是真值,则 <div> 元素及其内容将被渲染。否则,它们将从 DOM 中移除。

你也可以通过运行以下命令来创建自定义指令,以封装特定的行为或样式:

ng g d fallback-image 

下面是一个创建自定义指令 FallbackImageDirective 的例子,当找不到现有图像时,它会显示一个后备图像:

import { Directive, Input, ElementRef, HostListener, inject } from '@angular/core'
@Directive({
  selector: 'img[fallbackImage]',
  standalone: true
})
export class FallbackImageDirective {
  private el = inject(ElementRef)
  @Input() fallbackImage: string
  @HostListener('error')
  private onError() {
    const img = new Image()
    img.src = this.fallbackImage
    img.onload = () => (this.el.nativeElement.src = this.fallbackImage)
  }
}

在此代码中,FallbackImageDirective 是使用 @Directive 装饰器创建的。它定义了一个选择器 img[fallbackImage],这意味着指令将被应用于具有 fallbackImage 属性的图像元素。

下面是一个使用 FallbackImageDirective 的例子:

<img
  src="img/does-not-exist.png"
  [fallbackImage]="'/default.png'"
/>

HostListener 装饰器用于监听宿主元素上的事件。在这种情况下,我们监听 error 事件并调用相应的方法。ElementRef 允许我们访问宿主元素,从而可以修改其属性,例如 src 属性。

Angular 管道

管道 是 Angular 中的一项强大功能,它允许你在模板中直接转换和格式化数据。它们提供了一种方便的方式来执行数据操作,如过滤、排序、格式化等。此外,它们轻量级且可重用,可以串联起来创建复杂的转换。

Angular 提供了一套内置管道,你可以直接使用。让我们看看使用这些管道的一些例子:

  • date 管道用于格式化日期。以下是一个例子:

    <p>Today is {{ today | date: 'longDate' }}</p>
    

    在这里,today变量代表当前日期。date管道使用'longDate'格式来格式化日期,以长格式显示日期,例如"January 1, 2023"

  • currency管道用于格式化货币值。以下是一个示例:

    <p>The price is {{ price | currency: 'USD' }}</p>
    

    在这里,price变量代表货币值。currency管道使用'USD'货币代码将值格式化为货币,例如"$10.99"

  • uppercase管道用于将字符串转换为大写。以下是一个示例:

    <p>{{ greeting | uppercase }}</p>
    

除了内置的管道外,你还可以创建自己的自定义管道以执行自定义数据转换。首先,你可以使用ng命令创建一个独立管道:

ng g p reverse

注意

如果你正在使用低于 v17 版本的 Angular,在生成独立管道时请确保附加--standalone

然后我们可以添加逻辑来创建一个名为ReversePipe的自定义管道,该管道反转字符串:

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
  standalone: true,
  name: 'reverse'
})
export class ReversePipe implements PipeTransform {
  transform(value: string): string {
    return value.split('').reverse().join('');
  }
}

在这个示例中,使用@Pipe装饰器创建了一个名为ReversePipe的自定义 Angular 管道。该管道接收一个字符串,将其反转,并返回反转后的字符串。实现反转操作的transform方法是PipeTransform接口所必需的。

然后,如果我们想使用这个自定义管道,我们只需要将其导入组件并像平常一样使用:

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, ReversePipe],
  template: `
    <p> Revere of 'abc' is {{'abc' | reverse}} </p>
  `,
})

在这一行代码中,Angular 的管道机制正在使用名为reverse的自定义管道来转换字符串abc。行Reverse of 'abc' is {{'abc' | reverse}}将在 HTML 中渲染为Reverse of 'abc' is cba

注意

管道是可链式的,因此你可以组合不同的管道以创建所需的结果。例如,{{'abc' | reverse | uppercase}}将反转字符串并将其转换为大写。最终结果是CBA

Angular 信号

Angular 信号是一个系统,它细粒度地跟踪你的状态在应用中的使用方式和位置,允许框架优化渲染更新。Angular 16 中的这个特性是处理 Angular 中反应性的原生方式。

在大多数情况下,信号就是构建简单应用所需的所有东西。信号是一个包含值并允许你监听该值变化的函数。让我们看看以下示例:

import { Component, effect, signal, WritableSignal } from '@angular/core'
@Component({
  standalone: true,
  selector: 'my-app',
  template: `
    <p>Current count is: {{count()}}</p>
    <button (click)="setRandomCount()">Set random count</button>
  `,
})
export class App {
  public count: WritableSignal<number> = signal<number>(4);
  constructor() {
    effect(() => {
      console.log(`The current count is: ${this.count()}`);
    });
  }
  setRandomCount() {
    this.count.set(Math.floor(Math.random() * 10 + 1));
  }
}

这个代码块展示了如何使用来自@angular/core包的信号和效果来管理状态和副作用。以下是分解:

  • public count: WritableSignal<number> = signal<number>(4): 这一行声明了一个名为count的信号,它是一个包含数字的WritableSignal实例。count的初始值设置为4

  • setRandomCount() { ... }:这是一个将count设置为 1 到 10 之间的随机数的函数。使用this.count.set(...)方法来更新count的值。

  • effect()是一个操作,每当一个或多个信号值发生变化时就会运行,并将count的当前值打印到浏览器控制台日志中。

  • 组件的模板显示 count 的当前值,以及一个当点击时调用 setRandomCount 的按钮。

注意

Angular Signals 自 Angular 17 起被标记为稳定。你可以在 angular.io/guide/signals 上了解更多关于 Angular Signals 的信息。

Angular 控制流

为了提升开发者体验DX),Angular 17 引入了一个名为内置控制流的新功能。这个功能允许你在 Angular 模板中使用类似 JavaScript 的语法,轻松地显示、隐藏或重复元素。

NgIf 指令为例。假设我们有一个产品列表,我们想在没有产品可用时显示不同的内容。在控制流功能引入之前,我们会这样处理:

<div *ngIf="products.length; else noProducts">
  Show product list
</div>
<ng-template #noProducts>
  Products are empty!
</ng-template>

总体来说,这个代码片段在 products 数组有元素时,在 <div> 元素内显示“显示产品列表”消息。如果 products 数组为空,则显示来自 ng-template 块的“产品为空!”消息。这允许根据 products 数组的当前状态进行条件渲染。

现在,让我们看看如何使用新的 @if 控制流来完成它:

@if (products.length) {
  Show Product List
} @else {
  Products are empty!
}

在这个代码片段中,你会注意到我们可以直接使用 @if@else,甚至 @else if 语法,从而产生更干净、更熟悉的代码。这种方法也展示了类似 JavaScript 的编码风格,增强了 DX 的熟悉感和便捷性。

注意

除了增强 DX 之外,新的控制流与之前的实现相比,还显著提高了应用程序的性能。你可以参考在 krausest.github.io/js-framework-benchmark/current.html 可用的社区框架基准,以进一步验证。

Angular 团队还提供了一个很好的方法来运行迁移,将现有语法移动到新的控制流。你只需要运行以下命令:

ng generate @angular/core:control-flow

此命令将扫描现有的 NgIfNgForNgSwitch 的实现,并将它们升级到新的控制流。在最终确定并提交这些更改之前,彻底测试和验证所有功能按预期工作非常重要。

注意

Angular 控制流在 Angular 17 中仍处于开发者预览阶段。你可以在 angular.io/guide/control_flow 上了解更多关于 Angular 控制流的信息。

到目前为止,我们已经探讨了 Angular 的几个核心功能,这些功能是开发功能强大的 Angular 应用程序的基本构建块。在下一节中,我们将深入探讨有效组织 Angular 项目的宝贵技巧。

组织 Angular 项目

组织和构建 Angular 应用程序的目的是提高其可维护性、可扩展性和可重用性。这涉及到如何结构化代码库、在不同文件和文件夹之间分配责任,以及建立命名和组织文件的约定。有效地组织 Angular 项目不仅改善了开发者的体验,还有助于团队更好地协作,并降低了新加入项目的开发者学习曲线。

在组织 Angular 项目时,遵循既定的最佳实践并利用 Angular 推荐的项目结构非常重要。Angular 风格指南提供了组织 Angular 项目的几个最佳实践。您可以在以下位置找到风格指南:angular.io/guide/styleguide

风格指南中强调的一个关键原则是LIFT 方法,它代表快速定位代码、一眼识别代码、简化结构,以及尝试做到DRY(不要重复自己)。让我们逐一介绍 LIFT 原则的各个方面,并提供代码示例以更好地理解:

  • 快速定位代码:目标是按照一种方式组织代码库,使得你可以轻松地定位文件和模块。一种常见的方法是根据其功能或特性对文件进行分组。以下是一个例子:

    app/
      components/
        product/
          product.component.ts
          product.component.html
          product.component.scss
          product.component.spec.ts
      services/
        product.service.ts
      models/
        product.model.ts
    

    在这里,相关的文件被分组在componentsservicesmodels等目录中。这种结构有助于开发者在开发特定功能时快速找到所需的文件。

  • 一眼识别代码:重点是使用有意义的和描述性的名称来命名文件、类、变量和函数。这使得理解代码的目的和功能更加容易。以下是一个例子:

    // product.component.ts
    export class ProductComponent {
      // Component logic...
    }
    

    使用如ProductComponent这样的描述性名称可以清楚地传达代码的责任和目的。

  • 简化结构:目标是保持扁平的目录结构,而不是过度嵌套目录。这简化了导航和维护。以下是一个例子:

    app/
      components/
        product/
          product.component.ts
    

    在这里,product.component.ts文件直接位于components目录下,没有不必要的嵌套。

  • 尝试做到 DRY:这个原则鼓励代码重用和避免重复。它提倡将公共功能提取到可重用的组件、服务或模块中。以下是一个例子:

    // shared/ui/loading-spinner.component.ts
    @Component({
      selector: 'app-loading-spinner',
      template: `
        <div class="loading-spinner">
          <!-- Loading spinner template -->
        </div>
      `,
    })
    export class LoadingSpinnerComponent {
      // Component logic...
    }
    

    LoadingSpinnerComponent可以在应用程序的多个部分中重复使用以显示加载指示器,减少代码重复,并提高一致性。

通过遵循 LIFT 原则,Angular 项目可以从一个组织良好且易于维护的代码库中受益,使开发更加高效,并增强团队成员之间的协作。

除了 LIFT 方法之外,风格指南还提供了以下值得注意的最佳实践:

  • src/app 用于特定于应用程序的代码,以及 src/assets 用于资源。

  • 使用目录分离关注点:使用目录来分离关注点并分组相关文件。例如,将一个功能的相关组件、模板、样式和测试放在同一个目录中。这使得定位和维护相关代码变得更加容易。

  • feature-name.component.ts 用于组件文件,feature-name.service.ts 用于服务文件,以及 feature-name.spec.ts 用于测试文件。这种一致性有助于开发者快速定位和识别文件。

  • 在目录中使用 index.ts) 提供一个中央入口点以导出多个文件。Barrels 简化了导入,并使定位和管理相关文件变得更加容易。

  • 将功能级别的代码放置在功能模块中。

  • 考虑创建共享目录:创建一个共享目录来封装常用组件、指令、管道和服务。共享模块简化了共享资源的导入,并促进了代码的重用。

  • 使用懒加载:对于大型应用程序,考虑使用 懒加载 来按需加载功能模块。这提高了初始加载时间,并将代码分离成可管理的块。

记住,Angular 风格指南为每个最佳实践提供了详细的解释和示例,并且是有效组织 Angular 项目的宝贵资源。

注意

这些只是组织 Angular 项目时的最佳实践建议。在架构你的 Angular 项目方面,没有一种“一刀切”的方法。它将始终取决于其他因素,如项目的阶段、截止日期、团队规模等。确保你选择适合你项目的方案。一种推荐策略是从 LIFT 方法开始,这有助于加快应用程序的开发。随着应用程序的扩展,建议应用最佳实践来优化代码并确保其效率。

摘要

在本章中,我们涵盖了现代 Angular 开发的关键方面。我们首先介绍了 Angular 的发展历程,强调了其增长和进步。接着,我们讨论了最近 Angular 版本中的核心特性和改进,包括数据绑定、组件、服务、指令、管道和信号。我们还强调了有效组织 Angular 项目的重要性,并强调了使用最佳实践的重要性。

通过深入了解现代 Angular 开发,你现在已经准备好将你的技能提升到下一个层次。在下一章中,我们将专注于将 PrimeNG 集成到你的 Angular 项目中。我们将指导你通过将 PrimeNG 集成到你的 Angular 应用程序中,并利用其功能来增强用户体验的过程。

第四章:将 PrimeNG 集成到您的 Angular 项目中

在本章中,我们将探讨将 PrimeNG(一个流行的 UI 组件库)集成到您的 Angular 项目中的过程。PrimeNG 提供了一套丰富的预构建组件,可以增强您应用程序的功能和美观。无论您需要整合复杂的数据表、响应式布局还是交互式图表,PrimeNG 都提供了一系列组件以满足您的需求。通过使用 PrimeNG,它将帮助您以稳健、可靠和可访问的方式节省构建基础组件的时间,从而使您能够专注于应用程序而不是构建典型 UI 元素的所有核心组件。

通过遵循本章提供的逐步说明和示例,您将获得将 PrimeNG 无缝集成到您的 Angular 项目中以及轻松创建功能丰富且视觉吸引力的应用程序所需的知识和技能。

本章将涵盖以下主题:

  • 将 PrimeNG 组件添加到您的 Angular 项目中

  • 配置 PrimeNG 模块和依赖项

  • 使用 PrimeNG 组件 API 和指令

  • 自定义组件样式和主题

  • 解决常见的集成问题

技术要求

本章包含各种示例代码,展示如何将 PrimeNG 集成到 Angular 项目中。您可以在以下 GitHub 仓库的chapter-04文件夹中找到相关源代码:github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-04

将 PrimeNG 组件添加到您的 Angular 项目中

PrimeNG 是一个强大的 Angular UI 组件库,它提供了一系列预构建组件,可以增强您应用程序的功能和视觉吸引力。在本节中,我们将探讨如何将 PrimeNG 及其依赖项添加到您的 Angular 项目中,导入必要的样式,并在模板中使用 PrimeNG 组件。

安装 PrimeNG

在开始将 PrimeNG 集成到您的 Angular 项目之前,请确保您从一个全新的项目开始。现在按照以下步骤在您的 Angular 项目中安装 PrimeNG:

  1. 在您的项目目录中打开一个终端或命令提示符。

  2. 运行以下命令以安装 PrimeNG 并将其作为依赖项保存到您的项目中:

    npm install primeng
    

安装过程完成后,您将在根目录的package.json文件中看到primeng

注意

在撰写本书时,我们正在使用 PrimeNG 版本17.0.0。如果您有不同的版本并且您的应用程序无法正常工作,您可以尝试使用yarn add primeng@17.0.0来安装本书插件的正确版本。

您只需要primeng包就可以开始集成。在下一节中,我们将向您的应用程序添加一些样式和主题。

将 PrimeNG 样式导入到您的 Angular 应用程序中

主题和核心样式是组件的必要 CSS 文件。您可以在angular.jsonsrc/styles.css文件中找到可用的主题的全面选择。在本节中,我们将以lara-light-blue主题为例。

为了将主题和样式添加到您的angular.json文件中,请转到文件的styles部分并添加样式,如下所示:

// angular.json
"options": {
   ...
   "styles": [
      "apps/chapter-04/src/styles.scss",
      "node_modules/primeng/resources/themes/lara-light-blue/theme.css",
      "node_modules/primeng/resources/primeng.min.css"
   ],
},

除了在angular.json文件中添加主题和样式外,您还可以利用styles.scss导入样式:

// styles.scss
/* You can add global styles to this file, and also import other style files */
@import 'primeng/resources/themes/lara-light-blue/theme.css';
@import 'primeng/resources/primeng.css';

此外,每个主题都有自己的字体家族;建议为您的应用应用一个字体家族以实现统一的风格:

// styles.scss
body {
      font-family: var(--font-family);
}

注意

--font-family是一个 CSS 变量,也称为 CSS 自定义属性,它们是占位符,可以持有值并在整个 CSS 样式表中使用。您可以在developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties上了解更多信息。

例如,对于lara-light-blue主题,默认字体家族在theme.css文件中定义:

// node_modules/primeng/resources/themes/lara-light-blue/theme.css
:root {
   ...
   --font-family:-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
   ...
}

注意

如果您正在使用 Nx Workspace,则使用project.json而不是angular.json

与 PrimeNG 图标一起工作

图标在增强 Web 应用的视觉吸引力和可用性方面发挥着至关重要的作用,提供直观的视觉提示,并帮助用户快速识别和交互各种元素。在本节中,我们将探讨如何在 PrimeNG 中处理图标并利用它提供的庞大图标库。

要使用 PrimeIcons,您需要安装primeicons包:

npm install primeicons

之后,需要在应用的styles.scss文件中导入图标库的 CSS 文件:

// style.scss
@import "primeicons/primeicons.css";

注意

要获取 PrimeIcons 的完整列表,请查看此网站:primeng.org/icons

PrimeIcons 提供了特殊的语法:pi pi-{icons},可以在您的 Angular 组件中使用。您可以将{icons}替换为提供的链接中的图标名称,例如pi pi-user,以表示它代表一个用户图标。如果您想将 Prime Icons 作为一个独立元素使用,可以使用ispan元素,如下所示:

<i class="pi pi-user"></i>
<span class="pi pi-user"></span>

在此示例中,我们使用pi-user图标。您可以用支持的图标库中的任何其他图标名称替换它。

添加 PrimeNG 方法

向模板中添加 PrimeNG 有两种方法:PrimeNG 组件和 PrimeNG 指令。为了说明这一点,让我们使用 PrimeNG 的p-button元素。

p-button组件是一个自包含的 UI 元素,可以在您的整个应用中进行自定义和重用。它具有内置功能,如事件处理,以及各种自定义选项,包括图标支持、标签和样式类。以下是如何使用p-button组件的示例:

<p-button label="Click me" />

相比之下,pButton 指令用于向现有的 HTML 按钮元素添加行为。它允许你通过添加 PrimeNG 特定的功能(如样式和事件处理)来增强按钮元素的功能。以下是如何使用 pButton 指令的示例:

<button pButton type="button" label="Click me"></button>

在这个例子中,我们使用 pButton 指令通过 PrimeNG 特定的功能来增强现有的 HTML 按钮元素。我们将 type 属性设置为 "button" 并向按钮添加一个 label

当你需要一个可以自定义和在整个应用程序中重用的自包含 UI 元素,并且需要向按钮添加大量内置功能时,建议使用 p-button 组件。对于 pButton 指令,你可以用它来向现有的 HTML 按钮元素(如 buttoninputtag)添加行为,并在需要使用 PrimeNG 特定的功能来增强按钮功能时使用它。

在模板中使用 PrimeNG 组件

这一节重点介绍如何无缝地将 PrimeNG 组件集成到模板中。我们将学习如何在你的 Web 应用程序中有效地利用 PrimeNG 组件的丰富库。让我们开始吧。

如果你正在使用 NgModule 方法,你所需要做的就是将 PrimeNG 模块导入到 NgModule 装饰器中:

import { NgModule } from '@angular/core'
import { ButtonModule } from 'primeng/button'
@NgModule({
   imports: [
      // Other module imports
      ButtonModule
   ],
   // Other module configurations
})
export class AppModule { }

之前的代码显示我们将 ButtonModule 导入到 AppModule 中。

一旦导入 PrimeNG 模块,你就可以开始在 Angular 模板中使用 PrimeNG 组件。按照以下步骤来使用 PrimeNG 组件:

  1. 打开你想要使用 PrimeNG 组件的组件模板文件(.html)。

  2. 将 PrimeNG 组件选择器放置在模板中你想组件出现的位置。例如,要添加按钮组件,请使用以下代码:

    <p-button label="Click me" />
    

    对于独立组件方法,你将需要将 PrimeNG 组件导入到你正在工作的 Angular 组件中:

    import { Component } from '@angular/core'
    import { ButtonModule } from 'primeng/button'
    @Component({
       standalone: true,
       imports: [ButtonModule],
       selector: 'primengbook-root',
       template: `
          <h1>Welcome to chapter-04</h1>
          <p-button label="Click me" />
       `
    })
    export class AppComponent {}
    AppComponent, which utilizes ButtonModule from PrimeNG. p-button is an indicator that it’s a PrimeNG button component.
    

因此,你将在浏览器上得到一个样式化的按钮:

图 4.1 – PrimeNg 按钮组件

图 4.1 – PrimeNg 按钮组件

使用 PrimeNG 组件 API 和指令

作为开发者,你明白利用强大且可定制的库来简化开发过程的重要性。在这里,我们将了解如何使用 PrimeNG API 和指令以编程方式与组件交互,自定义其行为,并发挥其全部潜力。

每个 PrimeNG 组件都将有其自己的 API 和配置。以 PrimeNG 按钮组件为例:

 <p-button
   label="Click me"
   icon="pi pi-check"
   iconPos="right"
   [disabled]="isDisabled"
/>

在这个例子中,我们使用 label 输入来设置按钮标签,使用 icon 输入来设置按钮上显示的图标,使用 iconPos 输入来设置图标位置在标签的右侧,以及使用 disabled 输入根据 isDisabled 属性的值来禁用按钮。

你可以在以下屏幕截图中看到结果:

图 4.2 – PrimeNG 按钮配置

图 4.2 – PrimeNG 按钮配置

这里有一些你可以与p-button一起使用的其他配置选项:

  • styleClass: 设置按钮的 CSS 类

  • type: 设置按钮的类型(例如:"button"、"submit"、"reset")

  • tabIndex: 设置按钮的 tab 索引

  • loading: 表示按钮处于加载状态

  • loadingIcon: 设置加载状态时显示的图标

如果你想在点击按钮后触发事件,你可以利用click事件发射器来从你的组件中触发一个函数:

<button
   pButton
   label="Click me!"
   [loading]="loading"
   (click)="onClickEvent()"
></button>
...
onClickEvent() {
   this.loading = true
   setTimeout(() => {
      this.loading = false
   }, 2000)
}

以下代码展示了带有点击事件处理器的 PrimeNG 按钮:

  • (click)="onClickEvent()": 当用户点击按钮时,将在组件上触发onClickEvent()函数

  • [loading]="loading": 在点击后显示按钮的加载状态

让我们看看结果:

图 4.3 – 带点击事件的 PrimeNG 按钮

图 4.3 – 带点击事件的 PrimeNG 按钮

在按钮被点击后,加载状态被激活,并在按钮的左侧显示加载图标。

通过遵循这些步骤,你可以轻松地将 PrimeNG 组件添加到你的 Angular 项目中。在下一节中,我们将深入了解一些 PrimeNG 配置。

配置 PrimeNG 模块和依赖项

PrimeNG 提供了一套全面的配置选项,可以应用于其组件。这些选项使你能够控制组件功能的不同方面和视觉呈现。让我们看看一些常用的配置选项。

全局配置

PrimeNG 提供了一个全局配置对象,允许你为应用程序中的所有组件定义默认设置。例如,你可以使用PrimeNGConfig对象配置默认区域设置、动画持续时间和其他全局选项。提供的代码片段显示了在引导过程中 PrimeNG 的初始化和配置。

首先,让我们为 PrimeNG 创建全局配置文件:

// shared/providers/primeng.provider.ts
import { APP_INITIALIZER } from '@angular/core'
import { PrimeNGConfig } from 'primeng/api'
const factory = (primengConfig: PrimeNGConfig) => {
   return () => {
      primengConfig.ripple = true
      primengConfig.zIndex = {
         modal: 1100,      // dialog, sidebar
         overlay: 1000,   // dropdown, overlay panel
         menu: 1000,       // overlay menus
         tooltip: 1100,   // tooltip
      }
      // more configuration options
   }
}
export const primeNgProvider = {
   provide: APP_INITIALIZER,
   useFactory: factory,
   deps: [PrimeNGConfig],
   multi: true,
}

此代码定义了primeNgProvider,这是一个初始化和配置 PrimeNG 的提供者。它使用@angular/core中的APP_INITIALIZER令牌来确保配置在应用程序启动前应用。

备注

APP_INITIALIZER是 Angular 的一个功能,允许你在应用程序启动前运行一些初始化任务。通过向应用程序配置中的providers数组提供函数或服务到APP_INITIALIZER令牌,Angular 确保这些任务在应用程序完全加载前执行并完成。你可以了解更多信息在angular.io/api/core/APP_INITIALIZER

factory 函数是提供者的实现。它接收 PrimeNGConfig 对象作为依赖项,并返回一个函数。当此函数在应用程序初始化期间执行时,它配置 PrimeNG 的各个方面。在这个例子中,它启用了涟漪效果(ripple: true),这是一种在按钮交互时发生的视觉动画或图形效果,并设置了不同 PrimeNG 组件的 z-index 值。

之后,我们可以将 PrimeNg 全局配置添加到应用程序配置文件中:

// app.config.ts
import { ApplicationConfig } from '@angular/core'
import { primeNgProvider } from './shared/providers'
export const appConfig: ApplicationConfig = {
   providers: [primeNgProvider],
}

此代码是 Angular 应用程序引导过程的一部分。它提供 primeNgProvider 作为提供者,负责初始化和配置 PrimeNg。

通过在应用程序的引导过程中提供 primeNgProvider 作为提供者,它确保在应用程序启动之前正确初始化和配置了 PrimeNg,从而允许在应用程序的整个过程中无缝使用 PrimeNg 的组件和功能。

Angular 动画配置

动画在创建引人入胜和交互式用户界面中起着至关重要的作用。在 Angular 中,动画可以用来使组件生动起来,并提供平滑且视觉上吸引人的用户体验。在本节中,我们将探讨如何在您的 Angular 应用程序中集成和配置动画。

要在您的 Angular 应用程序中启用动画,您需要从 @angular/platform-browser/animations 包中导入 provideAnimations 函数。此函数提供了在 Angular 中处理动画所需的功能和工具。它利用浏览器底层的动画能力来提供无缝的动画效果。

要导入 provideAnimations,您需要将其包含在应用程序配置的 providers 数组中:

// app.config.ts
import { ApplicationConfig } from '@angular/core'
import { provideAnimations } from '@angular/platform-browser/animations'
export const appConfig: ApplicationConfig = {
   providers: [
      ...
      provideAnimations()
   ],
}

在某些场景下,您可能希望全局禁用应用程序中的动画。这可能是为了优化性能或适应特定的用户偏好。Angular 提供了 provideNoopAnimations 函数作为 provideAnimations 的替代方案来禁用动画:

// app.config.ts
import { ApplicationConfig } from '@angular/core'
import { provideNoopAnimations } from '@angular/platform-browser/animations'
export const appConfig: ApplicationConfig = {
   providers: [
      ...
      provideNoopAnimations()
   ],
}

provideNoopAnimations 函数实际上用无操作实现替换了动画功能。这导致在整个应用程序中禁用动画。

在下一节中,我们将进一步探讨如何自定义样式和主题。

自定义组件样式和主题

PrimeNg 提供了一系列具有默认样式的组件,这些组件旨在功能性和视觉吸引力。然而,为了与您应用程序的设计实现无缝集成,您可能需要自定义 PrimeNg 组件的外观。在本节中,我们将探讨在 PrimeNG 中自定义组件样式和主题的各种技术,以实现所需的视觉效果和感觉。

在组件级别覆盖样式

假设你想要改变 Button 组件的背景颜色和边框半径。以下是一个使用 CSS 覆盖 Button 组件默认样式的例子:

import { CommonModule } from '@angular/common'
import { Component } from '@angular/core'
import { ButtonModule } from 'primeng/button'
@Component({
   selector: 'primengbook-button-override-styles',
   standalone: true,
   imports: [CommonModule, ButtonModule],
   template: `
      <h2>Button Override Styles</h2>
      <button pButton type="button" label="Custom styles"></button>
   `,
   styles:
      `
         .p-button {
            background-color: #696cff;
            border-radius: 5px;
         }
      `,
})
export class ButtonOverrideStylesComponent {}

在这个例子中,我们针对的是 .p-button 类,这是应用到所有 Button 组件上的类。我们使用 background-colorborder-radius 属性来改变 Button 组件的外观。让我们看看结果:

图 4.4 – 组件自定义按钮样式

图 4.4 – 组件自定义按钮样式

如果你检查浏览器,你会看到样式正在正确地应用:

图 4.5 – 组件自定义按钮样式检查

图 4.5 – 组件自定义按钮样式检查

你可以从检查中看到按钮的背景颜色是 #696CFF,这是我们设置在样式部分的颜色代码。

全局覆盖样式

在上一节中,我们学习了如何在组件级别添加自定义样式。在本部分,我们将学习如何使用 CSS 预处理器全局应用样式。在我们开始之前,让我们快速了解一下 CSS 预处理器是什么。

CSS 预处理器是扩展默认 层叠样式表CSS)功能的脚本语言。它们允许开发者使用变量、嵌套规则、混入、函数等,这使得 CSS 更易于维护、主题化和扩展:

  • 变量:这些允许你在整个样式表中存储想要重用的值。例如,你可以定义一个用于你的主要颜色的变量,并在多个地方使用它:

     $primary-color: #5d738a;
    .p-button {
       background-color: $primary-color;
    }
    

    这种样式将把所有新的背景颜色应用到所有 PrimeNG 按钮上。

  • 嵌套规则:这些允许你在其他选择器内部嵌套 CSS 选择器,使你的 CSS 更易于阅读和维护:

    nav {
       ul {
          margin: 0;
          padding: 0;
          list-style: none;
       }
       li {
          display: inline;
          margin-right: 5px;
       }
    }
    
  • 混入和函数:这些允许你定义可重用的 CSS 块。它们可以接受参数,允许你自定义输出:

    @mixin box-shadow($x, $y, $blur, $color) {
          -webkit-box-shadow: $x $y $blur $color;
              -moz-box-shadow: $x $y $blur $color;
                      box-shadow: $x $y $blur $color;
    }
    button {
          @include box-shadow(0, 0, 5px, #ccc);
    }
    

    在混入内部有三个声明,每个声明代表 box-shadow 属性的一个供应商前缀版本。供应商前缀是 -webkit- 用于基于 WebKit 的浏览器(例如 Chrome 和 Safari),以及 -moz- 用于基于 Mozilla 的浏览器(例如 Firefox)。最后的声明是标准的 box-shadow 属性。

    无论你想要在你的元素上应用多少阴影,你只需包含 box-shadow 混入即可。它将为你生成以下代码:

    button {
          -webkit-box-shadow: 0 0 5px #ccc;
              -moz-box-shadow: 0 0 5px #ccc;
                      box-shadow: 0 0 5px #ccc;
    }
    

注意

使用 CSS 预处理器可以极大地提高你的工作效率,尤其是在大型项目中。然而,有一个学习曲线,你必须设置你的开发环境来编译代码。

现在,让我们看看如何应用全局样式的例子:

// styles.scss
@import 'primeng/resources/themes/lara-light-blue/theme.css';
@import 'primeng/resources/primeng.css';
@import 'primeicons/primeicons.css';
:root {
   --font-family: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
}
// Define a variable for primary color
$primary-color: #5d738a;
$text-color: #f8f4ef;
// Define box shadow
@mixin box-shadow($x, $y, $blur, $color) {
      -webkit-box-shadow: $x $y $blur $color;
          -moz-box-shadow: $x $y $blur $color;
                  box-shadow: $x $y $blur $color;
}
.p-button {
   background-color: $primary-color;
   color: $text-color;
   @include box-shadow(0, 0, 5px, #ccc);
}

在这个例子中,我们使用一个 CSS 变量来覆盖 lara-light-blue/theme.css 中的 font-family。之后,我们添加一个 Sass 变量来定义主要颜色和文字颜色值。然后我们通过混入添加 box-shadow 属性。

我们正在使用这些变量来全局样式化Button组件。让我们检查一下更改:

图 4.6 – 全局自定义按钮样式

图 4.6 – 全局自定义按钮样式

在调试浏览器后,你可以看到,除了组件级别的修改样式按钮外,你应用中的所有其他按钮都全局更新了新的样式。

注意

当自定义组件样式或应用主题时,务必彻底测试你的更改,以确保它们不会对你的应用的功能或可用性产生不利影响。此外,请注意,某些组件可能具有特定的 CSS 类或样式,你需要针对这些类或样式来定制其外观。

自定义 PrimeNG 组件的外观是创建一个视觉上吸引人且统一的 Web 应用的重要部分。无论你是在使用 CSS、CSS 预处理器还是 PrimeNG 内置的主题,都有一系列的工具和定制选项可供你使用,以帮助你实现设计目标。通过花时间定制你应用的风格和主题,你可以创建一个既实用又美观的应用。

在下一节中,我们将探讨在使用 PrimeNG 组件时可能出现的常见集成问题,并提供故障排除技巧以克服这些问题。

故障排除常见集成问题

将 PrimeNG 集成到你的 Angular 项目中有时会带来它自己的一套挑战。在本节中,我们将探讨 PrimeNG 集成过程中可能出现的常见问题,并提供故障排除技巧以帮助你克服这些问题。通过了解这些挑战并具备必要的调试技能,你可以确保集成过程的顺利进行。

冲突或兼容性问题

集成 PrimeNG 时,一个常见问题是遇到与其他库或 Angular 版本冲突或兼容性问题。确保所有依赖项,包括 Angular 和 PrimeNG,彼此兼容是至关重要的。例如,如果你正在使用 Angular 15.x 并且安装了 PrimeNG 16.x,集成 PrimeNG 组件时可能会出现冲突。

注意

以下是从 PrimeNG 官方网站获取的发布周期信息:“PrimeNG 的发布周期与 Angular 保持一致,每 6 个月发布一个新的大版本 PrimeNG,作为开源版本,与最新的 Angular 核心兼容”。你可以在primeng.org/lts查看当前的长期支持LTS)详细信息。

如果出现冲突,你可能需要调查具体的冲突版本,并找到解决方案或相应地更新版本。你应该注意检查package.json文件以确认依赖项的版本:

// package.json
...
"dependencies": {
      "@angular/animations": "17.0.6",
      "@angular/cdk": "17.0.3",
      "@angular/common": "17.0.6",
      "@angular/compiler": "17.0.6",
      "@angular/core": "17.0.6",
      "@angular/forms": "17.0.6",
      "@angular/platform-browser": "17.0.6",
      "@angular/platform-browser-dynamic": "17.0.6",
      "@angular/router": "17.0.6",
      "install": "⁰.13.0",
      "primeflex": "³.3.1",
      "primeicons": "⁶.0.1",
      "primeng": "17.0.0",
      "rxjs": "~7.8.0",
      "tslib": "².3.0",
      "zone.js": "0.14.2"
},
...

如您所见,primengangular 使用相同的版本 17.x。版本不同并不意味着您的 Angular 应用程序中会有冲突。如果您确实遇到错误,请从官方的 Angular 和 PrimeNG 网站检查版本:github.com/angular/angular/releasesgithub.com/primefaces/primeng/releases

在您检查了想要安装的 Angular 或 PrimeNG 的发布版本之后,使用 npm 来更新它:

npm install primeng@17.0.0

此命令将更新 primeng 到版本 17.0.0

缺少或错误的导入

另一个常见的问题是缺少或错误导入 PrimeNG 模块或组件。当在您的模板或代码中使用 PrimeNG 组件时,正确导入必要的模块至关重要。如果您忘记导入所需的模块,组件可能无法按预期工作或抛出错误。请仔细检查您的导入,并确保所有必需的 PrimeNG 模块都已导入到您的 Angular 应用程序中。

让我们看看以下组件:

@Component({
   selector: 'primengbook-button-override-styles',
   standalone: true,
   imports: [CommonModule],
   template: `
      <h2>Button Override Styles</h2>
      <button pButton type="button" label="Custom styles"></button>
   `
})
export class ButtonOverrideStylesComponent {}

在前面的代码中,您可以看到我们正在使用 pButton 指令方法,并且 VS Code 或编译器没有错误。然而,按钮将不会正确显示,因为您还没有将 ButtonModuleprimeng 导入到 imports 数组中。以下是修复此问题的方法:

import { ButtonModule } from 'primeng/button'
...
@Component({
   selector: 'primengbook-button-override-styles',
   standalone: true,
   imports: [CommonModule, ButtonModule dependency is correctly added to the imports array in the component.
			Incorrect configuration or setup
			Sometimes, issues arise due to incorrect configuration or setup of PrimeNG features. For example, if you’re using PrimeNG’s animation features, ensure that you have added the `provideAnimations` or `provideNoopAnimations` function as required.
			Additionally, verify that any necessary configuration options are set correctly.
			Do refer to the PrimeNG documentation for detailed instructions on setting up and configuring specific features: [`primeng.org/installation`](https://primeng.org/installation)
			Inspecting console errors and warnings
			When facing integration issues, the browser’s developer console is an invaluable tool for debugging. It provides error messages, warnings, and additional information that can help you identify the root cause of the problem. Inspect the console for any error messages related to PrimeNG components or modules. These error messages often provide valuable insights into the issue at hand. Let’s have a look at the following error:
			![Figure 4.7 – Sample error in the console log](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_04_07.jpg)

			Figure 4.7 – Sample error in the console log
			Based on the error displayed in the console, the issue appears to be on line 14 in the `app.component.ts` file. The error message indicates that `p-button` is not an Angular component and suggests that you add it to the `imports` array or include `CUSTOM_ELEMENTS_SCHEMA` in the `schemas` array. To resolve this issue, you should add the PrimeNG `ButtonModule` dependency to the component’s `imports` array.
			Using the Angular CLI
			The Angular CLI offers various helpful commands that can aid in troubleshooting. This includes using the `ng build` command to check for build errors or the `ng serve` command to run your application and observe any runtime issues.
			The CLI also provides options for generating component and module schematics, which can assist in setting up PrimeNG components correctly:

错误:apps/chapter-04/src/app/components/button-configuration.component.ts:11:5 - 错误 NG8001: 'p-button' 不是一个已知的元素:

  1. 如果 'p-button' 是一个 Angular 组件,那么请验证它是否包含在这个组件的 '@Component.imports' 中。

  2. 如果 'p-button' 是一个 Web 组件,那么请将 'CUSTOM_ELEMENTS_SCHEMA' 添加到这个组件的 '@Component.schemas' 中以抑制此消息。

11       <p-button


12          label="点击我!"

...

15          [disabled]="isDisabled"


16       />

~~~~~~

```js

			The previous output from the Angular CLI indicates that we forgot to import `ButtonModule` before using `p-button`.
			Seeking help from the community
			In case you encounter an issue that seems difficult to resolve, don’t hesitate to seek help from the developer community. Online forums, discussion boards, and social media groups dedicated to Angular and PrimeNG are excellent resources for getting assistance. Many experienced developers are willing to share their insights and provide guidance on troubleshooting specific integration issues.
			You can find the dedicated PrimeNG discussions at [`github.com/orgs/primefaces/discussions`](https://github.com/orgs/primefaces/discussions).
			Summary
			In this chapter, we explored the process of integrating PrimeNG into an Angular project. We learned how to add PrimeNG components to our application, configure PrimeNG modules and dependencies, work with PrimeNG component APIs and directives, customize component styles and themes, and troubleshoot common integration issues. By successfully integrating PrimeNG, we can leverage its rich set of UI components and features to enhance our Angular applications.
			Through the chapter, we gained valuable knowledge and skills that are essential for professional developers. Integrating PrimeNG into an Angular project opens up a world of possibilities for creating feature-rich and visually appealing web applications. By harnessing the power of PrimeNG, we can save development time and effort by utilizing pre-built, customizable components, and tapping into advanced functionalities.
			In the next chapter, we will delve into the realm of input components and form controls provided by PrimeNG. We will explore how to work with text inputs, checkboxes, radio buttons, dropdowns, and more, enabling us to create interactive and user-friendly forms. Additionally, we will dive into form validation techniques and learn how to handle user input effectively.

```

# 第二部分:UI 组件和功能

在这部分,您将深入 PrimeNG UI 组件的世界,并探索它们提供的丰富功能集。您将学习如何利用这些组件为您的 Angular 应用程序构建动态和交互式的用户界面。

到这部分结束时,您将对 PrimeNG 的大部分 UI 组件有一个全面的理解,并能够有效地利用它们来增强您应用程序的功能和用户体验。

这部分包含以下章节:

+   *第五章*,*介绍输入组件和表单控件*

+   *第六章*,*使用表格、列表和卡片组件*

+   *第七章*,*使用树、树表和时序组件*

+   *第八章*,*使用导航和布局组件*




# 第五章:介绍输入组件和表单控件

深入 Angular 和 PrimeNG 的世界,我们即将开始一段关于输入组件和表单控件的旅程。

本章致力于提供对如何在 Angular 应用程序中有效利用各种输入组件和表单控件的综合理解。我们将探讨文本输入、复选框、单选按钮、下拉菜单等的使用。此外,我们还将深入了解表单验证和处理用户输入的复杂性。

通过利用这些输入组件和掌握表单控件,我们将能够创建直观且以用户为中心的应用程序,这在当今的数字景观中至关重要。

本章将涵盖以下主题:

+   介绍输入组件和表单控件

+   处理文本输入、复选框和单选按钮

+   使用下拉菜单、多选和日期选择器

+   实现表单验证

# 技术要求

本章包含各种关于输入组件和 Angular 表单的工作代码示例。您可以在以下 GitHub 仓库的 `chapter-05` 文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-05`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-05)。

# 介绍输入组件和表单控件

在进入本章的主要内容之前,让我们先通过输入组件和表单控件的概述来设定场景。Angular 提供两种方式来通过表单处理用户输入:模板驱动和响应式表单。这两种方法都有其独特的优势,选择哪一种取决于您应用程序的具体需求。

让我们看看一个简单的 Angular 表单:

![图 5.1 – Angular 示例表单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_01.jpg)

图 5.1 – Angular 示例表单

我们将使用这两种方法来重新创建此表单。

## 模板驱动表单

**模板驱动表单**直接在 DOM 中定义控件,然后将它们链接回底层模型。它们的简单性使其成为简单用例的首选。例如,当处理具有少量字段和简单验证规则的表单时,模板驱动表单提供了一种快速且轻松实现表单功能的方法。

使用模板驱动表单,以下是重新创建 *图 5**.1* 的方法:

```js
import { CommonModule } from '@angular/common'
import { Component } from '@angular/core'
import { FormsModule, NgForm } from '@angular/forms'
@Component({
   standalone: true,
   imports: [CommonModule, FormsModule],
   template: `
      <h2>Template Driven Form</h2>
      <form #form="ngForm" (ngSubmit)="onSubmit(form)" novalidate>
         <input name="first" ngModel required />
         <button>Submit</button>
      </form>
      <p>Value: {{ form.value | json }}</p>
   `
})
export default class TemplateDrivenComponent {
   onSubmit(form: NgForm) {
      console.log(form.value) // { first: '' }
   }
}
```

下面是代码中重要部分的分解:

+   `FormsModule`:当您想在 Angular 应用程序中使用模板驱动表单时,会导入此模块。它提供了如 `NgForm` 和 `NgModel` 等指令:

    +   `NgForm`:这是一个自动附加到任何 `<form>` 标签的指令。它跟踪表单的值及其有效性。在表单提交时,它通过其 `value` 属性聚合所有表单控件值。

    +   `NgModel`: 这个指令从一个域模型创建一个`FormControl`实例并将其绑定到表单控件元素。它确保 UI 和组件模型之间的实时同步。

+   `#form="ngForm"`: 这将创建一个名为`form`的局部模板变量,你可以使用它来访问`NgForm`指令实例。这允许你在模板的其他地方使用其属性和方法。

+   `onSubmit(form: NgForm)`: 这是一个在表单提交时被调用的方法。它将表单的值记录到控制台。

## 响应式表单

在**响应式表单**中,你可以在组件类中创建和管理表单控件对象。它们以其健壮性、可扩展性、可重用性和可测试性而闻名,使得它们成为复杂场景和大表单的首选选择。以下是使用响应式表单方法重新创建*图 5.1*的示例:

```js
import { CommonModule } from '@angular/common'
import { Component } from '@angular/core'
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'
@Component({
   standalone: true,
   imports: [CommonModule, ReactiveFormsModule],
   template: `
      <h2>Reactive Forms</h2>
      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
         <input type="text" formControlName="first" />
         <button type="submit">Submit</button>
      </form>
      <p>Value: {{ profileForm.value | json }}</p>
   `
})
export default class ReactiveFormsComponent {
   profileForm = new FormGroup({
      first: new FormControl(''),
   })
   onSubmit() {
      console.log(this.profileForm.value) // { first: '' }
   }
}
```

下面是对代码中重要部分的分解:

+   `ReactiveFormsModule`, `FormControl`, 和 `FormGroup`: 当你想要在 Angular 应用程序中使用响应式表单时,这些被使用。

+   `[formGroup]="profileForm"`: 这用于将组件类中定义的`FormGroup`实例绑定到 DOM 中的表单元素。

+   `formControlName`: 这个指令用于将输入元素链接到`第一个`表单控件。

+   `onSubmit()`: 这是一个在表单提交时被调用的方法。在示例中,提交函数获取`profileForm`的值并将表单值打印到浏览器控制台。

## 使用 PrimeNG 输入组件增强 Angular 表单

输入组件和表单控件是任何交互式应用程序的骨架。它们允许用户与应用程序交互,输入数据,并做出选择。没有这些元素,应用程序将是一个静态实体,无法以美观的方式与用户互动。

PrimeNG 介入以增强标准输入元素。它提供了一套丰富的组件,采用统一的方法来处理用户输入。PrimeNG 的组件旨在易于使用和集成到你的 Angular 应用程序中,同时提供高度的可定制性。

例如,让我们看看 PrimeNG 如何增强之前的响应式表单字段:

```js
import { ButtonModule } from 'primeng/button'
import { InputTextModule } from 'primeng/inputtext'
@Component({
   standalone: true,
   imports: [CommonModule, ReactiveFormsModule, ButtonModule, InputTextModule],
   template: `
      <h2>PrimeNg Reactive Forms</h2>
      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
         <input pInputText formControlName="first" />
         <button pButton type="submit">Submit</button>
      </form>
      <p>Value: {{ profileForm.value | json }}</p>
   `,
})
```

在此代码中,`pInputText`和`pButton`是 PrimeNG 指令,它们改进了标准输入字段和按钮,并添加了如主题、样式等附加功能。与*图 5.1*相比,你可以在*图 5.2*中看到新的表单:

![图 5.2 – 带有 PrimeNG 样式的 Angular 表单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_02.jpg)

图 5.2 – 带有 PrimeNG 样式的 Angular 表单

总之,理解输入组件和表单控件对于构建交互性和用户友好的应用程序至关重要。Angular 提供了处理用户输入的强大工具,而 PrimeNG 通过一组丰富的可自定义组件增强了这些工具。在接下来的章节中,我们将深入了解如何在 Angular 应用程序中使用这些组件和控制。

# 处理文本输入、复选框和单选按钮

随着我们深入 Angular 和 PrimeNG 的实际应用方面,我们将关注文本输入、复选框和单选按钮的实现。这些表单控件是任何应用程序的基础,使用户能够与应用程序交互并提供必要的数据。

让我们看看这个联系表单组件:

![图 5.3 – 示例联系表单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_03.jpg)

图 5.3 – 示例联系表单

联系表单使用了各种 PrimeNG 组件,如 InputText、InputMask、Checkbox 和 RadioButton。让我们看看每个部分。

## InputText

在您的 Angular 项目中,`pInputText` 指令,您首先需要从 PrimeNG 中导入 `InputTextModule` 模块。您可以通过在组件文件中添加以下 `import` 语句来实现:

```js
import { InputTextModule } from 'primeng/inputtext'
```

接下来,您可以在模板文件中使用 `pInputText` 指令创建一个文本输入字段。这里是我们从 *图 5**.3* 中使用的联系表单示例。

```js
<label for="name">Name</label>
<input pInputText id="name" type="text" formControlName="name" />
```

让我们分解代码:

+   `<label for="name">Name</label>`: 这是一个标准的 HTML 标签元素。`for` 属性将标签与具有 `name` ID 的输入字段关联。

+   `pInputText`: 此指令告诉 Angular 将 PrimeNG 文本输入功能和应用样式应用于此输入字段。

+   `id="name"`: 此属性设置输入字段的 ID,用于将其与标签关联。

+   `type="text"`: 此属性设置输入字段的类型。在这种情况下,它是一个文本字段。

+   `formControlName="name"`: 此属性是 Angular 的响应式表单模块的一部分。它将输入字段绑定到组件类中的名为 `name` 的 `FormControl`。

## InputMask

随着我们深入 PrimeNG 的表单控件,让我们将注意力转向一个提供更受控输入体验的组件:**p-inputMask**。该组件旨在处理遵循特定格式的输入,例如电话号码、日期或社会保障号码。

要在 Angular 项目中使用 `p-inputMask` 组件,您首先需要从 PrimeNG 中导入 `InputMaskModule` 模块。您可以通过在组件文件中添加以下 `import` 语句来实现:

```js
import { InputMaskModule } from 'primeng/inputmask'
```

这里是我们在 *图 5**.3* 中使用的联系表单示例。

```js
<label for="phone">Phone</label>
<p-inputMask
   id="phone"
   mask="(999)-999-9999"
   formControlName="phone"
   placeholder="(999)-999-9999"
/>
```

在这个例子中,我们创建了一个用于电话号码的输入字段。`p-inputMask` 组件用作输入字段,强制执行电话号码格式。让我们进一步分解代码:

+   `<label for="phone">Phone</label>`: 这是一个标准的 HTML 标签元素。`for` 属性将标签与具有 `phone` ID 的输入字段关联。

+   `p-inputMask`: 此 PrimeNG 组件用于将 PrimeNG 样式应用于输入字段,并定义电话号码的输入格式。

+   `mask="(999)-999-9999"`:此属性设置特定字段的输入模式。在这种情况下,掩码由字符 9 表示的占位符组成,表示只能在这些位置输入数字字符。通过应用此掩码,用户被限制在指定位置输入数字,确保数据的一致性和准确性。

+   `formControlName="phone"`:此属性是 Angular 的响应式表单模块的一部分。它将输入字段绑定到组件类中名为`phone`的`FormControl`。

## 复选框

随着我们继续探索 PrimeNG 的表单控件,让我们关注一个允许用户进行二元选择的组件:**p-checkbox**。此组件用于创建复选框,允许用户从一组选项中选择一个或多个选项。

要在您的 Angular 项目中使用`p-checkbox`组件,您首先需要从 PrimeNG 导入`CheckboxModule`模块。您可以通过将以下`import`语句添加到组件文件中来实现:

```js
import { CheckboxModule } from 'primeng/checkbox'
```

这里是我们在上一份联系表单中使用的示例,如图**5**.3 所示:

```js
<p-checkbox
   formControlName="subscribe"
   [binary]="true"
   label="Subscribe to newsletter"
/>
```

让我们分解一下代码:

+   `p-checkbox`:此 PrimeNG 组件用于将 PrimeNG 样式应用于复选框字段。

+   `formControlName="subscribe"`:此属性是 Angular 的响应式表单模块的一部分。它将复选框绑定到组件类中名为`subscribe`的`FormControl`。

+   `[binary]="true"`:此属性将复选框的值设置为`true`或`false`。如果复选框被选中,则值为`true`;否则为`false`。

+   `label="Subscribe to newsletter"`:此属性设置显示在复选框旁边的标签。

## 单选按钮

PrimeNG 的`p-radioButton`是一个 UI 组件,可用于在 Angular 表单中创建单选按钮输入。它是一个有用的组件,允许用户从一组互斥选项中选择一个,例如在调查、偏好选择、表单验证、过滤、排序和逐步过程中。

要在您的 Angular 项目中使用`p-radioButton`组件,您首先需要从 PrimeNG 导入`RadioButtonModule`模块。您可以通过将以下`import`语句添加到组件文件中来实现:

```js
import { RadioButtonModule } from 'primeng/radiobutton'
```

这里是我们在上一份联系表单中使用的示例,如图**5**.3 所示:

```js
<p-radioButton
   ngFor="let gender of genders"
   name="gender"
   value="{{ gender.value }}"
   label="{{ gender.name }}"
   formControlName="gender"
/>
```

让我们分解一下代码:

+   `p-radioButton`:这是我们定义 PrimeNG 单选按钮的地方。

+   `*ngFor="let gender of genders"`:这是 Angular 内置的用于渲染列表的指令。它为`genders`数组中的每个性别创建一个新的单选按钮。

+   `name="gender"`:此属性设置单选按钮组的名称。所有具有相同名称的单选按钮属于同一组,并且一次只能选择一个。

+   `value="{{ gender.value }}"`:此属性设置单选按钮的值。它与当前`gender`对象的`value`属性绑定。

+   `label="{{ gender.name }}"`:此属性设置显示在单选按钮旁边的标签。它与当前 `gender` 对象的 `name` 属性绑定。

+   `formControlName="gender"`:此属性是 Angular 的响应式表单模块的一部分。它将一组单选按钮绑定到组件类中的名为 `gender` 的 `FormControl`。

我们已经了解了一些基本的表单控件,这些控件允许用户与您的应用程序进行交互。在下一节中,我们将介绍更多复杂的组件,如下拉菜单、多选和日期选择器。

# 使用下拉菜单、多选和日期选择器

随着我们继续探索 PrimeNG 的表单控件,我们将进入更复杂的组件领域:下拉菜单、多选和日期选择器。这些组件提供了更高层次的交互性,对于许多类型的应用程序来说是必不可少的。

## 下拉菜单

**下拉菜单**组件(也称为选择框)是网络应用程序中最常用的表单元素之一,允许用户从选项列表中选择单个选项。

PrimeNG 下拉菜单提供了一种直观且交互式的方式来从下拉列表中选择一个选项。它通过提供包括单选、多选、筛选、自定义模板、懒加载以及键盘导航在内的广泛选项,增强了用户体验。凭借其可定制的样式和与 Angular 的无缝集成,PrimeNG 下拉菜单是创建增强型下拉功能在 Angular 应用程序中的强大工具。

要开始使用 PrimeNG 下拉菜单,首先在您的组件中导入 `DropdownModule`:

```js
import { DropdownModule } from 'primeng/dropdown'
```

下面是一个使用模板驱动方法使用 `Dropdown` 组件的基本示例:

```js
<p-dropdown
      [options]="cities"
      ngModel
      optionLabel="name"
      name="city"
/>
```

在这个示例中,`cities` 是一个选项数组,`ngModel` 是一个双向绑定属性,它持有选定的选项。`optionLabel` 属性配置下拉菜单显示每个选项的名称属性作为标签。以下是浏览器中的结果:

![图 5.4 – 示例下拉组件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_04.jpg)

图 5.4 – 示例下拉组件

注意

当选项是简单的原始值,如字符串数组时,您不需要在组件中指定 `optionLabel` 和 `optionValue`。

下拉菜单组件包含了许多功能,使其灵活且适用于各种用例。让我们来看看这些选项。

### 筛选

下拉菜单组件的一个突出特点是它内置的筛选功能。此功能允许用户通过在下拉菜单中键入来缩小选项范围,使得在长列表中找到所需的选项变得更容易。要启用筛选,我们只需将 `filter` 属性设置为 `true`:

```js
<p-dropdown
   [options]="cities"
   ngModel
   optionLabel="name"
   name="cityWithFilter"
   [filter]="true"
/>
```

在将 `filter` 属性添加到下拉菜单组件后,您将拥有在下拉菜单中进行搜索的选项。在这里,我搜索了 `van`,显示了结果 **温哥华**:

![图 5.5 – 带筛选功能的下拉菜单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_05.jpg)

图 5.5 – 带筛选功能的下拉菜单

### 分组

为了更好地组织选项,Dropdown 组件支持分组。我们可以通过使用[group]属性将选项分组到某些类别下:

```js
groupedCities = [
   {
      label: 'Canada',
      value: 'ca',
      items: [
         { label: 'Vancouver', value: 'Vancouver' },
         { label: 'Toronto', value: 'Toronto' },
         { label: 'Montreal', value: 'Montreal' },
         { label: 'Ottawa', value: 'Ottawa' },
      ],
   },
   ...
]
...
<p-dropdown
   [options]="groupedCities"
   ngModel
   name="cityWithGroup"
   placeholder="Select a City"
   [group]="true"
>
   <ng-template let-group pTemplate="group">
      <div>
         <span class="pi pi-map-marker"></span>
         <span>{{ group.label }}</span>
      </div>
   </ng-template>
</p-dropdown>
```

在本例中,城市根据`groupedCities`数组中指定的属性进行分组。`ng-template let-group pTemplate="group"`代码定义了一个带有左侧地图制作图标的分组标题模板。让我们看看结果:

![图 5.6 – 带有分组的示例下拉菜单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_06.jpg)

图 5.6 – 带有分组的示例下拉菜单

### 模板化

Dropdown 组件也支持模板化,这意味着我们可以自定义选项的显示方式。让我们看看以下示例:

```js
<p-dropdown
   [options]="cities"
   ngModel
   optionLabel="name"
   name="cityWithTemplate"
   [showClear]="true"
   placeholder="Select a City"
>
   <ng-template pTemplate="selectedItem">
      <div *ngIf="form.value.cityWithTemplate">
         <span class="pi pi-map-marker"></span>
         <span>{{ form.value.cityWithTemplate.name }}</span>
      </div>
   </ng-template>
   <ng-template let-city pTemplate="item">
      <div>
         <span class="pi pi-map-marker"></span>
         <span>{{ city.name }}</span>
      </div>
   </ng-template>
</p-dropdown>
```

在本例中,我们可以看到有两个`ng-template`元素用于支持我们如何样式化选项和所选值:

+   `<ng-template pTemplate="selectedItem">`:这定义了一个用于自定义下拉组件中选中项渲染的模板。它显示了模型中选中城市的`name`属性。

+   `<ng-template let-city pTemplate="item">`:这是选项列表中每个项目的模板。

让我们看看结果:

![图 5.7 – 带有模板化的示例下拉菜单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_07.jpg)

图 5.7 – 带有模板化的示例下拉菜单

我们可以通过左侧带有地图制作图标的方式来查看所选城市的样式。下拉菜单还具备使用组件中的`[showClear]="true"`选项清除所选项的能力。

### 处理事件

PrimeNG 的 Dropdown 组件提供了几个事件,开发者可以利用这些事件来增强其应用程序的功能性和交互性:

+   `onChange`:当下拉值发生变化时发出

+   `onFilter`:当下拉中的数据进行过滤时触发

+   `onFocus`:当下拉获得焦点时触发

+   `onBlur`:当下拉失去焦点时调用

+   `onClick`:当组件被点击时调用

+   `onShow`:当下拉菜单覆盖层变为可见时触发

+   `onHide`:当下拉覆盖层变为隐藏时触发

+   `onClear`:当下拉值被清除时调用

+   `onLazyLoad`:在懒加载模式下调用以加载数据

这里是一个示例,说明您如何使用 PrimeNG Dropdown 的`onChange`事件,当用户选择一个选项时:

```js
<p-dropdown
   [options]="cities"
   ngModel
   optionLabel="name"
   (onChange)="onCityChange($event.value)"
   name="cityWithEvents"
/>
...
onCityChange(value: { name: string; code: string }) {
   alert(`You have selected: ${value.name}`)
}
```

在本例中,每当所选城市发生变化时,都会调用`onCityChange`方法,并在浏览器中显示一个警告。

总结来说,PrimeNG 中的 Dropdown 组件是一个功能强大的工具,提供了从基本选择到高级功能如过滤、分组和模板化等一系列功能。在下一节中,我们将深入探讨 MultiSelect 组件。

注意

您可以使用 Dropdown 组件进行更多功能和配置。请访问[`primeng.org/dropdown`](https://primeng.org/dropdown)获取最新文档。

## MultiSelect

`MultiSelect` 组件是一种选择输入形式,允许用户从下拉列表中选择多个选项,这在需要提供选项列表并允许用户选择多个选项时非常有用。例如,在一个调查表中,你可能会要求用户选择他们精通的所有编程语言。在这种情况下,`MultiSelect` 组件是一个理想的选择。

要开始使用 PrimeNG 的 `MultiSelect` 组件,首先在你的组件中导入 `MultiSelectModule`:

```js
import { MultiSelectModule } from 'primeng/multiselect'
```

这里是一个使用模板驱动方法使用 `MultiSelect` 组件的基本示例:

```js
cities = [
   { name: 'Toronto', code: 'TOR' },
   { name: 'Montreal', code: 'MTL' },
   { name: 'Vancouver', code: 'VAN' },
   { name: 'Calgary', code: 'CGY' },
   { name: 'Ottawa', code: 'OTT' },
   { name: 'Edmonton', code: 'EDM' },
   { name: 'Quebec City', code: 'QUE' },
   { name: 'Winnipeg', code: 'WIN' },
   { name: 'Hamilton', code: 'HAM' },
   { name: 'Kitchener', code: 'KIT' },
]
...
<p-multiSelect
   [options]="cities"
   ngModel
   optionLabel="name"
   name="city"
/>
```

在代码片段中,`cities` 是一个选项数组,您希望将其显示在 `MultiSelect` 列表中,而 `optionLabel` 指示下拉列表显示每个选项的 `name` 属性作为标签。让我们看看结果:

![图 5.8 – MultiSelect 示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_08.jpg)

图 5.8 – MultiSelect 示例

在前面的屏幕截图中,我们有一个基本的 `MultiSelect` 组件,其中已选择 **多伦多** 和 **蒙特利尔**。

`MultiSelect` 组件具有一些功能,使其成为创建交互式表单的强大工具。让我们看看其中的一些。

### 过滤

`MultiSelect` 组件的一个突出特点是它内置的过滤功能,允许用户通过在搜索框中输入来缩小下拉列表中的选项。这在处理大量选项时特别有用,例如:

```js
<p-multiSelect [options]="cities" filter attribute to true, you enable the filtering feature. Now, when a user clicks on the MultiSelect box, they will see a search box at the top of the dropdown list.
			Grouping
			For better organization of options, the `MultiSelect` component also supports grouping. We can group options under certain categories by using the `[``group]` attribute:

```

groupedCities = [

{

标签: '加拿大',

值: 'ca',

项目: [

{ 标签: '温哥华', 值: '温哥华' },

{ 标签: '多伦多', 值: '多伦多' },

{ 标签: '蒙特利尔', 值: '蒙特利尔' },

{ 标签: '渥太华', 值: '渥太华' },

],

},

...

]

...

<p-multiSelect

[选项]="groupedCities"

[分组]="true"

ngModel

名称="cityWithGroup"

默认标签="选择一个城市"

<ng-template let-group pTemplate="group">

<div>

<span class="pi pi-map-marker"></span>

<span>{{ group.label }}</span>

</div>

</ng-template>

</p-multiSelect>

```js

			In this example, the cities are grouped according to a property specified in the `groupedCities` array. The code `ng-template let-group pTemplate="group"` defines a template for the group header with a map maker icon on the left. Let’s have a look at the result:
			![Figure 5.9 – Sample MultiSelect with grouping](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_09.jpg)

			Figure 5.9 – Sample MultiSelect with grouping
			Templating
			The `MultiSelect` component also supports templating, which allows you to customize the appearance of the options in the dropdown list and the selected items:

```

<p-multiSelect

[选项]="cities"

ngModel

默认标签="选择一个国家"

名称="cityWithTemplating"

选项标签="name"

<ng-template let-cities pTemplate="selectedItems">

<div *ngFor="let city of cities">

<span class="pi pi-map-marker"></span>

<span>{{ city.name }}</span>

</div>

<div *ngIf="cities?.length === 0">选择城市</div>

</ng-template>

<ng-template let-city pTemplate="item">

<div>

<span class="pi pi-map-marker"></span>

<span>{{ city.name }}</span>

</div>

</ng-template>

</p-multiSelect>

```js

			In this code snippet, we use the `ng-template` element to define a template for the items in the selection list. The `let-cities` attribute in `<ng-template let-cities pTemplate="selectedItems">` creates a local variable called `cities` that holds the current selected cities. The `<ng-template let-city pTemplate="item">` attribute tells PrimeNG to use this template for the items in the dropdown list.
			Note
			When checking the PrimeNG documentation, under the `MultiSelect` component, you can find the complete list here: [`primeng.org/multiselect#api.multiselect.templates`](https://primeng.org/multiselect#api.multiselect.templates).
			The PrimeNG `MultiSelect` component is a versatile tool that can enhance the user experience of your forms. In the next section, we will look into the `Calendar` component.
			Calendar
			PrimeNG **Calendar** is a date picker component that allows users to select dates, times, or both. It’s highly customizable, supporting various formats and modes such as inline, button, icon, and input styles.
			To get started with the PrimeNG `Calendar` component, first import `CalendarModule` in your component:

```

导入 { CalendarModule } 从 'primeng/calendar'

```js

			PrimeNG provides the `p-calendar` component for creating date pickers. Here’s a basic example:

```

<p-calendar ngModel 名称="calendar" />

```js

			In this example, `name="calendar"` is a form control in your form and will hold the selected date:
			![Figure 5.10 – Sample calendar](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_10.jpg)

			Figure 5.10 – Sample calendar
			The PrimeNG Calendar component isn’t just a simple date picker. It has a wealth of advanced features that can cater to almost any requirement you might have.
			Format
			The selected date has the default format of `dd/mm/yyyy`. However, you can update the format of the selected date by using the `dateFormat` property. Here is the list of the available options for `dateFormat`:

				*   `d`: Day of month (no leading zero).
				*   `dd`: Day of month (two digits).
				*   `o`: Day of the year (no leading zeros).
				*   `oo`: Day of the year (three digits).
				*   `D`: Day name, short.
				*   `DD`: Day name, long.
				*   `m`: Month of year (no leading zero).
				*   `mm`: Month of year (two digits).
				*   `M`: Month name, short.
				*   `MM`: Month name, long.
				*   `y`: Year (two digits).
				*   `yy`: Year (four digits).
				*   `@`: Unix timestamp (milliseconds (ms) – since 01/01/1970).
				*   `!`: Windows ticks (100ns (nanoseconds) – since 01/01/0001).
				*   `'...'`: Literal text allows you to insert any text as it is in the date string. For example, `'Day: 'dd 'Month: 'MM 'Year: 'yy` will turn into `Day: 08 Month: August` `Year: 2023`.
				*   `''`: If you want to display a single quote within your date string, you must use two single quotes together, for example, `'Today''s date is 'dd/MM/yy` will turn into `Today's date` `is 08/August/2023`.
				*   Any other characters that are not recognized as part of the date format pattern will be treated as literal text and will be displayed as is. For example, if you use the format string `dd+MM+yy`, a date might display as `16+August+2023`.

			Here is an example of changing the format of the selected date:

```

<p-calendar

ngModel

名称="calendarWithFormat"

日期格式="dd-mm-yy"

/>

```js

			In this example, `dateFormat="dd-mm-yy"` indicates the new format of the selected date. Here is the result:

			![Figure 5.11 – Sample calendar](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_11.jpg)

			Figure 5.11 – Sample calendar
			After selecting the date, you can see the format of the selected date changes to `13-07-2023`.
			Range selection
			To enable range selection in the PrimeNG Calendar component, you can use the `selectionMode` attribute along with the `ngModel` directive. The `selectionMode` attribute allows you to specify the mode of selection, including `"single"`, `"multiple"`, and `"range"`. Here’s how you can enable range selection:

```

<p-calendar

ngModel

名称="calendarDateRange"

选择模式="range"

/>

```js

			Here, `calendarDateRange` is an array that will hold the start and end dates of the selected range.
			![Figure 5.12 – Sample calendar with date range](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_12.jpg)

			Figure 5.12 – Sample calendar with date range
			PrimeNG Calendar is a powerful, flexible, and customizable date picker component for Angular applications. It also offers extensive customization options, allowing you to tailor its appearance and behavior to your needs.
			Note
			In this section, we’ve only scratched the surface of what the PrimeNG `Calendar` can do. We encourage you to explore its documentation to learn more about its features and capabilities: [`primeng.org/calendar`](https://primeng.org/calendar).
			In this section, we went through some important input components. In the next section, let’s go through validation and input handling techniques.
			Implementing form validation
			As we venture deeper into the realm of form controls, we come across a critical aspect of any form—**validation**. Ensuring the data entered by users is valid and as expected is paramount to maintaining data integrity and providing a seamless user experience. It’s not just about checking whether a field is empty or not; it’s about ensuring the data is in the right format, within certain limits, or meets any other criteria you set. In this section, we’ll explore how to implement form validation using Angular and PrimeNG.
			Understanding Angular form states
			Before diving into the details of Angular form validation, let’s first discuss some important concepts related to form validation. In Angular, a form is represented by the `FormGroup` class, which contains an organized collection of form controls such as input fields, checkboxes, and dropdowns.
			Angular form validation revolves around the state of the form controls. There are several states that a form control can have:

				*   `pristine`: A form control is considered pristine if the user hasn’t interacted with it yet. It means that the control’s value has not been changed since it was initialized.
				*   `dirty`: A form control becomes dirty once the user interacts with it and modifies its value.
				*   `touched`: A form control is marked as touched when the user focuses on it and then moves away, indicating that they have interacted with the control.
				*   `untouched`: The opposite of touched, an untouched form control means that the user hasn’t interacted with it yet. The difference between `pristine` and `untouched` is that `untouched` refers to a form control that has not been interacted with by the user, while `pristine` indicates a form control that has not been modified.
				*   `valid`: A form control is considered valid if it satisfies all the validation rules defined for it.
				*   `invalid`: If a form control fails to satisfy any of the validation rules, it is marked as invalid.

			These states play a crucial role in form validation, as they help determine the visual feedback to provide to the user and enable or disable form submission based on the validity of the form controls.
			Built-in Angular form validation
			Angular provides a set of built-in validators that cover common form validation scenarios. These validators can be used on form controls to ensure that the user input meets specific requirements. Here are a few examples:

				*   `required`: The `required` validator ensures that a form control has a non-empty value
				*   `minLength` and m`axLength`: These validators validate the minimum and maximum length of a form control’s value, respectively
				*   `pattern`: The `pattern` validator allows you to specify a regular expression pattern that the form control’s value must match

			To apply these validators to a form control, you need to associate them with the control in the component code. For example, if you have an input field for the user’s name that must be filled in and has a minimum length of four characters, you can define the form with validators like this (in a template-driven form):

```

<input

名称="first"

ngModel

必需

最小长度="4"

#name="ngModel"

/>

<ng-container *ngIf="name.invalid && (name.dirty || name.touched)">

<div *ngIf="name.errors?.['required']">

此字段是必需的

</div>

<div *ngIf="name.errors?.['minlength']">

名称长度必须至少为 4 个字符。

</div>

</ng-container>

```js

			Let’s break down the example:

				*   `required`: This indicates that the input must have a value before the form can be submitted.
				*   `minlength="4"`: This specifies that the input value must be at least four characters long.
				*   `#name="ngModel"`: This creates a local template variable called `name`. This allows us to access the properties and methods of the `NgModel` directive within the template.
				*   `*ngIf="name.invalid && (name.dirty || name.touched)"`: The `*ngIf` directive checks if the input is invalid (`name.invalid`) and if the input has been interacted with (`name.dirty` or `name.touched`). If both conditions are true, the content inside `<ng-container>` will be displayed.
				*   `*ngIf="name.errors?.['required']"`: This displays an error message if the input is missing a value (because of the `required` attribute).
				*   `*ngIf="name.errors?.['minlength']"`: This displays an error message if the input value is less than four characters long (because of the `minlength="4"` attribute).

			Now let’s look at validators in reactive forms:

```

contactForm = this.formBuilder.group({

name: ['', [Validators.required, Validators.minLength(4)]],

})

...

<input pInputText id="name" type="text" formControlName="name" />

<ng-container *ngIf="contactForm.controls.name as name">

<div *ngIf="name.dirty && name.hasError('required')">

This field is required

</div>

<div *ngIf="name.dirty && name.hasError('minlength')">

名称长度必须至少为 4 个字符。

</div>

</ng-container>

```js

			Let’s break down the code:

				*   `name: ['', [Validators.required, Validators.minLength(4)]]`: This initializes a form control called `name` with an empty string as its default value. The array that follows specifies the validation rules for this control:
    *   `Validators.required`: This validator ensures that the control has a non-empty value
    *   `Validators.minLength(4)`: This validator ensures that the control’s value is at least four characters long
				*   `*ngIf="contactForm.controls.name as name"`: This checks if the name control exists in `contactForm` and assigns it to a local template variable, `name`
				*   `*ngIf="name.dirty && name.hasError('required'):` This displays an error message if the `name` control has been interacted with (`name.dirty`) and if it’s missing a value (`name.hasError('required')`)
				*   `*ngIf="name.dirty && name.hasError('minlength')"`: This displays an error message if the `name` control has been interacted with and if its value is less than four characters long

			Based on the logic just explained, we can create the entire form with proper validation. *Figure 5**.13* shows a contact form with an invalid state:
			![Figure 5.13 – Sample form validation](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_13.jpg)

			Figure 5.13 – Sample form validation
			As we can see, the error state indicates that the form requires all of the fields to be filled out before the information can be submitted.
			Crafting custom form validation
			While Angular provides a range of built-in validators, there are times when we need to add something specific to our application. Thankfully, Angular allows us to create custom form validators.
			Here’s a simple example of creating a custom validator that checks whether the name is invalid or not:

```

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'

export const invalidNameValidator = (nameRe: RegExp): ValidatorFn => {

return (control: AbstractControl): ValidationErrors | null => {

const invalid = nameRe.test(control.value)

return invalid ? { invalidName: { value: control.value } } : null

}

}

```js

			In the code example, the main function, `invalidNameValidator`, takes a regular expression (`nameRe`) as its argument and returns a custom validator function. This custom validator function checks whether a form control’s value matches the provided regular expression. If the value doesn’t match the pattern, meaning that it’s valid, the function returns `null`, indicating that there are no validation errors.
			Note
			Custom validators return a key-value pair if the validation fails. The key is a string of your choosing that describes the error, and the value is the value of the control.
			To use this validator, we just need to add it to the existing validators:

```

contactForm = this.formBuilder.group({

name: [

'',

[

Validators.required,

Validators.minLength(4),

// 自定义验证器

invalidNameValidator(/test/i),

],

],

...

})

----

<div *ngIf="name.dirty && name.hasError('invalidName')">

名称不能为 "{{ name.errors?.['invalidName'].value }}".

</div>

```js

			Here is the code breakdown:

				*   `invalidNameValidator(/test/i)`: This is a custom validator (as we discussed in the previous section). It checks whether the form control’s value matches the regular expression `/test/i`. If the value matches, the validator will flag it as invalid.
				*   `*ngIf="name.dirty && name.hasError('invalidName')"`: This displays an error message if the `name` control has been interacted with and if the name is invalid.

			Let’s look at the result:
			![Figure 5.14 – Custom form validation](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_05_14.jpg)

			Figure 5.14 – Custom form validation
			PrimeNG and form validation
			PrimeNG complements Angular’s form validation by providing visual feedback for validation errors. It includes a variety of CSS classes that can be used to highlight invalid fields and display error messages.
			Here’s an example of how to use PrimeNG to display validation errors:

```

<label for="name">名称</label>

<input pInputText id="name" type="text" formControlName="name" />

<ng-container *ngIf="contactForm.controls.name as name">

<small

class="p-error"

ngIf="name.dirty && name.hasError('required')"

此字段为必填项

</small>

<small

class="p-error"

ngIf="name.dirty && name.hasError('minlength')"

名称太短

</small>

</ng-container>

```js

			In this example, if the `name` field is invalid and `dirty`, the `p-error` class is added to the field, and the error message is displayed.
			Form validation is a critical aspect of any application that involves user input. It helps ensure data integrity and enhances the user experience. Angular provides robust support for form validation, and PrimeNG complements this by providing visual feedback for validation errors.
			In this section, we’ve explored how to implement form validation using Angular and PrimeNG. However, this is just the tip of the iceberg. Both Angular and PrimeNG offer a wealth of features for form validation that can cater to almost any requirement We encourage you to explore Angular documentation to learn more: [`angular.io/guide/form-validation`](https://angular.io/guide/form-validation)
			Summary
			In this chapter, we delved into the world of input components and form controls, exploring their usage in Angular applications with PrimeNG. We started by understanding their importance in building interactive applications, setting the foundation for the practical aspects that followed.
			We then navigated through the implementation of basic form controls such as text inputs, checkboxes, and radio buttons, and moved on to more complex ones such as dropdowns, multi-selects, and date pickers. Each section was enriched with code examples, demonstrating the integration of these components into our applications.
			Toward the end, we tackled the crucial topic of form validation. We emphasized the importance of validating user input for enhancing user experience and ensuring data integrity, guiding you through the process with Angular and PrimeNG.
			As we look forward to the next chapter, on data tables and other displaying components, remember that the knowledge gained here is vital for any web developer. Form controls are fundamental to web applications, and understanding their effective use is key to creating user-friendly applications.

```




# 第六章:与表、列表和卡片组件一起工作

随着我们继续使用 PrimeNG 和 Angular 的旅程,我们发现自己处于数据展示组件的领域。在本章中,我们将重点关注三个关键组件:数据表、列表和卡片组件。这些组件是任何应用程序的功臣,负责以清晰、简洁和用户友好的方式向用户展示数据。它们是我们应用程序中的原始数据与用户交互的精致、交互式界面之间的桥梁。

本章的目标是向您提供必要的知识和技能,以便您能够有效地利用数据表、列表和卡片组件,以最用户友好的方式呈现数据。通过掌握这些组件,您可以确保用户能够轻松理解并交互数据,最终提高用户参与度和满意度。

在本章中,我们将涵盖以下主题:

+   使用 PrimeFlex 创建响应式布局

+   介绍数据展示组件

+   与数据表组件一起工作

+   与列表组件一起工作

+   与卡片组件一起工作

# 技术要求

本章包含 PrimeNG 展示组件的各种代码示例。您可以在以下 GitHub 仓库的`chapter-06`文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-06`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-06)。

# 使用 PrimeFlex 创建响应式布局

`Flexbox`和`Grid`等。

## 将 PrimeFlex 与 PrimeNG 集成

PrimeFlex 可以通过`npm`安装轻松集成到 PrimeNG 中:

```js
npm install primeflex
```

在安装过程之后,我们将 PrimeFlex 库包含到我们的`styles.scss`文件中:

```js
// styles.scss
@import 'primeflex/primeflex.scss';
```

一旦我们导入`primeflex.scss`,我们就能创建一个 Angular 应用程序,确保适当的间距、排版、布局以及所有其他基本元素。

让我们比较一下使用和未使用 PrimeFlex 构建布局的方式。

在没有实用库如 PrimeFlex 的情况下构建布局可能会很繁琐。您可能会发现自己正在编写重复且冗长的 CSS 代码,如下面的代码示例所示:

```js
<h1 [routerLink]="['/']">Welcome to chapter-06</h1>
<div class="layout-wrapper">
   <aside>
      <nav>
         <p-menu [model]="items" />
      </nav>
   </aside>
   <main>
      <router-outlet />
   </main>
</div>
....
styles: [
   `
      .layout-wrapper {
         display: flex;
         gap: 4rem;
         flex-wrap: wrap;
}
   `,
],
```

我们通过创建`.layout-wrapper`类并向其中添加 CSS 语法来应用样式。这是一个由网络浏览器支持的标准化 CSS 实现。使用常规 CSS,您需要从头开始编写自己的样式。

另一方面,PrimeFlex 通过提供封装常见 CSS 属性的实用类来简化这一过程。让我们看看带有 PrimeFlex 实用类的模板:

```js
<h1 [routerLink]="['/']">Welcome to chapter-06</h1>
<div class="flex flex-wrap gap-7">
       ...
</div>
```

通过使用常见的 CSS 实用类,我们可以简化我们的模板,例如将`.layout-wrapper`类转换为`flex flex-wrap gap-7`,从而消除编写自定义 CSS 代码的需求。

PrimeFlex 实用类的使用为你的整个 Angular 应用提供了易用性和一致性,尽管正常的 CSS 提供了更大的灵活性和定制选项,但需要更多的努力和专业知识来实现所需的样式。让我们在浏览器中查看:

![图 6.1 – PrimeFlex 类示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_01.jpg)

图 6.1 – PrimeFlex 类示例

你可以观察到实用类映射到它们各自的 CSS 样式,例如,从 `flex` 到 `display:` `flex !important;`。

这里有一些与布局(`Flexbox` 和 `Grid`)和文本相关的实用类示例:

![图 6.2 – 常见 PrimeFlex 实用类](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_02.jpg)

图 6.2 – 常见 PrimeFlex 实用类

这些类是 PrimeFlex 对 CSS 的实用优先方法的一部分,为你提供了一组可重用的类,这些类封装了常见的布局和文本样式模式。通过使用这些类,你可以快速构建复杂的布局并应用文本样式,而无需编写自定义 CSS 代码,从而实现更高效和可维护的开发过程。有关完整实用类集,请查阅 [`primeflex.org`](https://primeflex.org) 的文档。

## 在 PrimeFlex 中使用 Flexbox

PrimeFlex 提供了一个强大且多功能的 **Flexbox** 实用系统,允许你创建灵活和响应式的布局。使用 Flexbox,你可以轻松地在容器内分配和排列元素,使其成为构建现代和动态用户界面的优秀工具。

### 创建弹性容器

要创建 Flexbox 布局,你需要指定一个容器元素作为弹性容器。通过将 `flex` 类应用于容器,你可以启用 Flexbox 行为,允许子元素成为弹性项:

```js
<div class="flex">
   <div class="text-center p-3 bg-primary">Flex Item 1</div>
   <div class="text-center p-3 bg-primary">Flex Item 2</div>
   <div class="text-center p-3 bg-primary">Flex Item 3</div>
</div>
```

在前面的代码示例中,`flex` 类被应用于容器元素,这使得它成为一个弹性容器。容器内的子元素自动成为弹性项。

这是 *图 6**.3* 的基本代码,你将在后面看到。

### 应用弹性方向

Flexbox 还提供了四种主要方向来在弹性容器内排列弹性项:`row`、`row-reverse`、`column` 和 `column-reverse`。方向是通过将以下类之一应用于弹性容器来确定的:

+   `flex-row`: 项目从左到右以行形式排列

+   `flex-row-reverse`: 项目从右到左以行形式排列

+   `flex-column`: 项目从上到下以列的形式排列

+   `flex-column-reverse`: 项目从下到上以列的形式排列

这是一个我们如何创建具有不同布局的弹性容器的示例:

![图 6.3 – PrimeFlex Flexbox 示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_03.jpg)

图 6.3 – PrimeFlex Flexbox 示例

在这个示例中,我们通过轻松地应用不同的类,在行或列中创建多样化的布局,从而利用 Flexbox 的力量。

注意

要了解更多关于 Flexbox 及如何调试的信息,您可以访问 [`developer.chrome.com/docs/devtools/css/flexbox/`](https://developer.chrome.com/docs/devtools/css/flexbox/)。这是一个有用的资源,它提供了关于理解 Flexbox 和解决您可能遇到的问题的见解和指导。

### 使用 flex wrap

默认情况下,弹性项目会尝试适应单行。然而,如果空间不足,弹性项目会缩小以适应。要控制弹性项目的换行行为,可以使用以下类:

+   `flex-wrap`: 如果需要,项目将换行到多行

+   `flex-wrap-reverse`: 如果需要,项目将按相反顺序换行到多行

+   `flex-nowrap`: 项目保持在单行上

让我们将换行功能应用到我们的弹性容器中:

```js
<div class="flex flex-wrap">
   ...
</div>
```

在此示例中,弹性容器应用了 `flex-wrap` 类,允许弹性项目在需要时换行。

### 使用 justify content

Flexbox 提供了强大的对齐选项,用于在主轴和交叉轴上定位弹性项目。PrimeFlex 提供了各种类来控制对齐:

+   `justify-content-end`: 这会将弹性项目沿主轴对齐到弹性容器的末尾

+   `justify-content-center`: 这会将弹性项目沿弹性容器的主轴居中

+   `justify-content-between`: 这将在主轴上均匀分布弹性项目,并在它们之间留有相等的空间

+   `justify-content-around`: 这将在主轴上均匀分布弹性项目,并在它们周围留有相等的空间

+   `justify-content-evenly`: 这将在主轴上均匀分布弹性项目,并在它们之间留有相等的空间,包括第一个项目之前和最后一个项目之后

这些对齐类可以应用于弹性容器或单个弹性项目:

```js
<div class="flex flex-wrap justify-content-evenly">
   ...
</div>
```

在此先前的示例中,弹性容器应用了 `justify-content-evenly` 类,它将弹性项目均匀分布,并在它们周围留有相等的空间。`flex-wrap` 类允许项目在需要时换行。

考虑到所有的 Flexbox 代码,让我们看看结果:

![图 6.4 – Flexbox 示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_04.jpg)

图 6.4 – Flexbox 示例

您可以看到元素是一个弹性容器,如果需要,弹性项目会换行。弹性项目沿主轴均匀分布,它们之间以及容器的开始和结束处都有相等的空间。

### 使用 PrimeFlex 中的网格系统

PrimeFlex 中的网格系统是构建复杂布局的另一个强大功能,它提供了简单直观的语法来构建网格和排列页面上的元素。

PrimeFlex 网格遵循 12 列结构。每一行由一个容器组成,该容器包含一个或多个列。行内的列会根据可用空间自动调整其宽度。

要创建一个网格布局,您需要将内容包裹在一个带有 `grid` 类的容器元素中。在容器内部,您可以使用 `col` 类来定义列。列是通过添加 `col-{size}` 类来指定的,其中 `{size}` 代表元素应跨越的列数。

这里是一个具有两列的基本网格结构的示例:

```js
<div class="grid">
   <div class="col-6">
         <div class="text-center p-3 bg-primary">6</div>
   </div>
   <div class="col-6">
         <div class="text-center p-3 bg-primary">6</div>
   </div>
   <div class="col-6">
         <div class="text-center p-3 bg-primary">6</div>
   </div>
   <div class="col-6">
         <div class="text-center p-3 bg-primary">6</div>
   </div>
</div>
```

在这个示例中,我们有一个包含两列的网格容器。每个列跨越六列,从而形成两个等宽的列。以下是结果:

![图 6.5 – PrimeFlex 网格示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_05.jpg)

图 6.5 – PrimeFlex 网格示例

PrimeFlex 网格还提供了响应式类,允许您为不同的屏幕尺寸创建不同的布局。您可以使用以下语法根据屏幕断点指定不同的列大小:

+   `sm`:小屏幕(576px 及以上)

+   `md`:中等屏幕(768px 及以上)

+   `lg`:大屏幕(992px 及以上)

+   `xl`:超大屏幕(1200px 及以上)

让我们使用断点来创建响应式布局:

```js
<div class="grid">
   <div class="col-12 md:col-6 lg:col-3">
      <div class="text-center p-3 bg-primary">6</div>
   </div>
   ...
</div>
```

在这个示例中,我们有一个包含两列的网格容器。默认情况下,在小型屏幕上(12 列),每个列宽度为全宽,在中等屏幕上(六列)宽度为半宽,在大屏幕上(四列)宽度为三分之一。

PrimeFlex 是一个对网页开发者非常有价值的工具,它提供了一种简化的 CSS 样式方法。它与 PrimeNG 的集成增强了开发体验,提供了一致且灵活的设计系统。在下一节中,我们将介绍 PrimeNG 数据显示组件。

# 介绍数据显示组件

正如我们所知,数据是任何应用程序的生命线,但原始数据本身并不很有用。真正重要的是我们如何向用户展示这些数据。这正是数据显示组件发挥作用的地方。它们是将原始数据转换为有意义信息的工具,为用户提供洞察力,并使他们能够与数据互动。

PrimeNG 提供了各种数据显示组件。这些组件旨在以清晰、简洁和用户友好的方式展示数据。它们包括数据表、列表、卡片等等。每个组件都有其优势和用例,共同提供了一个全面的数据显示工具包。没有这些组件,用户将面临难以解释和分析的原始数据。这可能导致错误、误解和不良决策。

让我们看看以下 PrimeNG 数据显示组件及其使用场景:

+   **数据表**,例如,非常适合以结构化格式显示大量数据。它们支持排序、过滤和分页等功能,使用户能够轻松导航和与数据互动。

+   另一方面,**列表**非常适合以简单直接的方式显示一系列项目。它们非常灵活,可以用于各种用例,从简单的文本项列表到具有自定义布局的复杂列表。

+   **卡片**是数据展示的另一个强大工具。它们以灵活和可扩展的格式展示相关信息的集合,是一种很好的展示方式。卡片可以包含任何类型的内容,从文本和图像到按钮和链接,并且可以以各种方式排列,以创建视觉上吸引人的布局。

在接下来的章节中,我们将更深入地探讨这些组件,探讨如何在您的 Angular 应用程序中使用它们。我们将提供代码示例来说明它们的用法,并讨论每个组件可用的各种选项和配置。

注意

记住,有效数据展示的关键不仅在于选择正确的组件,还在于正确地使用它们。这关乎理解数据,知道哪些信息对用户来说很重要,并以易于理解和交互的方式呈现。

# 与数据表格组件一起工作

深入数据展示的世界,我们发现周围环绕着众多组件,每个组件都有其独特的特性和功能。在这些组件中,PrimeNG 表格因其多功能性和强大的功能而脱颖而出,可以将原始数据转换为有意义的、交互式的和视觉上吸引人的信息。

要在您的 Angular 项目中使用 PrimeNG 表格,您首先需要从 PrimeNG 中导入 `TableModule`。您可以通过将以下 `import` 语句添加到您的模块文件中来实现这一点:

```js
import { TableModule } from 'primeng/table'
```

本节将探讨 PrimeNG 表格的各种功能和特性,为您提供实际示例和见解,帮助您在应用程序中充分利用其全部潜力。在以下示例中,我们将使用示例产品数据。以下是 `Product` 的界面:

```js
interface Product {
   id: number
   name: string
   price: number
   description: string
   quantity: number
   rating: number
   category: string
}
```

那么,让我们开始学习表格。

## 创建基本表格

PrimeNG 表格需要一组数据来显示,以及定义如何表示这些数据的列组件。以下是一个简单的示例:

```js
<p-table
   [value]="products"
   [tableStyle]="{ 'min-width': '50rem' }"
>
      <ng-template pTemplate="header">
            <tr>
                  <th>ID</th>
                  <th>Name</th>
                  <th>Category</th>
                  <th>Quantity</th>
            </tr>
      </ng-template>
      <ng-template pTemplate="body" let-product>
            <tr>
                  <td>{{ product.id }}</td>
                  <td>{{ product.name }}</td>
                  <td>{{ product.category }}</td>
                  <td>{{ product.quantity }}</td>
            </tr>
      </ng-template>
</p-table>
```

让我们分解代码片段:

+   `<p-table>`: 这是 PrimeNG 表格组件,用于显示表格数据。

+   `[value]="products"`: 这个属性绑定将 `products` 属性绑定到 `p-table` 组件的 `value` 属性。这意味着组件代码中的 `products` 变量是表格的数据源。

+   `[tableStyle]="{ 'min-width': '50rem' }"`: 这个属性绑定将内联 CSS 样式对象绑定到 `p-table` 组件的 `tableStyle` 属性。提供的样式对象将表格的最小宽度设置为 `50rem`。

+   `<ng-template pTemplate="header">`: 这是一个表格表头行的模板。它定义了列标题。在这种情况下,表格有四列:`ID`、`Name`、`Category` 和 `Quantity`。

+   `<ng-template pTemplate="body" let-product>`:这是一个表格主体的模板。它定义了数据每一行应该如何显示。`let-product` 语法用于创建一个局部模板变量 product,它保存了每一行的当前产品对象。

+   在身体模板内部,我们为每一行有一个 `<tr>` 元素,并为每一行中的每个单元格有一个 `<td>` 元素。`{{ product.id }}`、`{{ product.name }}`、`{{ product.category }}` 和 `{{ product.quantity }}` 表达式用于将当前产品对象的属性绑定到单元格中。

之后,我们将有一个基本的表格,如下所示:

![图 6.6 – 基本表格](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_06.jpg)

图 6.6 – 基本表格

## 带动态列的表格

可以使用 `*ngFor` 指令动态定义列。这在您的数据结构事先未知或可以动态变化时特别有用。下面是如何做到这一点的方法:

```js
cols = [
   { field: 'id', header: 'ID' },
   { field: 'name', header: 'Name' },
   { field: 'category', header: 'Category' },
   { field: 'quantity', header: 'Quantity' },
]
...
<p-table
    [columns]="cols"
    [value]="products"
    [tableStyle]="{ 'min-width': '50rem' }"
>
      <ng-template pTemplate="header" let-columns>
            <tr>
                  <th *ngFor="let col of columns">
                        {{ col.header }}
                  </th>
            </tr>
      </ng-template>
      <ng-template pTemplate="body" let-rowData let-columns="columns">
            <tr>
                  <td *ngFor="let col of columns">
                        {{ rowData[col.field] }}
                  </td>
            </tr>
      </ng-template>
</p-table>
```

在这个例子中,`columns` 属性绑定到 `cols` 数组,该数组包含列的定义。每一列都是一个对象,具有 `header` 属性(列标题)和 `field` 属性(绑定到数据对象的属性)。由于表格数据是在组件中定义的,因此可以为用户提供选择要显示的列、重新排序列或动态向表格中添加或删除列的选项。

## 带排序的表格

排序是数据展示的基本方面,允许用户以对特定任务有意义的方式对数据进行排序。PrimeNG 为表格中的数据排序提供了内置功能。

下面是如何在 PrimeNG 表格组件中启用排序的示例:

```js
<p-table [value]="products">
      <ng-template pTemplate="header">
            <tr>
                  <th pSortableColumn="name">Name
                        <p-sortIcon field="name"></p-sortIcon>
                  </th>
                  <th pSortableColumn="price">Price
                        <p-sortIcon field="price"></p-sortIcon>
                  </th>
            </tr>
      </ng-template>
      <ng-template pTemplate="body" let-product>
            <tr>
                  <td>{{ product.name }}</td>
                  <td>{{ product.price }}</td>
            </tr>
      </ng-template>
</p-table>
```

在这个例子中,使用 `pSortableColumn` 指令指定当点击列标题时应按哪个字段排序数据。使用 `p-sortIcon` 组件显示表示排序顺序的图标。

默认情况下,点击列标题一次将按升序排序数据。再次点击它将按降序排序数据。让我们看看结果:

![图 6.7 – 带排序的表格](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_07.jpg)

图 6.7 – 带排序的表格

您可以看到,在截图上,点击 **价格** 标题后,表格按价格升序排序。

## 带过滤的表格

过滤是另一个强大的功能,允许用户根据特定标准缩小表格中显示的数据。同样,PrimeNG 为此提供了内置功能。

要为列启用过滤,您需要向表格中的列定义添加一些自定义模板。以下是如何做到这一点的示例:

```js
<p-table
    [value]="products"
    [globalFilterFields]="['name', 'price']"
    #dt
>
   <ng-template pTemplate="caption">
      <div class="flex">
         <button pButton label="Clear" class="p-button-outlined"
            icon="pi pi-filter-slash" (click)="dt.clear()"
         ></button>
         <span class="p-input-icon-left ml-auto">
            <i class="pi pi-search"></i>
            <input pInputText type="text" placeholder="Search keyword"
               (input)="dt.filterGlobal($event.target.value, 'contains')"            />
         </span>
      </div>
   </ng-template>
   <ng-template pTemplate="header">
      <tr>
         <th>
            <input pInputText type="text" placeholder="Search by name"
               (input)="dt.filter($event.target.value, 'name', 'contains')"
          />
         </th>
         <th>
            <input pInputText type="text" placeholder="Search by price"
               (input)="dt.filter($event.target.value, 'price', 'equals')"
            />
         </th>
      </tr>
   </ng-template>
   <ng-template pTemplate="body" let-product>
      <tr>
         <td>{{ product.name }}</td>
         <td>{{ product.price }}</td>
      </tr>
   </ng-template>
   <ng-template pTemplate="emptymessage">
      <tr>
         <td colspan="7">No products found.</td>
      </tr>
   </ng-template>
</p-table>
```

之前的代码是一个具有列和全局过滤功能的 PrimeNG 表格的示例。让我们分解一下:

+   `<p-table>` 是一个 PrimeNG 表格组件,用于显示表格数据。

+   `[value]="products"` 是一个属性绑定,将 `products` 属性绑定到 `<p-table>` 组件的 `value` 属性上。这意味着组件代码中的 `products` 变量是表格的数据源。

+   `[globalFilterFields]="['name', 'price']"` 属性绑定将字段名数组(`name` 和 `price`)绑定到 `<p-table>` 组件的 `globalFilterFields` 属性上。`globalFilterFields` 属性允许你指定要应用全局过滤器的字段/列。在这种情况下,全局过滤器将应用于表格的 `name` 和 `price` 字段。

+   `#dt` 是一个名为 `dt` 的模板引用变量,它被分配给 `<p-table>` 组件。模板引用变量允许你在模板代码中引用组件,并在需要时访问其属性和方法。

+   `<ng-template pTemplate="caption">` 模板包含一个清除所有过滤器的按钮和一个用于全局搜索的输入字段。当按钮被点击时,会调用 `dt.clear()` 方法来清除所有过滤器。当用户在全局搜索输入字段中输入时,会调用 `dt.filterGlobal()` 方法来根据输入值过滤所有行。

+   `<ng-template pTemplate="header">` 模板包含每个列的输入字段。当用户在这些输入字段中输入时,会调用 `dt.filter()` 方法来根据对应列的输入值过滤行。

+   `<ng-template pTemplate="body" let-product>` 模板定义了如何显示每一行数据。`let-product` 语法用于创建一个局部模板变量 `product`,它为每一行持有当前的产品对象。

+   当没有行可供显示时,会显示 `<ng-template pTemplate="emptymessage">` 模板,这可能是因为 `products` 数组为空,或者没有行匹配当前的过滤器。

让我们看看最终的结果:

![图 6.8 – 带过滤的表格](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_08.jpg)

图 6.8 – 带过滤的表格

例如,用户可以通过在列标题或全局搜索输入字段中的输入字段中输入来过滤行。用户还可以通过点击 **清除** 按钮来清除所有过滤器。

## 带有分页器的表格

当处理大量数据集时,一次性显示所有数据可能会让人感到不知所措且不切实际。**分页**是解决这个问题的常见方法,它允许用户一次查看数据的一个子集。PrimeNG 的表格组件内置了分页器,这使得实现此功能变得非常简单。

要在 PrimeNG 表格中启用分页,你只需将 `paginator` 属性设置为 `true` 并定义 `rows` 属性来指定每页的行数:

```js
<p-table
   [value]="products"
   [paginator]="true"
   [rowsPerPageOptions]="[5,10,20]"
   [rows]="10"
   >
   <!-- Table content -->
</p-table>
```

让我们逐一查看每个属性及其用途:

+   `[value]="products"`:这会将表格的值绑定到一个名为 `products` 的变量上。

+   `[paginator]="true"`:这启用了表格的分页功能。

+   `[rowsPerPageOptions]="[5,10,20]"`: 这定义了每页显示行数的选项。在这种情况下,选项设置为`5`、`10`和`20`。用户可以选择这些选项之一来控制表格中显示的行数。

+   `[rows]="10"`: 这设置了每页显示的行数。在这种情况下,它设置为`10`。这意味着表格的每一页将显示最多 10 行。

让我们看看启用了分页器功能的表格:

![图 6.9 – 带有分页器的表格](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_09.jpg)

图 6.9 – 带有分页器的表格

此截图展示了选择要显示的行数的选项,默认值设置为`10`。此外,分页器功能允许在不同页面之间无缝导航。

总结来说,PrimeNG 的表格组件提供了一个强大且灵活的解决方案,用于显示表格数据。具有排序、过滤和分页等功能,为开发者提供了以有组织和交互方式展示数据的工具。

注意

要了解更多关于表格组件以及探索其他功能,例如可伸缩的列、冻结列和可滚动的表格,您可以访问 PrimeNG 文档网站 [`primeng.org/table`](https://primeng.org/table)。

接下来,我们将探讨网络应用程序中数据展示的另一个重要方面:PrimeNG 的列表组件,它提供了一套多样化的工具,用于显示和与数据列表互动。

# 与列表组件一起工作

PrimeNG 提供各种列表组件,以满足不同的需求和用例。这些组件旨在将原始数据转换为有意义的列表,为用户提供直观的方式与信息互动。

PrimeNG 的列表组件包括几个关键元素,可用于创建多样化的列表展示:

+   `DataView`: 此元素提供网格和列表视图来显示数据,并具有排序和过滤选项

+   `OrderList`: 此元素允许用户在列表中对项目进行重新排序

+   `PickList`: 此元素允许用户从列表中选择项目并将它们移动到另一个列表

这些组件不仅关乎数据的显示;它们还提供排序、过滤和选择等功能,增强了用户与数据互动的能力。让我们更详细地看看每一个。

## DataView

`DataView`是一个多功能的组件,特别适用于您需要以结构化方式展示大量数据时。它提供了各种功能,如分页、排序和可定制的模板,使其成为构建数据驱动应用程序的绝佳选择。

在以下场景中,您可能需要考虑使用 PrimeNG 的`DataView`组件:

+   `DataView`组件可以帮助您高效地实现这一点

+   `DataView`组件可以用于以网格或列表格式显示结果,并提供分页和排序选项

+   `DataView`可用于在各个小部件中一致地展示数据,提供连贯的用户体验

在以下子节中,让我们考虑一个示例,其中我们有一个产品集合,我们希望使用`DataView`组件来显示。每个产品都有名称、类别和价格等属性。我们将以列表布局展示产品,使用户能够浏览它们并将项目添加到购物车中。

### 创建基本数据视图

在使用`DataView`组件之前,确保在您的应用程序中安装了 PrimeFlex 非常重要。这是必要的,因为`DataView`依赖于 PrimeFlex 提供的`Grid`功能来有效地组织和展示数据。有关安装 PrimeFlex 的详细说明,请参阅本章前面的部分。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { DataViewModule } from 'primeng/dataview'
```

一旦我们安装并导入了依赖项,我们就可以在我们的 Angular 模板中使用`DataView`组件。以下是我们如何以列表布局显示产品的示例:

```js
<p-dataView [value]="products">
   <ng-template pTemplate="list" let-products>
      <div class="col-12" *ngFor="let product of products">
         <div class="flex flex-column xl:flex-row xl:align-items-start p-4 gap-4">
            <div class="flex flex-column sm:flex-row justify-content-between align-items-center xl:align-items-start flex-1 gap-4">
                  <div class="flex flex-column align-items-center sm:align-items-start gap-3">
                        <div class="text-2xl font-bold text-900">{{ product.name }}</div>
                        <div class="flex align-items-center gap-3">
                              <span class="flex align-items-center gap-2">
                                    <i class="pi pi-tag"></i>
                                    <span class="font-semibold">{{ product.category }}</span>
                              </span>
                        </div>
                  </div>
                  <div class="flex sm:flex-column align-items-center sm:align-items-end gap-3 sm:gap-2">
                        <span class="text-2xl font-semibold">{{'$'+ product.price }}</span>
                        <button pButton icon="pi pi-shopping-cart" class="md:align-self-end mb-2 p-button-rounded" ></button>
                  </div>
            </div>
            </div>
         </div>
   </ng-template>
</p-dataView>
```

以下是代码分解:

+   `<p-dataView>`:这是 PrimeNG 库中的 Angular 组件,用于在视图中显示数据。

+   `[value]="products"`:此属性绑定将组件代码中的`products`属性绑定到`<p-dataView>`组件的`value`属性。这意味着组件代码中的`products`变量是数据视图的数据源。

+   `<ng-template pTemplate="list" let-products>`:此模板渲染数据视图中的每个项目。`pTemplate`属性值为`"list"`表示此模板用于列表项。`let-products`属性声明了一个名为`products`的局部变量,它代表数据视图中的`products`数组。

因此,我们创建了一个包含三个项目的产品列表:

![图 6.10 – 基本数据视图](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_10.jpg)

图 6.10 – 基本数据视图

### 带分页的 DataView

如果您有大量产品并希望将它们显示在多个页面上,您可以在`DataView`组件中启用分页。以下是如何启用具有特定每页行数的分页的示例:

```js
<p-dataView
    [value]="products"
    [rows]="4"
    [paginator]="true"
>
      <!-- DataView template -->
</p-dataView>
```

在这里,我们将`rows`属性设置为`4`,表示我们希望每页显示四个产品。通过将`paginator`属性设置为`true`,`DataView`组件自动添加分页控件,使用户能够浏览页面。

### 带排序的 DataView

`DataView`组件还提供了内置的排序功能,允许用户根据特定标准对数据进行排序。以下是如何启用排序并添加下拉菜单以选择排序选项的示例:

```js
<p-dataView
   [value]="products"
   [rows]="4"
   [paginator]="true"
   [sortField]="sortField"
   [sortOrder]="sortOrder"
   >
   <ng-template pTemplate="header">
      <div class="flex flex-column md:flex-row md:justify-content-between">
         <p-dropdown
            [options]="sortOptions"
            [(ngModel)]="sortKey"
placeholder="Sort By Price"
            (onChange)="onSortChange($event)"
            styleClass="mb-2 md:mb-0"
         />
      </div>
   </ng-template>
   <!-- DataView template -->
</p-dataView>
...
sortOptions = [
   { label: 'Price High to Low', value: '!price' },
   { label: 'Price Low to High', value: 'price' }
];
sortOrder!: number;
sortField!: string;
onSortChange(event: HTMLInputElement) {
   const value = event.value;
   if (value.indexOf('!') === 0) {
      this.sortOrder = -1;
      this.sortField = value.substring(1, value.length);
   } else {
      this.sortOrder = 1;
      this.sortField = value;
   }
}
```

让我们分解代码以了解其功能:

+   `[value]="products"`:此绑定将父组件中的`products`数组绑定到`DataView`组件,该组件将成为显示项的数据源。

+   `[rows]="4"`: 如果启用了分页,这将设置每页显示的行数。

+   `[paginator]="true"`: 这启用了 `DataView` 组件的分页功能。

+   `[sortField]="sortField"` 和 `[sortOrder]="sortOrder"`: 这些属性用于控制数据的排序;`sortField` 指定按哪个字段排序数据,而 `sortOrder` 指定顺序(升序或降序)。

+   `<p-dropdown ... />`: 这行代码创建了一个下拉列表,其选项由 `sortOptions` 数组定义。当用户选择一个选项时,会调用 `onSortChange` 方法。

+   `sortOptions`: 这个数组定义了下拉列表中可用的排序选项。`value` 字段包含一个表示排序标准的字符串。如果值以 `!` 开头,表示降序。

+   `onSortChange(event: HTMLInputElement)`: 当用户从下拉列表中选择排序选项时,会调用此方法。它解析所选值并相应地设置 `sortOrder` 和 `sortField` 属性。

通过使用这些属性和模板,您可以在 `DataView` 组件中启用排序,并为用户提供无缝的排序体验。在下面的屏幕截图中,您可以看到我们构建了一个每次显示四个项目的产品列表,并按 **价格从低到高** 排序:

![图 6.11 – 带排序和分页的数据视图](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_11.jpg)

图 6.11 – 带排序和分页的数据视图

到目前为止,我们已经见证了 PrimeNG 的 `DataView` 在提供可适应数据展示方面的出色能力。现在,让我们探索 PrimeNG 的 `OrderList`,这是一个专门组件,为列表管理带来了独特的触感。

## 排序列表

在与项目集合交互时,有时这些项目的顺序很重要。这就是 PrimeNG 的 `OrderList` 发挥作用的地方。`OrderList` 组件是一个强大的工具,它允许您在 Angular 应用程序中管理和排序项目集合。它就像有一个个人助理来帮助您组织数据。

`OrderList` 在您需要为用户提供手动排序项目列表的方式时特别有用,例如在待办事项列表中优先排序任务、重新排列书籍章节、管理播放列表、分类产品或按相册排序照片。

### 创建基本排序列表

让我们通过一个示例来深入了解 `OrderList` 组件如何在应用程序中使用。我们将创建一个用户可以重新排序的产品列表。

`OrderList` 组件利用 Angular CDK 的 `DragDropModule` 来处理拖放操作。确保安装了 `@angular/cdk` 包非常重要。如果没有,我们需要通过以下命令将其添加到我们的 `package.json` 文件中:

```js
npm install @angular/cdk
```

注意

`@angular/cdk`(组件开发工具包或 CDK)库由 Angular 团队提供,提供了一组可重用的组件、指令和实用函数,以简化 Angular 应用程序的开发。CDK 提供了一套工具和构建块,有助于创建一致、可访问和响应式的用户界面。你可以在 [`material.angular.io/cdk`](https://material.angular.io/cdk) 上了解更多关于 CDK 的信息。

然后,我们还需要从 PrimeNG 中导入 `OrderListModule` 到我们的组件:

```js
import { OrderListModule } from 'primeng/orderlist'
```

一旦我们安装并导入了依赖项,我们就可以在我们的 Angular 模板中使用 `OrderList` 组件。以下是我们如何显示顺序列表中的产品的示例:

```js
<p-orderList
         [value]="products"
         [listStyle]="{ 'max-height': '30rem' }"
         header="Products"
>
   <ng-template let-product pTemplate="item">
      <div class="flex flex-column xl:flex-row xl:align-items-start gap-4">
         <div
            class="flex flex-column sm:flex-row justify-content-between align-items-center xl:align-items-start flex-1 gap-4"
         >
            <div
               class="flex flex-column align-items-center sm:align-items-start gap-3"
            >
               <div class="text-xl font-bold text-700">{{ product.name }}</div>
               <div class="flex align-items-center gap-3">
                  <span class="flex align-items-center gap-2">
                     <i class="pi pi-tag"></i>
                     <span class="font-semibold">{{ product.category }}</span>
                  </span>
               </div>
            </div>
            <div
               class="flex sm:flex-column align-items-center sm:align-items-end gap-3 sm:gap-2"
            >
               <span class="text-xl font-semibold">{{
                  '$' + product.price
               }}</span>
               <button
                  pButton
                  icon="pi pi-shopping-cart"
                  class="md:align-self-end mb-2 p-button-rounded"
               ></button>
            </div>
         </div>
      </div>
   </ng-template>
</p-orderList>
```

下面是示例代码的分解:

+   `[value]="products"`:这将我们的产品列表绑定到 `OrderList` 的 `value` 属性。

+   `header="Products"`:这用于设置列表的标题。

+   `[listStyle]="{ 'max-height': '30rem' }"`:这设置了列表的最大高度。

+   `<ng-template let-product pTemplate="item">`:这自定义了列表中每个项目的显示方式。我们可以使用 `let-product` 语法访问当前产品。

现在,在下面的屏幕截图中,你会注意到产品列表被标记为**Products**。此外,你还有能力通过位于左侧面板中的箭头按钮选择产品并在列表中重新定位它:

![图 6.12 – 基本顺序列表](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_12.jpg)

图 6.12 – 基本顺序列表

### 带有过滤的 OrderList

`OrderList` 组件也支持过滤和搜索功能,使用户能够快速在源列表中找到特定的项目。要启用过滤,我们可以使用 `filterBy` 和 `filterPlaceholder` 属性:

```js
<p-orderList
   [value]="products"
   [listStyle]="{ 'max-height': '30rem' }"
   header="Products"
   filterBy="name"
   filterPlaceholder="Filter by name"
>
   <!-- Item template here... -->
</p-orderList>
```

在此示例中,我们将 `filterBy` 属性设置为 `name` 以根据产品名称过滤产品,而 `filterPlaceholder` 属性指定了搜索输入字段的占位符文本。以下是最终结果:

![图 6.13 – 带有过滤的顺序列表](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_13.jpg)

图 6.13 – 带有过滤的顺序列表

如屏幕截图所示,我们可以在搜索框中输入产品名称或产品名称来过滤和搜索**产品 22**。

### 带有拖放的 OrderList

`OrderList` 组件允许用户使用拖放手势重新排序项目。默认情况下,拖放功能是禁用的。要启用它,我们可以使用 `[``dragdrop]` 属性:

```js
<p-orderList
   [value]="products"
   [listStyle]="{ 'max-height': '30rem' }"
   header="Products"
   [dragdrop]="true"
>
<!-- Item template here... -->
</p-orderList>
```

启用拖放允许用户点击并保持对项目的选择,将其拖动到目标列表中的新位置,然后放下以重新排序项目。此功能为用户提供了一种直观的交互方式,根据他们的偏好重新排列项目。

我们发现了 PrimeNG 的 `OrderList` 的动态功能,增强了我们应用程序中的列表交互。现在,让我们将注意力转向 PrimeNG 的 `PickList`,这是一个双列表界面,承诺提供更多的交互性和多功能性。

## PickList

PrimeNG 的`PickList`组件是一个强大的工具,它允许开发者在不同的列表之间重新排序项目时创建交互式和可定制的列表。它提供了一个用户友好的界面,用于以拖放的方式管理和操作数据。无论您需要实现多选功能、构建任务管理系统还是创建自定义表单构建器,`PickList`组件都提供了灵活性和功能性,以满足您的需求。

在以下场景中这可能是有益的:

+   `PickList`组件可用于在源列表中显示可用任务列表,并在目标列表中显示分配的任务列表。用户可以根据其分配状态轻松地在列表之间移动任务。

+   `PickList` 组件可以帮助组织表单元素。您可以在源列表中显示所有可用字段,并将选定的字段移动到目标列表中,以定义表单结构。这提供了一种方便的方式,根据用户偏好动态生成表单。

+   `PickList` 组件可以通过允许用户将选定的产品移动到目标列表(代表他们的定制目录)来简化此过程。

通过利用`PickList`组件,您可以增强用户体验,改进数据组织,并在您的应用程序中实现高效的数据操作。

让我们考虑一个例子,其中我们有一个产品列表,并希望允许用户使用 PrimeNG 的`PickList`组件将选定的产品添加到他们的购物车中。要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { PickListModule } from 'primeng/picklist'
```

一旦我们安装并导入了依赖项,我们就可以在我们的 Angular 模板中使用`PickList`组件。以下是我们如何以选择列表布局显示产品的示例:

```js
<p-pickList
   [source]="products"
   [target]="selectedProducts"
   sourceHeader="Available Products"
   targetHeader="Selected Products"
   [dragdrop]="true"
   [responsive]="true"
   [sourceStyle]="{ height: '30rem' }"
   [targetStyle]="{ height: '30rem' }"
   breakpoint="1400px"
>
   <ng-template let-product pTemplate="item">
       <!-- Item template here... -->
   </ng-template>
</p-pickList>
```

在此示例中,我们将`products`和`selectedProducts`数组分别传递给`PickList`组件的`source`和`target`属性。我们还使用`sourceHeader`和`targetHeader`属性为源列表和目标列表提供标签。

让我们看看最终结果:

![图 6.14 – 示例选择列表](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_14.jpg)

图 6.14 – 示例选择列表

当您运行应用程序时,您应该看到两个列表并排渲染。左侧列表表示可用产品,右侧列表表示选定的产品。用户可以通过使用`PickList`组件提供的拖放功能从源列表中选择产品并将它们移动到目标列表。

总结来说,PrimeNG 的列表组件是一套强大的工具,用于在 Angular 应用程序中显示和交互数据列表。凭借其灵活性、定制选项以及与 Angular 的集成,它们为开发者提供了创建引人入胜且功能齐全的列表展示所需的一切。无论是需要简单的列表还是更复杂的交互,如重新排序和选择,PrimeNG 的列表组件都提供了一种强大的解决方案,可以增强任何应用程序的用户界面。

在下一节中,我们将介绍本章的最后一个组件:PrimeNG 卡片组件。

# 与卡片组件一起工作

PrimeNG 卡片是一个容器组件,它提供了一个灵活且可扩展的内容容器,具有多种变体和选项。它本质上是一个包含内容和关于单一主题的操作的矩形框。将其视为一个将特定信息组合在一起的小型容器。

卡片极其灵活,可以在各种场景中使用:

+   **产品列表**:卡片可以用于在线商店中显示产品,其中每个卡片代表一个带有图片、标题、价格和描述的产品

+   **用户资料**:在社交媒体平台上,卡片可以代表用户资料,展示图片、姓名和其他个人详情

+   **博客文章**:对于博客列表,每个卡片可能显示一篇文章的特色图片、标题和简短摘要

使用卡片可以将内容变得更加易于消化,将信息分解成易于一眼看懂的小块。

让我们深入一个实际例子。假设你正在构建一个在线商店,并希望使用 PrimeNG 的 `Card` 组件来显示产品列表。要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { CardModule } from 'primeng/card'
```

之后,我们添加 PrimeNG 的 `Card` 组件来创建产品列表:

```js
<p-card
     ngFor="let product of products"
   [header]="product.name"
   [style]="{ width: '300px' }"
>
   <img
      src="img/placeholder.png"
      alt="{{ product.name }}"
      style="width:100%"
   />
   <div class="flex flex-column">
      <p>{{ product.description }}</p>
      <h3>\${{ product.price }}</h3>
      <button pButton type="button" label="Add to Cart"></button>
   </div>
</p-card>
```

这段代码展示了在 Angular 模板中使用 PrimeNG `Card` 组件的用法。让我们分析代码并解释每个部分:

+   `<p-card>`:这是 PrimeNG `Card` 组件的开始,它代表一个单独的卡片元素

+   `*ngFor="let product of products"`:这是一个名为 `ngFor` 的 Angular 结构性指令,用于遍历产品数组并为每个产品生成一张卡片

+   `[header]="product.name"`:这会将 `product.name` 属性绑定到 `Card` 组件的标题输入,从而设置卡片的标题文本

+   `[style]="{ width: '300px' }"`:这会将内联 CSS 样式绑定到 `Card` 组件的样式输入,将卡片的宽度设置为 300 像素

让我们看看最终结果:

![图 6.15 – 卡片示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_06_15.jpg)

图 6.15 – 卡片示例

在这个例子中,我们正在遍历产品列表并为每个产品创建一张卡片。每个卡片显示产品的图片、描述、价格以及一个“**添加到** **购物车**”按钮。

我们最近学习了 PrimeNG 卡片组件的灵活性和设计特性,这大大提高了我们创建视觉上吸引人的用户界面的能力。现在,对我们来说,回顾和巩固我们的理解非常重要。

# 摘要

在本章中,我们深入探讨了 PrimeNG 的数据显示组件。我们首先理解了这些组件在将原始数据转换为有意义的、用户友好的信息中的作用,并看到了 PrimeNG 丰富的数据显示组件,如表格、列表和卡片,如何被用来在 Angular 应用程序中有效地展示数据。

本章获得的知识至关重要,因为有效的数据展示是构建用户友好应用的关键方面。通过使用 PrimeNG 的数据显示组件,我们可以创建不仅外观美观,而且提供无缝用户体验的应用程序。

但我们的旅程并未结束。展望下一章,我们将深入了解其他 PrimeNG 数据显示组件。我们将学习如何使用`Tree`、`Scroller`、`Timeline`和`VirtualScroller`等组件在我们的 Angular 应用程序中展示数据。

因此,让我们保持势头。我们在理解和使用 PrimeNG 的数据显示组件方面取得了巨大进步。现在,是时候迈出下一步,探索数据操作组件了。让我们继续进入下一章!




# 第七章:使用 Tree、TreeTable 和 Timeline 组件

应用程序中的数据展示不仅仅是表格、列表和卡片。有时,数据的本质需要更分层或按时间顺序的结构。这就是 `Tree`、`TreeTable` 和 `Timeline` 等组件发挥作用的地方。在本章中,我们将深入探讨这些专用组件,每个组件都提供独特的显示和交互方式,以使用 PrimeNG 在 Angular 应用程序中处理数据。

主要目标是熟悉专门针对特定数据展示需求的 PrimeNG 组件。随着我们进入本章,我们将掌握处理各种数据展示挑战的知识,以及如何在 Angular 应用程序中有效地实现它们。掌握这些组件意味着准备好提供增强用户体验和数据清晰度的解决方案。

在本章中,我们将涵盖以下主题:

+   使用 Tree 组件

+   使用 TreeTable 组件

+   使用 Timeline 组件

# 技术要求

本章包含各种 PrimeNG 显示组件的代码示例。您可以在以下 GitHub 仓库的 `chapter-07` 文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-07`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-07)。

# 使用 Tree 组件

PrimeNG **Tree** 组件是一个强大的工具,用于以有组织和视觉吸引力的方式显示分层数据,提供一种树状结构,其中数据可以呈现为节点,并且可以展开或折叠以显示或隐藏子节点。

在本节中,我们将探讨使用 PrimeNG `Tree` 组件的各个方面,包括其目的、用法和关键特性。该组件还提供了一系列功能,包括节点展开和折叠、选择模式、数据的懒加载、拖放功能以及上下文菜单支持,我们也将对其进行探讨。

## 何时使用 PrimeNG Tree 组件

PrimeNG 的 `Tree` 组件在需要以分层方式组织和展示数据的情况下特别有用。它通常用于处理类别、文件目录、组织结构以及其他表现出父子关系的任何数据。

例如,让我们考虑一个产品目录应用程序。目录可能包含类别、子类别和按层次结构组织的产品。在这种情况下,PrimeNG `Tree` 组件可以用来直观地表示产品目录,使用户能够浏览类别和子类别,并选择特定的产品。

## 创建基本的 Tree 组件

为了更好地理解如何使用 PrimeNG `Tree` 组件,让我们看一下前一个部分提到的产品目录示例。假设我们为我们的产品有以下层次结构:

```js
- Electronics
   - Computers
      - MacBook Air
      - Smartphone
   - Phones
      - iPhone
      - Samsung
      - Google Pixel
- Home & Garden
   - Outdoor
   - Furniture
   - Office
- Books & Media
   - Books
   - Movies & TV
```

使用 PrimeNG `Tree` 组件,我们可以表示这种层次结构。

要开始使用,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { TreeModule } from 'primeng/tree'
```

一旦安装并导入了依赖项,我们就可以在 Angular 模板中使用 `Tree` 组件。以下是如何以树布局显示产品的示例:

```js
// tree.component.ts
import { TreeNode } from 'primeng/api'
// Html / template
<p-tree [value]="products" />
// TypeScript
products: TreeNode[] = [
   {
      "key": "0",
      "label": "Electronics",
      "data": "Category Level",
      "icon": "pi pi-tablet",
      "children": [
         {
            "key": "0-0",
            "label": "Computers",
            "data": "SubCategory Level",
            "icon": "pi pi-desktop",
            "children": [
               {
                  "key": "0-0-0",
                  "label": "MacBook Air",
                  "data": "Product Level",
                  "icon": "pi pi-apple"
               },
               ...
            ]
         },
         ...
      ]
   }
   ...
]
```

让我们分解一下代码:

+   `<p-tree [value]="products" />`:这表示了 PrimeNG `Tree` 组件的使用。它将 `Tree` 组件的 `value` 属性绑定到 `products` 变量。

+   `Products: TreeNode[]`:这定义了 `products` 变量为 `TreeNode` 对象的数组。`TreeNode` 是 PrimeNG 定义的一种类型,用于在树组件中表示节点。每个 `TreeNode` 对象具有以下属性:

    +   `key`:节点的唯一标识符。

    +   `label`:将显示在节点上的文本。

    +   `data`:与节点相关联的附加数据。在这个例子中,它表示节点的级别(类别级别、子类别级别或产品级别)。

    +   `icon`:与节点相关联的可选图标。

    +   `children`:子节点的数组。此属性允许树具有嵌套结构。

这是一个表示商店的简化树结构的示例。以下是结果:

![图 7.1 – 基本树](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_01.jpg)

图 7.1 – 基本树

## 展开和折叠节点

PrimeNG `Tree` 组件的一个基本特性是能够展开和折叠节点。这使用户能够根据兴趣在树中导航,显示或隐藏子节点。

默认情况下,PrimeNG `Tree` 组件以所有节点折叠的状态开始。用户可以通过点击其展开图标来展开一个节点,也可以通过点击其折叠图标来折叠一个节点。

除了用户交互之外,您还可以通过操作节点的状态来以编程方式控制节点的展开和折叠。例如,您可以使用 `expandAll()` 和 `collapseAll()` 方法分别以编程方式展开所有节点或折叠所有节点。

下面是一个演示展开和折叠功能的示例:

```js
// tree.component.ts
<div class="grid gap-2 p-2 mb-2">
   <button
      pButton
      type="button"
      label="Expand all"
      (click)="expandAll()"
   ></button>
   <button
      pButton
      type="button"
      label="Collapse all"
      (click)="collapseAll()"
   ></button>
</div>
<p-tree [value]="products" />
...
expandAll() {
   this.products.forEach((node) => {
      this.expandRecursive(node, true)
   })
}
collapseAll() {
   this.products.forEach((node) => {
      this.expandRecursive(node, false)
   })
}
private expandRecursive(node: TreeNode, isExpand: boolean) {
   node.expanded = isExpand
   if (node.children) {
      node.children.forEach((childNode) => {
         this.expandRecursive(childNode, isExpand)
      })
   }
}
```

在这里,我们向 UI 中添加了两个按钮用于展开和折叠节点。`expandAll()` 和 `collapseAll()` 方法分别绑定到相应按钮的点击事件。当用户点击**展开全部**按钮时,树中的所有节点都将展开,而当点击**折叠全部**按钮时,所有节点都将折叠。

`expandRecursive(...)` 方法是一个私有方法,它递归地展开或折叠树中的节点。它接受一个 `TreeNode` 对象(`node`)和一个布尔值(`isExpand`)作为参数,并将 `node` 的 `expanded` 属性设置为 `isExpand` 的值,从而展开或折叠节点。

点击 **展开** **全部** 按钮的结果如下:

![图 7.2 – 带展开和折叠能力的树](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_02.jpg)

图 7.2 – 带展开和折叠能力的树

## 使用节点选择事件

PrimeNG `Tree` 组件提供了一系列事件和方法,允许你与组件交互并响应用户操作。这些事件和方法使你能够执行诸如处理节点选择、捕获节点展开和折叠事件以及动态加载数据等任务。

在启用节点选择事件之前,我们需要向组件添加 `selectionMode`。有四种选择类型:

+   `<p-tree [value]="products"` `selectionMode="single" />`。

+   `<p-tree [value]="products"` `selectionMode="single"` `[metaKeySelection]="true"/>`。

+   `<p-tree [value]="products"` `selectionMode="multiple" />`。

+   `<p-tree [value]="products"` `selectionMode="checkbox" />`。

在添加节点选择类型后,PrimeNG `Tree` 组件在节点被选择或取消选择时发出事件。你可以使用这些事件根据用户的节点选择执行操作。

要捕获节点选择事件,你可以使用 `(onNodeSelect)` 和 `(onNodeUnselect)` 事件绑定。以下是一个 `single` 选择示例:

```js
// tree.component.ts
<p-tree
      [value]="products"
selectionMode="single"
      (onNodeSelect)="onNodeSelected($event)"
      (onNodeUnselect)="onNodeUnselected($event)"
/>
...
onNodeSelected(event: TreeNodeSelectEvent) {
   console.log(event)
}
onNodeUnselected(event: TreeNodeSelectEvent) {
   console.log(event)
}
```

在代码中,`(onNodeSelect)` 事件绑定到 `onNodeSelected()` 方法,而 `(onNodeUnselect)` 事件绑定到 `onNodeUnselected()` 方法。当节点被选择或取消选择时,这些方法将被调用。

让我们看看选择 **Electronics** 节点时的一个示例事件:

![图 7.3 – 带选择事件的树](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_03.jpg)

图 7.3 – 带选择事件的树

因此,我们可以看到在选择具有以下详细信息的 `node` 数据后:

+   `expanded: true`:这表示当前节点处于展开状态

+   `parent: undefined`:这表明当前节点没有父节点

+   `data, icon, key, label, children`:这显示了当前节点的现有值

## 使用节点展开和折叠事件

当节点展开或折叠时,PrimeNG `Tree` 组件会发出事件。你可以利用这些事件在用户展开或折叠节点时执行操作。

要捕获节点展开或折叠事件,你可以使用 `(onNodeExpand)` 和 `(onNodeCollapse)` 事件绑定。以下是一个示例:

```js
// tree.component.ts
<p-tree
   [value]="productsWithEvents"
(onNodeExpand)="onNodeExpanded($event)"
   (onNodeCollapse)="onNodeCollapsed($event)"
/>
...
onNodeExpanded(event: TreeNodeSelectEvent) {
   console.log(event)
}
onNodeCollapsed(event: TreeNodeSelectEvent) {
   console.log(event)
}
```

在这里,`(onNodeExpand)` 事件绑定到 `onNodeExpanded()` 方法,而 `(onNodeCollapse)` 事件绑定到 `onNodeCollapsed()` 方法。这些方法将在节点展开或折叠时分别被触发。展开或折叠后的事件值与选择或取消选择节点时的值相同。

## 懒加载的工作方式

PrimeNG 的 `Tree` 组件支持数据的懒加载,这在处理大数据集时非常有用。您不必一次性加载所有节点,而可以在用户展开节点时动态加载节点。

要启用懒加载,您需要使用 `[loading]` 属性和 `(onNodeExpand)` 事件。`[loading]` 属性允许您指示树是否正在加载数据,而 `(onNodeExpand)` 事件在节点展开时触发,允许您动态加载子节点。

这里有一个演示懒加载的例子:

```js
<p-tree
   [loading]="loading"
   [value]="products"
   (onNodeExpand)="loadChildNodes($event)"
/>
...
loading = false
loadChildNodes(event: TreeNodeSelectEvent) {
   if (event.node) {
      this.loading = true
      // example of retrieving child nodes data
      event.node.children = this.nodeService.getChildNodes(event.node)
      this.loading = false
   }
}
```

在前面的代码中,`[loading]` 属性绑定到 Angular 组件中的 `loading` 变量,该变量指示树是否正在加载数据。`(onNodeExpand)` 事件绑定到 `loadChildNodes()` 方法,该方法负责加载展开节点的子节点。以下是结果:

![图 7.4 – 带加载的树](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_04.jpg)

图 7.4 – 带加载的树

注意

应在 Angular 组件中实现 `loadChildNodes()` 方法,以便根据展开的节点动态获取子节点。

在本节中,我们讨论了何时使用 PrimeNG `Tree` 组件,例如在需要按层次组织数据的情况下,如产品目录或文件系统,以及它的各种功能。在下一节中,我们将深入了解 `TreeTable` 组件。

# 与 TreeTable 组件一起工作

当涉及到以表格格式展示层次数据结构时,PrimeNG 的 **TreeTable** 成为了一个强大的工具,它结合了两个世界的最佳之处:树的嵌套结构和表格的有序列。让我们开始一段旅程,更好地理解这个组件,并看看它如何提升我们的数据展示水平。

## 何时使用 PrimeNG TreeTable 组件

PrimeNG 的 `TreeTable` 组件在需要表示具有层次结构的数据的场景中特别有用。它提供了一种直观且用户友好的方式来导航和交互层次数据,使其非常适合处理组织结构、文件系统、产品类别以及其他表现出父子关系的任何数据的应用程序。

`TreeTable` 组件更适合以表格格式展示具有高级交互选项(如排序、过滤和分页)的层次数据,而 `Tree` 组件最适合以紧凑且可折叠的树形结构显示和导航层次数据。

通过利用 `TreeTable` 组件,你可以以结构化和组织的方式展示复杂层次数据,使用户能够展开和折叠节点,执行排序和过滤操作,并以无缝的方式与数据交互。

## 创建一个基本的 TreeTable 组件

假设你有一个产品列表,这些产品被归类在不同的产品系列下。每个产品都有价格、可用性和评分等详细信息。`TreeTable` 组件可以是一个很好的选择来表示这些数据。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { TreeTableModule } from 'primeng/treetable'
```

一旦安装并导入了依赖项,我们就可以在我们的 Angular 模板中使用 `TreeTable` 组件。以下是如何在 `TreeTable` 布局中显示产品的示例:

```js
<p-treeTable
   [value]="products"
   [scrollable]="true"
   [tableStyle]="{ 'min-width': '50rem' }"
>
   <ng-template pTemplate="header">
      <tr>
         <th>Name</th>
         <th>Price (USD)</th>
         <th>Rating</th>
      </tr>
   </ng-template>
<ng-template pTemplate="body" let-rowNode let-product="rowData">
      <tr [ttRow]="rowNode">
         <td>
            <p-treeTableToggler [rowNode]="rowNode" />
            {{ product.name }}
         </td>
         <td>{{ product?.price | currency }}</td>
         <td>{{ product?.rating }}</td>
      </tr>
   </ng-template>
</p-treeTable>
...
products: TreeTableNode[] = [
   {
      "key": "0",
      "data": {
         "name": "Electronics"
      },
      "children": [
         {
            "key": "0-0",
            "data": {
               "name": "Computers"
            },
            "children": [
               {
                  "key": "0-0-0",
                  "data": {
                     "id": 1,
                     "name": "MacBook Air",
                     "price": 999,
                     "description": "Light and portable MacBook",
                     "quantity": 100,
                     "rating": 4,
                     "category": "Computers"
                  }
               },
               ...
            ]
         }
      ]
   }
   ...
]
```

让我们分解一下代码:

+   `<p-treeTable>`: 这是 PrimeNG 库中的 Angular 组件,用于以树状结构显示层次表格数据。

+   `[value]="products"`: 此属性绑定将 `products` 属性设置为 `TreeTable` 组件的数据源。组件代码中的 `products` 变量包含一个 `TreeTableNode` 对象数组,代表层次数据结构。

+   `[scrollable]="true"`: 此属性绑定启用 `TreeTable` 组件内的滚动,如果内容超出可用空间。

+   `[tableStyle]="{ 'min-width': '50rem' }"`: 此属性绑定将内联 CSS 样式对象应用于 `<p-treeTable>` 组件的 `tableStyle` 属性。在这种情况下,它将 `TreeTable` 组件的最小宽度设置为 `50rem`。

+   `<ng-template pTemplate="header">`: 此元素定义了一个用于渲染 `TreeTable` 组件的标题行的模板。

+   `<ng-template pTemplate="body" let-rowNode let-product="rowData">`: 此元素定义了一个用于渲染 `TreeTable` 组件的体(行)的模板。此模板还具有两个其他属性:

    +   `let-rowNode`: 这声明了一个名为 `rowNode` 的局部变量,它代表正在渲染的当前行节点

    +   `let-product="rowData"`: 这声明了一个名为 `product` 的局部变量,它代表与当前行关联的数据

+   `<p-treeTableToggler [rowNode]="rowNode" />`: 此组件用于在 `TreeTable` 组件中显示用于展开和折叠子节点的切换按钮。

+   `products: TreeTableNode[]`: 这定义了 `products` 数组作为 `TreeTable` 组件的数据源。`products` 数组由 `TreeTableNode` 对象组成,代表层次数据结构。每个节点都有一个包含产品信息的数据属性,例如 `name`、`price`、`rating` 和 `category`。示例显示了一个嵌套结构,其中包含一个父节点 `Electronics` 和一个子节点 `Computers`,它进一步包含一个具有相应属性的 `MacBook Air` 子节点。

这是一个表示商店的简化 `TreeTable` 结构的示例。以下是结果:

![图 7.5 – 基本的 TreeTable 结构](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_05.jpg)

图 7.5 – 基本的 TreeTable 结构

## 使用动态列

`TreeTable` 组件允许我们根据提供的数据或配置动态创建列。而不是在模板中手动定义每个列,我们可以绑定来自组件的列集合,并让`TreeTable`组件动态生成必要的列。这种方法不仅高效,而且提供了高度的灵活性。

让我们考虑一个电子商务应用的例子,该应用以`TreeTable`格式显示产品。该应用需要处理不同的产品类别,每个类别都有自己的属性集。目标是根据所选的产品类别动态渲染列。以下是代码:

```js
<div class="grid gap-4 ml-0 mb-4">
   <button
      (click)="updateColumns('RATING')"
      pButton
      label="Rating"
   ></button>
   <button
      (click)="updateColumns('QUANTITY')"
      pButton
      label="Quantity"
   ></button>
</div>
<p-treeTable
   [value]="products"
   [columns]="cols"
   [scrollable]="true"
   [tableStyle]="{ 'min-width': '50rem' }"
>
   <ng-template pTemplate="header" let-columns>
      <tr>
         <th *ngFor="let col of columns">
            {{ col.header }}
         </th>
      </tr>
   </ng-template>
   <ng-template
      pTemplate="body"
      let-rowNode
      let-product="rowData"
      let-columns="columns"
   >
      <tr>
         <td *ngFor="let col of columns; let i = index">
            <p-treeTableToggler [rowNode]="rowNode" *ngIf="i === 0" />
            {{ product[col.field] }}
         </td>
      </tr>
   </ng-template>
</p-treeTable>
...
cols = [
   { field: 'name', header: 'Name' },
   { field: 'price', header: 'Price' },
   { field: 'rating', header: 'Rating' },
]
updateColumns(option: string) {
   switch (option) {
      case 'RATING':
         this.cols = [
            { field: 'name', header: 'Name' },
            { field: 'price', header: 'Price' },
            { field: 'rating', header: 'Rating' },
         ]
         break
      case 'QUANTITY':
         this.cols = [
            { field: 'name', header: 'Name' },
            { field: 'price', header: 'Price' },
            { field: 'quantity', header: 'Quantity' },
         ]
         break
      default:
         break
   }
}
```

让我们分解一下代码:

+   `<button (click)="updateColumns(...)" >`:这是一个按钮元素,当点击时触发`updateColumns()`方法。

+   `<th *ngFor="let col of columns">{{ col.header }}</th>`:这一行使用`*ngFor`指令遍历`columns`数组并为每个列生成一个`<th>`元素。列的`header`属性作为`header`单元格的内容显示。

+   `<td *ngFor="let col of columns; let i = index">`:这一行使用`*ngFor`指令遍历`columns`数组并为每个列生成一个`<td>`元素。

+   `<p-treeTableToggler [rowNode]="rowNode" *ngIf="i === 0" />`:这个`<p-treeTableToggler>`组件用于在`TreeTable`组件中显示一个切换按钮,用于展开和折叠子节点。

使用这种实现,`TreeTable`将根据所选类型显示适当的产品数据列。让我们看看结果:

![图 7.6 – 带有动态列的 TreeTable](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_06.jpg)

图 7.6 – 带有动态列的 TreeTable

如果用户从**评分**类型切换到**数量**类型,表格将自动更新以显示**数量**列而不是**评分**,如图 7.5 所示。

## 启用 TreeTable 分页器

`TreeTable` 组件允许我们将大量数据分解成更小、更易于管理的块或页面。而不是一次性显示数百或数千行,分页器允许用户逐页浏览数据。它提供了移动到下一页或上一页、跳转到开始或结束以及选择页面大小的控件。

我们可以通过添加`paginator`和`rows`属性轻松地在`TreeTable`组件中启用分页器,如下面的代码所示:

```js
<p-treeTable [value]="products" [paginator]="true" [rows]="2">
      <!-- Column templates and other TreeTable configurations go here -->
</p-treeTable>
```

在这个例子中,我们通过设置`[paginator]="true"`并指定每页显示两行来启用分页,`[rows]="2"`。

现在,`TreeTable` 组件将显示分页控件,用户可以逐页浏览产品数据:

![图 7.7 – 带有分页器的 TreeTable](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_07.jpg)

图 7.7 – 带分页器的树表

## 相关事件和方法

`TreeTable` 组件提供了各种事件和方法,您可以利用这些来增强您应用程序的功能性和交互性。以下是一些常用的事件和方法:

+   `onNodeExpand`: 当节点被展开时触发此事件

+   `onNodeCollapse`: 当节点折叠时触发此事件

+   `onNodeSelect`: 当选择一个节点时触发此事件

+   `onNodeUnselect`: 当节点被取消选择时触发此事件

您可以使用这些事件和方法根据您应用程序的需求自定义`TreeTable`组件的行为。例如,您可以通过监听`onNodeCollapse`事件在节点折叠时执行某些操作,例如删除详细信息或触发附加操作:

```js
<p-treeTable
[value]="files"
(onNodeCollapse)="handleNodeCollapse($event)"
>
      <!-- Column templates and other TreeTable configurations -->
</p-treeTable>
...
handleNodeCollapse(event: TreeTableNodeCollapseEvent) {
   const collapsedNodeData = event.node.data
   // Handle actions when a node is collapsed
}
```

让我们分解一下代码:

+   `(onNodeCollapse)="handleNodeCollapse($event)"`: 这将 `handleNodeCollapse` 方法绑定到 `TreeTable` 组件中节点折叠时发生的事件。该方法将使用 `event` 对象作为参数被调用。

+   `handleNodeCollapse(event: TreeTableNodeCollapseEvent) { ... }`: 这接受一个 `TreeTableNodeCollapseEvent` 类型的 `event` 对象作为参数。这个 `event` 对象包含了有关折叠节点的信息,您可以使用这些信息来处理事件。

在我们探索 PrimeNG 的 `TreeTable` 组件过程中,我们已经看到了它在以结构化和用户友好的方式呈现层次化数据方面的强大功能。从动态列到高效的分页,`TreeTable` 组件为各种数据表示挑战提供了一个强大的解决方案。现在,让我们将焦点转移到 PrimeNG 的 `Timeline` 组件上,这是一个能够以时间顺序美妙的可视化数据的工具。

# 使用时间线组件

PrimeNG 的 `Timeline` 组件是 Angular 应用程序中由 PrimeNG 库提供的强大组件,它允许您按时间顺序可视化一系列连锁事件。时间轴提供了一种用户友好且交互式的显示事件的方式,使用户更容易理解活动或随时间变化的顺序。

PrimeNG 的 `Timeline` 组件旨在以线性方式呈现事件,使用户能够浏览不同的阶段或里程碑。时间线中的每个事件都由一个标记表示,该标记可以自定义以显示相关信息,例如状态、日期或任何其他有意义的资料。

时序图提供了各种功能来增强用户体验——它支持垂直和水平布局,提供了时序图方向的灵活性,以及对齐选项来定位时序图条相对于内容的位置。

## 当使用 PrimeNG 时序图组件时

PrimeNG 的 `Timeline` 组件可以在各种应用程序和场景中使用。以下是一些 `Timeline` 组件可能有益的示例:

+   **项目管理**:使用时序图来展示项目里程碑,例如项目启动、需求收集、开发阶段和项目完成。这有助于利益相关者和团队成员可视化项目的进度并理解关键事件的顺序。

+   **订单跟踪**:如果您有一个电子商务应用程序,可以利用时序图来显示订单处理的各个阶段,如订单提交、支付验证、订单履行和交付。这为顾客提供了一个清晰的订单进度概览。

+   **历史事件**:时序图也适合展示历史事件或重大成就。例如,您可以使用它来展示科学发现的时序、重大历史事件或特定行业的演变。

+   **产品更新**:如果您维护产品路线图或想展示软件产品的发布历史,时序图可以是一种有效的方式来展示随时间推移的不同版本、更新和新功能。

每当需要按事件发生的顺序表示一系列事件时,PrimeNG 的 `Timeline` 组件是一个极佳的选择。

## 创建基本时序图

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { TimelineModule } from 'primeng/timeline'
```

一旦我们安装并导入了依赖项,我们就可以在 Angular 模板中使用 `Timeline` 组件。以下是一个如何在时序图布局中显示订单状态的示例:

```js
<p-timeline [value]="orderStatuses">
   <ng-template pTemplate="content" let-order>
      <span [class]="order.icon"></span>
      {{ order.title }}
   </ng-template>
</p-timeline>
...
orderStatuses = [
   {
      title: 'Order Placed',
      content: 'Your order has been received and is being processed.',
      icon: 'pi pi-shopping-cart',
   },
   {
      title: 'Order Confirmed',
      content:
         'Your payment has been confirmed and the order is now being prepared.',
      icon: 'pi pi-check-square',
   },
   {
      title: 'In Warehouse',
      content: 'Your product is in the warehouse, awaiting dispatch.',
      icon: 'pi pi-globe',
   },
   {
      title: 'Shipped',
      content:
         'Your order has been shipped and is on its way to the delivery address.',
      icon: 'pi pi-truck',
   },
   {
      title: 'Out for Delivery',
      content: 'The product is out for delivery and will reach you soon.',
      icon: 'pi pi-map-marker',
   },
   {
      title: 'Delivered',
      content: 'Your product has been successfully delivered. Enjoy!',
      icon: 'pi pi-check-circle',
   },
]
```

让我们分解代码并理解其功能:

+   `<p-timeline [value]="orderStatuses">`:这代表了 PrimeNG 的 `Timeline` 组件的使用。它将 `Timeline` 组件的 `value` 属性绑定到 `orderStatuses` 变量。

+   `<ng-template pTemplate="content" let-order>`:这定义了在时序图中渲染每个状态内容的模板。

+   `orderStatuses`:这代表订单的不同阶段或状态。数组中的每个元素对应将在时序图中显示的特定事件。

总体而言,代码展示了如何使用 PrimeNG 的 `Timeline` 组件来显示订单状态的时序图。时序图通过 `orderStatuses` 数组中的状态数据填充,每个状态都使用包含图标和标题的模板进行渲染。这允许以直观且信息丰富的方式按时间顺序展示事件。

下面是代码的结果:

![图 7.8 – 基本时序图](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_08.jpg)

图 7.8 – 基本时间线

## 时间线对齐

PrimeNG 的**时间线对齐**功能允许您控制内容相对于时间线的位置。您可以将内容对齐到时间线的左侧、右侧、顶部、底部或交替侧。这种灵活性允许您根据您的设计偏好或应用需求自定义时间线的外观和布局。

让我们想象我们正在构建一个时间线来展示产品的订单状态。您可以通过选择`align`属性的不同的值来自定义对齐方式,例如`left`、`right`或`alternate`,具体取决于您的特定设计要求。让我们更新我们现有的订单状态时间线的对齐方式:

```js
<p-timeline [value]="orderStatuses" align="alternate">
   // timeline content
</p-timeline>
```

在代码片段中,我们使用了`align`属性并设置为`alternate`值,以使每个活动的内容在时间线线的交替两侧对齐。这种布局创建了一个有趣的视觉模式,活动出现在时间线的左右两侧:

![图 7.9 – 时间线对齐](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_09.jpg)

图 7.9 – 时间线对齐

## 时间线水平布局

除了对齐选项之外,PrimeNG 的`Timeline`组件还提供了一个水平布局选项。水平布局旨在从左到右线性地展示事件或里程碑,这在您想要展示跨越广阔区域的时间线时尤其有用,例如项目时间线或历史事件序列。

要使用具有水平布局的`Timeline`,您可以在`Timeline`组件中将`layout`属性设置为`horizontal`。让我们看看一个例子:

```js
<p-timeline [value]="orderStatuses" layout="horizontal">
   // timeline content
</p-timeline>
```

在代码片段中,我们将`layout`属性设置为`horizontal`,表示我们希望以水平方式显示时间线事件。以下是结果:

![图 7.10 – 水平时间线](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_07_10.jpg)

图 7.10 – 水平时间线

通过我们对 PrimeNG 的`Timeline`组件的探索,我们看到了它如何提供一种动态的方式来视觉表示事件、里程碑或过程。它的灵活性,从基本事件表示到对齐或布局等特性,确保我们可以根据我们的需求定制叙述。在我们结束这一节的时候,让我们花点时间回顾我们的旅程并总结关键要点。

# 摘要

在本章中,我们探讨了使用 PrimeNG 的`Tree`、`TreeTable`和`Timeline`组件表示层次和时序数据的复杂性。这些强大的工具在以视觉吸引力和用户友好的方式展示结构化数据方面发挥着关键作用,无论是显示项目的层次结构还是可视化随时间推移的事件序列。

我们从揭示`Tree`组件的能力和了解其在有效表示父子关系数据中的重要性开始。`TreeTable`组件在此基础上扩展了这一概念,通过提供表格和层次数据展示的无缝集成。此外,`Timeline`组件展示了其在按时间顺序可视化序列、里程碑或事件方面的能力,为我们提供了清晰和连贯地呈现叙事或流程的灵活性。

通过掌握这些组件,我们为自己配备了有效以直观方式呈现复杂数据结构的必要工具。这不仅提升了用户体验,还确保了我们的应用既实用又美观。

随着我们为下一次旅程做好准备,我们将深入探讨另一组 PrimeNG 组件,这些组件将进一步提升我们应用的用户交互性和功能性。准备好在下一章探索导航和布局组件。




# 第八章:与导航和布局组件一起工作

在 Web 应用程序中导航应该是一个无缝的体验。我们如何构建内容、引导用户以及响应他们的交互可以显著影响他们的整体体验。本章深入探讨了 PrimeNG 的导航和布局组件,旨在帮助我们为 Angular 应用程序构建直观且用户友好的界面。

在这次探索中,我们将揭示 PrimeNG 导航组件的潜力,了解它们如何被用来引导用户通过我们的应用程序。从菜单到面包屑,从标签页到手风琴,我们将学习如何构建内容结构,创建导航路径,并设计适应不同屏幕尺寸的响应式布局。

本章的整体目标是赋予你利用 PrimeNG 的导航和布局组件的有效知识和技术。到本章结束时,你将能够创建无缝的导航体验,以结构化的方式组织内容,并确保适应各种设备的响应式布局。你还将深入了解如何处理导航事件并将它们集成到应用程序的功能中。

在本章中,我们将涵盖以下主题:

+   介绍导航和布局组件

+   与菜单一起工作

+   介绍 PrimeNG 面板

# 技术要求

本章包含 PrimeNG 显示组件的各种代码示例。你可以在以下 GitHub 仓库的`chapter-08`文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-08`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-08)

# 介绍导航和布局组件

在 Web 开发领域,创建直观且用户友好的界面对于为用户提供无缝体验至关重要。导航和布局组件在实现这一目标中发挥着关键作用。这些组件作为组织内容、引导用户通过应用程序的不同部分以及确保响应式设计的构建块。

## 导航和布局组件是什么?

**导航组件**为用户提供了一种在应用程序的各个功能、部分和内容之间导航的方式。它们提供了直观且易于访问的方法来访问不同的功能,从而提高了应用程序的整体可用性。导航组件的例子包括菜单、面包屑、标签页和工具栏。

另一方面,**布局组件**负责在应用程序中构建和组织内容的展示。它们确保信息以清晰和视觉上吸引人的方式显示。布局组件为创建能够适应不同屏幕尺寸和设备的响应式和自适应设计提供了基础。

## 打造直观的导航和布局体验

创建直观的导航系统就像设计一个城市的道路网络。它应该是逻辑的,易于遵循,并满足用户的需求。以下是一些可以帮助您实现这一目标的要点:

+   *以用户为中心的设计*:始终以用户为中心进行设计。了解他们的需求、习惯和期望。与用户直觉产生共鸣的导航系统总是更有效。

+   *一致性是关键*:无论是导航按钮的位置还是下拉菜单的样式,在整个应用程序中保持一致性有助于用户建立熟悉感。

+   *反馈*:当用户与导航元素互动时提供反馈。无论是按钮在悬停时改变颜色,还是菜单打开时的微妙动画,这些小交互都可以增强用户体验。

+   *适应性*:确保您的导航和布局组件是响应式的,适应不同的屏幕尺寸和设备,无论在桌面、平板电脑还是手机上查看,都能提供无缝的体验。

## 创建导航和布局组件的最佳实践

在创建导航和布局组件时,以下是一些需要记住的额外最佳实践:

+   *简洁性*:过度复杂的导航会使用户困惑;追求清晰和易用性。

+   *模块化*:将复杂的导航和布局结构分解成更小、可重用的组件。这促进了代码的可重用性、可维护性和可扩展性。

+   *可访问性*:确保您的导航组件对所有人都是可访问的,包括有残疾的人。使用语义 HTML,为图像提供 alt 文本,并确保组件是键盘可导航的。

+   *测试*:测试您的导航组件。这可以通过可用性测试来完成,其中真实用户与您的应用程序互动。他们的反馈可以提供宝贵的见解,以改进您的组件。

请记住,这些最佳实践作为指南,根据您应用程序的具体需求和需求进行调整至关重要。现在,让我们继续探讨构建 Angular 应用程序中直观 UI 的另一个重要方面:PrimeNG 菜单。

# 与菜单一起工作

**菜单**是 UI 的一个基本元素,它提供了导航结构,并允许用户访问应用程序的各种功能和功能。在 PrimeNG 中,您可以找到各种菜单组件,可以轻松集成到您的项目中。在本节中,我们将探讨菜单是什么,讨论何时使用 PrimeNG 菜单,并提供在电子商务应用程序中使用 PrimeNG 菜单的示例。

## PrimeNG 菜单是什么?

菜单的复杂性和设计各不相同,从简单的基于文本的菜单到更复杂的具有子菜单和图标的分层菜单。PrimeNG 提供了一系列菜单组件,以满足不同的用例和设计需求。PrimeNG 提供的以下是一些流行的菜单组件:

+   `Menu`:`p-menu` 组件是一个多功能的菜单,支持多种模式,如弹出、滑动和覆盖。它可以作为一个独立的菜单或作为其他组件中的下拉菜单使用。

+   `Menubar`:`p-menubar` 组件代表一个水平菜单栏,通常用于顶级导航场景。它允许您创建一个干净且简洁的导航界面,特别适用于具有多个部分或模块的应用程序。

+   `MegaMenu`:`p-megaMenu` 组件是为更复杂的导航场景设计的,允许您创建带有图片、图标和子菜单的多列菜单。

+   `ContextMenu`:`p-contextMenu` 组件允许显示上下文特定的菜单,当用户在元素上右键点击或长按时出现。它对于提供上下文相关的操作或选项非常有用。

+   `TieredMenu`:`p-tieredMenu` 组件是一个支持多级嵌套菜单的层次菜单。它适合以结构化的方式组织选项。

+   `Breadcrumb`:`p-breadcrumb` 组件用于显示表示用户在应用程序层次结构中当前位置的面包屑导航路径。它通常放置在页面顶部附近,并提供链接到高级别部分或页面。

这些只是 PrimeNG 中可用的菜单组件的几个示例。根据您应用程序的需求,您可以选择最合适的菜单组件来创建无缝且直观的导航体验。

## 创建基本菜单

假设您想在电子商务应用程序主页的顶部添加一个水平菜单。此菜单包括电子产品、服装、家居和厨房以及运动和健身等类别,每个类别代表一个当用户悬停或点击时展开的下拉菜单。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import {MenuModule} from 'primeng/menu'
```

一旦安装并导入了依赖项,让我们看看如何设置 PrimeNG 菜单:

```js
import { MenuItem } from 'primeng/api'
<p-menu [model]="menuItems" />
...
menuItems: MenuItem[] = [
   {
      label: 'Electronics',
      items: [
         { label: 'Computers', routerLink: '/products/computers' },
         { label: 'Smartphones', routerLink: '/products/smartphones' },
         { label: 'Televisions', routerLink: '/products/televisions' },
      ],
   },
   ...
]
```

让我们分解一下代码:

+   `<p-menu [model]="menuItems" />`:这表示 PrimeNG `Menu` 组件的使用。它将 `Menu` 组件的 `model` 属性绑定到 `menuItems` 变量。

+   `menuItems: MenuItem[]`:这是一个来自 PrimeNG API 的 `MenuItem` 对象数组。每个 `MenuItem` 对象可以具有各种属性,例如 `label`、`items`、`routerLink`、`routerLinkActiveOptions` 等。

注意

菜单项中的`routerLink`属性是 Angular 的一个特性,它简化了应用程序中不同路由之间的导航。`routerLinkActiveOptions`属性提供了一种通过针对`p-menuitem-link-active`类来验证和样式化活动菜单的方法。默认情况下,活动类应用于与`MenuItem`对象中定义的`routerLink`值匹配的路由。如果您需要不同的配置,请参阅[`angular.io/api/router/IsActiveMatchOptions`](https://angular.io/api/router/IsActiveMatchOptions)上的文档。

在本例中,每个菜单项都由一个`label`属性表示,该属性指定要显示的文本。`items`属性表示每个类别的子菜单项。然后使用`routerLink`属性在点击项时导航到相应的产品列表页面。以下是结果:

![图 8.1 – 基本菜单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_01.jpg)

图 8.1 – 基本菜单

## 使用 Menubar

PrimeNG `Menubar`是一个为水平布局设计的动态导航组件。它不仅是一个链接列表,还提供了定制选项以满足不同的应用程序需求。您可以在`Menubar`中包含链接、按钮和其他 UI 组件,使其变得灵活且适应性强。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { MenubarModule } from 'primeng/menubar'
```

之后,我们可以通过使用 PrimeNG 的`p-menubar`组件来创建菜单栏:

```js
<p-menubar [model]="menuItems" />
```

让我们看看结果:

![图 8.2 – Menubar](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_02.jpg)

图 8.2 – Menubar

在本例中,`Menubar`包含两个主要部分:**电子产品**和**服装**。**电子产品**部分进一步细分为**电脑**、**智能手机**和**电视**等子类别。这种结构确保用户可以迅速导航到他们想要的产品类别或轻松访问他们的账户设置,而无需任何麻烦。

## 使用 MegaMenu

PrimeNG `MegaMenu`是一个下拉导航组件,在二维面板中显示子菜单,非常适合具有大量导航选项的情况,消除了滚动长列表的需求。它对于具有多个功能或类别的网站或应用程序特别有用,提供了有组织和易于访问的导航选项。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { MegaMenuModule } from 'primeng/megamenu'
```

之后,我们可以通过使用 PrimeNG 的`p-megaMenu`组件来创建菜单栏:

```js
<p-megaMenu [model]="megaMenuItems" />
...
megaMenuItems: MegaMenuItem[] = [
   {
      label: 'Categories',
      items: [
         [
            {
               label: 'Electronics',
               items: [
                  { label: 'Laptops', routerLink: '/electronics/laptops' },
                  { label: 'Cameras', routerLink: '/electronics/cameras' },
               ],
            },
            ...
         ],
      ],
   },
   ...
]
```

在本例中,我们定义了一个`MegaMenuItem`对象的数组来表示菜单结构。每个`MenuItem`对象都有一个`label`属性来指定菜单项显示的文本。此外,`items`属性用于在菜单项内嵌套子菜单。生成的`MegaMenu`组件将有一个顶级名为**类别**:

![图 8.3 – MegaMenu](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_03.jpg)

图 8.3 – MegaMenu

当用户将鼠标悬停在**类别**上时,将出现一个下拉子菜单,包含额外的子类别和产品。在这个例子中,在悬停之后,你可以看到**电子产品**、**时尚**和**家居与生活**子类别。在**电子产品**子类别中,还有进一步的选择:**笔记本电脑**和**相机**。

## 与 ContextMenu 一起工作

PrimeNG `ContextMenu`是一个上下文菜单组件,在用户执行特定操作(通常是右键点击)时在 UI 中弹出。它提供了一系列用户可以执行的操作,这些操作与用户交互的区域或元素上下文相关。与导航到顶级菜单或搜索选项不同,上下文菜单将操作直接带到用户面前。

当你想为用户提供快速操作而不使 UI 杂乱无章时,会使用上下文菜单。在以下情况下特别有益:

+   屏幕空间有限

+   你希望提供与特定元素或区域相关的选项

+   你的目标是减少用户需要点击的次数

例如,在文本编辑器中,右键点击可能会弹出剪切、复制或粘贴的选项。在图片查看器中,它可能提供缩放、保存或分享的选项。

让我们设想一个电子商务平台,用户可以在其中浏览产品。上下文菜单可以为这些产品提供快速操作。要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { MegaMenuModule } from 'primeng/megamenu'
```

之后,让我们为产品图片创建一个上下文菜单:

```js
<img
   #img
   src="img/placeholder.png"
   alt="Product"
   aria-haspopup="true"
   class="max-w-full"
/>
<p-contextMenu [target]="img" [model]="contextMenuItems" />
...
contextMenuItems: MenuItem[] = [
   {
      label: 'View Details',
      icon: 'pi pi-search',
      command: (event) => this.viewProduct(event.item),
   },
   {
      label: 'Add to Cart',
      icon: 'pi pi-shopping-cart',
      command: (event) => this.addToCart(event.item),
   },
   {
      label: 'Add to Wishlist',
      icon: 'pi pi-heart',
      id: 'wishlist',
      command: (event) => this.addToWishlist(event.item),
   },
]
viewProduct(item: MenuItem) {
   // Logic to view product details
}
addToCart(item: MenuItem) {
   // Logic to add product to cart
}
addToWishlist(item: MenuItem) {
   // Logic to add product to wishlist
}
```

让我们分解一下代码:

+   `<img #img .../>`:这是一个模板引用变量,用于在 Angular 组件中引用`<img>`元素

+   `<p-contextMenu [target]="img" [model]="contextMenuItems" />`:这部分代码定义了 PrimeNG 的`ContextMenu`组件并配置了其属性:

    +   `[target]="img"`:这会将`ContextMenu`组件的目标属性绑定到`img`模板引用变量。这意味着当用户右键点击引用的`<img>`元素时,`ContextMenu`组件将被触发。

    +   `[model]="contextMenuItems"`:这会将`ContextMenu`的`model`属性绑定到`contextMenuItems`数组。

    +   `contextMenuItems: MenuItem[]`:这是一个`MenuItem`对象的数组。每个对象代表上下文菜单中的一个菜单项。它具有如`label`(显示在菜单项上的文本)、`icon`(与菜单项关联的图标)和`command`(当选择菜单项时要执行的功能)等属性。在这种情况下,`command`属性被设置为调用 Angular 组件中的特定方法(`viewProduct()`、`addToCart()`和`addToWishlist()`)。

让我们看看结果:

![图 8.4 – ContextMenu 示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_04.jpg)

图 8.4 – ContextMenu 示例

在这个设置中,当用户在产品上右键点击时,他们会看到查看详情、添加到购物车或添加到愿望单的选项。这增强了购物体验,使操作迅速而直接。

## 与`TieredMenu`一起工作

PrimeNG 的`TieredMenu`是一个多级菜单系统,允许选项以分层结构组织。与扁平列表不同,您会得到一个级联菜单,其中选项可以有子选项,这些子选项还可以有自己的子选项,依此类推。这种分层结构在视觉上直观,使用户能够轻松地浏览类别和子类别。

PrimeNG 的`TieredMenu`在各种需要分层导航菜单的场景中都是一个很好的选择。以下是一些您可以利用`TieredMenu`功能的情况:

+   *复杂的应用程序菜单*:当您有一个包含大量菜单项和子菜单的应用程序时,`TieredMenu`简化了菜单结构和管理的组织。它允许您创建一个逻辑上的菜单层次结构,使用户能够更容易地浏览应用程序。

+   *电子商务网站*:`TieredMenu`在电子商务网站上特别有用,因为它们通常有广泛的产品类别和子类别。通过使用`TieredMenu`,您可以创建一个用户友好的导航系统,使购物者能够轻松浏览不同的产品类别和子类别。

+   *管理仪表板*:管理仪表板通常有多个部分和子部分,每个部分都需要自己的菜单集。`TieredMenu`提供了一个干净且组织良好的方式来表示这些菜单,使管理员能够轻松访问各种功能和设置。

+   *多级下拉菜单*:如果您需要实现多级下拉菜单,`TieredMenu`简化了这一过程。它处理了管理嵌套菜单的复杂性,并确保在不同层次之间的平滑过渡。

为了说明 PrimeNG `TieredMenu`在电子商务环境中的应用,让我们考虑一个场景,其中我们有一个在线商店销售电子产品。我们希望创建一个导航菜单,允许用户浏览不同的产品类别和子类别。

要开始使用,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { TieredMenuModule } from 'primeng/tieredmenu'
```

之后,我们可以通过添加以下代码来启用 PrimeNG 的`TieredMenu`:

```js
<p-tieredMenu [model]="tieredMenus" />
...
tieredMenus: MenuItem[] = [
   {
      label: 'Electronics',
      icon: 'pi pi-tablet',
      items: [
         {
            label: 'Computers',
            icon: 'pi pi-desktop',
            items: [
               { label: 'MacBook Air', icon: 'pi pi-apple' },
               { label: 'Ultrabooks', icon: 'pi pi-desktop' },
               { label: 'Mobile Workstations', icon: 'pi pi-mobile' },
            ],
         },
         ...
      ],
   },
]
```

让我们分解一下代码:

+   `<p-tieredMenu [model]="tieredMenus" />`:这代表 PrimeNG 的`TieredMenu`组件。`model`属性将组件类中的`tieredMenus`属性绑定到`TieredMenu`组件。

+   `tieredMenus: MenuItem[]`:这是一个来自 PrimeNG API 的`MenuItem`对象数组。每个`MenuItem`对象可以具有各种属性,例如`label`、`icon`、`items`、`routerLink`等。

让我们看看结果:

![图 8.5 – 分层菜单](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_05.jpg)

图 8.5 – 分层菜单

在这个例子中,我们有一个顶级菜单项 **Electronics**,它有几个子菜单:**Computers**、**Smartphones** 和 **Tablets**。每个子菜单都可以有自己的子菜单,从而创建一个层次结构。用户可以通过悬停在相应项上在菜单项和子菜单之间导航。

## 使用面包屑

PrimeNG `Breadcrumb` 是一个导航组件,它指示应用程序层次结构中的当前位置。它提供了一系列链接,每个链接代表层次结构中的一个级别,可以返回主页或主仪表板。

注意

应将面包屑用作辅助导航工具 – 它补充了主要导航,但不应该取代它。因此,请确保你的应用程序还有一个主要导航系统,例如菜单或侧边栏。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { BreadcrumbModule } from 'primeng/breadcrumb'
```

之后,我们可以通过添加以下代码来启用 PrimeNG `Breadcrumb`:

```js
<p-breadcrumb [model]="breadcrumbItems" />
...
breadcrumbItems: MenuItem[] = [
   { icon: 'pi pi-home', routerLink: '/' },
   { label: 'Electronics', routerLink: '/electronics' },
   {
      label: 'Computers',
      routerLink: '/electronics/computers',
   },
   { label: 'MacBook Air', routerLink: '/electronics/computers/macbook-air' },
]
```

让我们分解代码并解释每个部分:

+   `<p-breadcrumb [model]="breadcrumbItems" />`:这代表了 PrimeNG `Breadcrumb` 组件的实际用法。`[model]` 属性用于将 `breadcrumbItems` 数组绑定到组件的模型属性。

+   `breadcrumbItems: MenuItem[]`:这是一个包含 PrimeNG API 中的 `MenuItem` 对象的数组。每个 `MenuItem` 对象可以具有各种属性,如 `label`、`icon`、`items`、`routerLink` 等。

让我们看看结果:

![图 8.6 – 面包屑](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_06.jpg)

图 8.6 – 面包屑

在这个例子中,面包屑从 `Home` 链接开始,该链接将用户带回到网站的首页。随后的链接代表分类层次结构:**Electronics**、**Computers**,最后是当前页面,**MacBook Air**。

在深入研究了 PrimeNG 菜单的多种功能后,我们看到了它们如何简化导航并增强应用程序的用户体验。现在,让我们转向探索 PrimeNG 面板,这些是提供灵活方式在应用程序中组织和展示内容的必要组件。

# 介绍 PrimeNG 面板

`Panel`、`ScrollPanel`、`Accordion`、`Splitter`、`Fieldset` 和 `TabView`。

PrimeNG 面板可以在各种场景中使用,在这些场景中,内容组织和展示至关重要。以下是一些你可以从使用 PrimeNG 面板中受益的情况:

+   `Accordion` 在你有多个内容部分并且想要通过只允许用户展开他们感兴趣的部分来节省空间时非常有用。

+   `ScrollPanel` 组件使用户能够滚动内容,确保所有信息都保持可访问。

+   `Splitter` 组件在你需要创建可调整大小和可折叠的面板时非常有价值,使用户能够根据他们的偏好自定义布局。

+   `Fieldset`组件在您有一个包含相关字段且需要视觉上分组在一起以改善用户体验和理解表单时特别有用。

+   `TabView`组件在您有多个相关信息或功能集可以组织到标签中时非常有用,允许用户轻松切换。

要开始,让我们创建一个基本的 PrimeNG 面板。

## 创建基本面板

PrimeNG 面板本质上是一个围绕内容包装的容器,为内容提供结构化的外观。它包含一个可选的标题栏,可以用来为内部内容提供标题或上下文。面板的美丽之处在于其简洁性。它不对内容施加任何特定的样式或行为;相反,它提供了一个整洁的边界,使内容更加突出。

无论您是在设计仪表板、表单还是内容页面,面板都可以成为您为内容提供结构的首选组件。当您想要将相关的信息片段组合在一起时,它尤其有用,这使用户更容易处理和理解。

注意

虽然 PrimeNG `Panel`功能多样,但使用它时必须谨慎。过度使用可能会使页面显得杂乱。始终追求设计功能和平衡。

让我们深入电子商务领域,看看 PrimeNG `Panel`的实际应用。在电子商务网站上,`Panel`组件可以用来显示关于产品的详细信息。例如,当用户点击产品缩略图或名称时,面板可以滑动或展开以显示产品详情,包括图片、描述、规格和客户评价。这使用户能够在不离开当前页面的情况下探索产品信息。

要开始,我们需要从 PrimeNG 库中导入必要的模块:

```js
import { PanelModule } from 'primeng/panel'
```

一旦安装并导入依赖项,让我们看看如何设置 PrimeNG 面板:

```js
<p-panel header="Product Details" [toggleable]="true">
   <p>
      Experience the power of Laptop XYZ, featuring the latest processor and a
      sleek design.
   </p>
   <!-- other information -->
</p-panel>
```

让我们分解代码并解释每个部分:

+   `<p-panel>`:这是在 PrimeNG 中创建面板的主要组件标签。

+   `header="Product Details"`:此属性将面板的标题或头部设置为`"Product Details"`。

+   `-toggleable]="true"`:此属性使面板的内容可折叠。当设置为`true`时,允许用户点击面板标题来切换(显示/隐藏)面板内的内容。

让我们看看结果:

![图 8.7 – 基本面板](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_07.jpg)

图 8.7 – 基本面板

在这个例子中,您可以通过点击减号或加号图标来展开或折叠产品详情。

## 与 ScrollPanel 一起工作

PrimeNG `ScrollPanel` 允许你在应用程序内创建可滚动的区域,使用户能够查看超出可用空间的内容。`ScrollPanel` 组件提供类似原生的滚动体验,这意味着当用户在应用程序中导航时,滚动感觉平滑自然,就像在设备内置的应用程序中一样,该组件还支持水平和垂直滚动。

与在不同平台上可能不一致的默认浏览器滚动条不同,`ScrollPanel` 提供了一致的视觉和感觉。这不仅仅是关于美观;这是为了为用户提供更平滑、更直观的滚动体验。

让我们通过添加 `ScrollPanel` 来改进 *图 8.7* 中显示的基本面板。首先,从 PrimeNG 库中导入必要的模块:

```js
 import { ScrollPanelModule } from 'primeng/scrollpanel'
```

现在,让我们看看如何设置 PrimeNG `ScrollPanel`:

```js
<p-panel header="Product Details" [toggleable]="true">
   <p-scrollPanel [style]="{ width: '100%', height: '200px' }">
      <p>
         Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque ut...
      </p>
      <!-- Other content -->
   </p-scrollPanel>
</p-panel>
```

让我们分解代码并解释其功能:

+   `<p-panel ...>`:这代表 PrimeNG `Panel` 组件。

+   `<p-scrollPanel [style]="{ width: '100%', height: '200px' }">`:这将在面板内创建一个可滚动的视口来处理溢出的内容。它允许用户垂直滚动内容。`[style]` 属性用于定义可滚动区域的尺寸。在这种情况下,宽度设置为 `100%`,高度设置为 `200px`。

让我们看看结果:

![图 8.8 – 带滚动条的 ScrollPanel](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_08.jpg)

图 8.8 – 带滚动条的 ScrollPanel

在这个例子中,我们在一个具有固定宽度和高度的基面板内定义了 `ScrollPanel`。由于 **产品详情** 面板的内容相当大,滚动功能允许用户轻松查看内容,而不会失去整个页面的上下文。

## 与 Accordion 一起工作

PrimeNG `Accordion` 是一个 UI 组件,允许你以堆叠的方式显示内容。可以将其视为可展开/折叠的面板垂直堆栈。每个面板都有一个标题栏,当你点击它时,面板内的内容展开,显示更多详细信息。这种机制确保用户不会一次性被过多信息淹没。相反,他们可以选择深入哪些部分,使他们的浏览体验更加专注和整洁。

在空间有限且需要展示与相关详细内容关联的项目列表的情况下,Accordions 非常有用。它们特别适用于以下情况:

+   **组织相关内容**:将相关部分或主题分组,使用户能够快速导航到他们感兴趣的区域

+   **多步骤表单**:将长表单分解成可管理的部分,逐步引导用户

+   **常见问题解答**:展示一系列问题,并展开以显示答案

+   **产品规格**:在电子商务中,使用手风琴式布局显示产品的详细规格或特性,而不会让用户感到信息过载

想象一下,您正在开发一个销售电子小工具的电子商务平台。对于每个产品,都有大量信息需要传达规格、用户评价、保修详情等。使用 PrimeNG 手风琴,您可以整洁地组织这些信息。

要开始使用,请从 PrimeNG 库中导入必要的模块:

```js
import { AccordionModule } from 'primeng/accordion'
```

现在,我们可以看到如何设置 PrimeNG 手风琴:

```js
<p-accordion>
   <p-accordionTab header="Specifications">
      <ul>
         <li>Processor: XYZ</li>
         <li>Memory: 8GB RAM</li>
         <!-- ... other specs ... -->
      </ul>
   </p-accordionTab>
   <p-accordionTab header="User Reviews">
      <p>"This product is fantastic! Highly recommend." - Alex G</p>
      <!-- ... other reviews ... -->
   </p-accordionTab>
   <p-accordionTab header="Warranty">
      <p>This product comes with a 2-year warranty covering...</p>
   </p-accordionTab>
   <!-- ... other tabs ... -->
</p-accordion>
```

提供的代码演示了如何使用 PrimeNG `Accordion` 组件创建一组具有不同内容部分的标签。让我们分解它:

+   `<p-accordion>...</p-accordion>`:此代码片段将 `Accordion` 组件包裹在其子元素周围,表示手风琴部分的开始和结束。

+   `<p-accordionTab header="...">`:此元素代表手风琴的一个部分。header 属性定义了该部分的标题。

让我们看看结果:

![图 8.9 – 手风琴](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_09.jpg)

图 8.9 – 手风琴

我们创建了三个手风琴标签,因此当用户点击标题时,标签内的相关内容会展开。

## 与 Fieldset 一起工作

PrimeNG `Fieldset` 是一个容器组件,旨在将相关内容分组在一个视觉上独特的边界内。它与经典的 HTML `<fieldset>` 元素类似,但增加了额外的功能和样式。最显著的功能是其可选的可切换属性,允许用户展开或折叠内容,使其非常适合需要隐藏/显示功能的章节。

`Fieldset` 是您在以下情况下首选的组件:

+   **分组相关元素**:这在表单中尤其如此,您可能希望将相关的输入字段分组

+   **提高可读性**:通过分割内容,您使用户更容易处理信息

+   **交互式内容展示**:利用其可切换功能,您可以在不压倒主要内容的情况下展示可选或补充信息

在上一节中,我们使用 PrimeNG `Accordion`(如图 *图 8.9* 所示)显示了产品信息,如规格、保修详情和用户评价。除了使用 `Accordion`,我们还可以使用 `Fieldset` 来整洁地包装每个部分。

要开始使用,请从 PrimeNG 库中导入必要的模块:

```js
import { FieldsetModule } from 'primeng/fieldset'
```

然后,让我们设置 PrimeNG `Fieldset`:

```js
<p-fieldset legend="Specifications" [toggleable]="true" class="block mb-4">
   <ul>
      <li>Screen Size: 15.6 inches</li>
      <li>Processor: Intel i7</li>
      <li>RAM: 16GB</li>
      <!-- ... other specifications ... -->
   </ul>
</p-fieldset>
<p-fieldset legend="User Reviews" [toggleable]="true" class="block mb-4">
   <p>"Fantastic product with great performance!" - Yen N</p>
   <!-- ... other reviews ... -->
</p-fieldset>
<p-fieldset
legend="Warranty Details"
   [toggleable]="true"
   class="block mb-4"
>
   <p>
      This product comes with a 2-year warranty covering manufacturing
      defects.
      <!-- ... more warranty details ... -->
   </p>
</p-fieldset>
```

提供的代码演示了如何使用 PrimeNG `Fieldset` 组件及其特定属性。让我们分解代码并解释每个部分:

+   `<p-fieldset>`:代表 PrimeNG `Fieldset` 组件的 HTML 标签。

+   `legend="规格"`:`legend` 属性设置 `Fieldset` 组件的标题或描述。在这种情况下,`legend` 设置为 `规格`,表示 `Fieldset` 组件内的内容与产品的规格相关。

+   `[toggleable]="true"`:`[toggleable]`属性是一个属性绑定,它启用了`Fieldset`组件的可切换功能。当设置为`true`时,字段集中的内容可以被用户展开或折叠。这使用户能够根据需要隐藏或显示规格部分。

让我们看看结果:

![图 8.10 – 字段集](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_10.jpg)

图 8.10 – 字段集

在这个例子中,每个字段集都作为特定类型信息的容器。用户可以快速浏览图例(如**规格**或**用户评论**)并决定他们想要深入了解哪些部分,并根据需要展开它们。

## 使用 TabView

PrimeNG 的`TabView`是一个导航组件,允许您将内容分解成多个标签显示。每个标签都作为其独特内容的容器,确保信息是有组织的且易于访问。使用`TabView`,用户可以快速在不同部分之间切换,而不会感到不知所措。

`TabView`功能丰富,在各种场景中都有其位置:

+   **设置或配置页面**:这样可以将不同类别的设置分组到单独的标签下

+   **个人资料页面**:这可能是在您想要将用户信息、活动历史和设置分别放入不同的标签时

+   **电子商务中的产品描述**:这是当您想要将产品详情、客户评论和规格分开时

+   **文档**:这是当您想要将指南、API 参考和示例分开时

在前面的章节中,我们使用 PrimeNG 的`Accordion`(*图 8.9*)和`Fieldset`(*图 8.10*)显示了产品信息。现在,让我们尝试使用`TabView`。要开始,请从 PrimeNG 库中导入必要的模块:

```js
import { TabViewModule } from 'primeng/tabview'
```

现在让我们看看如何设置 PrimeNG 的`TabView`:

```js
<p-tabView>
   <p-tabPanel header="Warranty">
      <p>
         This product comes with a 2-year warranty covering manufacturing
         defects.
         <!-- ... more warranty details ... -->
      </p>
   </p-tabPanel>
   <p-tabPanel header="Specifications">
      <ul>
         <li>Screen Size: 15.6 inches</li>
         <li>Processor: Intel i7</li>
         <li>RAM: 16GB</li>
         <!-- ... other specifications ... -->
      </ul>
   </p-tabPanel>
   <p-tabPanel header="Reviews">
      <p>"Spectacular product! Highly recommended" - Aaron D</p>
      <!-- ... other reviews ... -->
   </p-tabPanel>
</p-tabView>
```

让我们分解代码并解释每个部分:

+   `<p-tabView>`:这个 PrimeNG 组件作为我们标签的容器。

+   `<p-tabPanel header="Warranty">`:这些组件代表`tabView`容器内产品信息的不同部分。`header`属性包含标签的值。

让我们看看结果:

![图 8.11 – TabView](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_11.jpg)

图 8.11 – TabView

通过使用 PrimeNG 的`TabView`,我们可以创建内容的不同部分,这些部分以标签的形式显示。用户可以通过点击标签标题(**保修**、**规格**或**评论**)在相应的部分之间切换,并访问相关信息。

## 使用 Splitter

PrimeNG 的`Splitter`是一个布局组件,允许用户通过拖动分隔线来调整其子元素的大小。将其想象为用户可以根据他们的查看偏好调整大小的可调整分区。它在需要提供两个或更多内容部分之间可调整比例的场景中特别有用,无论是水平还是垂直。

`Splitter`组件在各种场景中都非常出色:

+   当你有多个小部件或面板,并希望给用户提供调整它们大小的灵活性时,使用`Splitter`

+   当你可能在一侧有一个代码部分而在另一侧有一个预览时,可以使用`Splitter`组件

+   `Splitter`组件允许用户调整视图以并排比较两个图像

+   `Splitter`组件可用于需要提供可调整的多面板视图的应用程序

让我们考虑一个例子,其中 PrimeNG 的`Splitter`组件将两个图像并排比较。首先,从 PrimeNG 库中导入必要的模块:

```js
import { SplitterModule } from 'primeng/splitter'
```

然后让我们看看如何设置 PrimeNG 的`Splitter`组件:

```js
<p-splitter [style]="{ height: '300px' }" layout="horizontal">
   <ng-template pTemplate>
      <div class="col flex align-items-center justify-content-center">
         <img src="img/placeholder.png" alt="Image 1" />
      </div>
   </ng-template>
   <ng-template pTemplate>
      <div class="col flex align-items-center justify-content-center">
         <img src="img/placeholder.png" alt="Image 2" />
      </div>
   </ng-template>
</p-splitter>
```

让我们分解代码并解释每个部分:

+   `<p-splitter>`:这代表 PrimeNG 的`Splitter`组件。

+   `[style]="{ height: '300px' }"`:这用于将内联 CSS 样式应用到`Splitter`组件上。在这种情况下,`Splitter`组件的高度通过`height`属性设置为 300 px。

+   `layout="horizontal"`:`layout`属性定义了`Splitter`组件内面板的排列方向。在这个例子中,它被设置为`horizontal`,表示面板将以水平方式并排显示。

+   `<ng-template pTemplate>`:`pTemplate`指令是 PrimeNG 特有的,用于标记 Angular 模板为 PrimeNG 模板。

让我们看看结果:

![图 8.12 – Splitter](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_08_12.jpg)

图 8.12 – Splitter

在这里,我们在`Splitter`组件内部创建了两个面板,每个面板包含一个用于比较目的的图像。

在探索了 PrimeNG 面板的各种功能和能力之后,我们获得了宝贵的见解,了解了这个强大的组件如何增强 Angular 应用程序中内容的布局和展示。

# 摘要

在本章中,我们穿越了 PrimeNG 组件的广阔世界。它带我们经历了一次导航和布局组件的复杂旅程。这些元素是任何应用程序的骨架,决定了用户如何与内容互动,并确保无缝体验。

我们深入研究了从菜单和面板的各种组件。每个组件都服务于独特的目的,从组织内容到增强导航。到目前为止,你应该已经牢固地掌握了在应用程序中何时以及如何使用这些组件。这些组件将使我们能够以有意义的方式构建和展示内容,从而提高整体用户体验。此外,通过各种示例,我们看到了这些组件如何集成到实际应用中。这些实用见解旨在弥合理论知识与实际实施之间的差距。

随着我们过渡到下一章,我们将深入探讨使用主题定制 PrimeNG 组件的艺术。主题化是一个强大的工具,它允许你根据品牌指南或特定的设计偏好定制组件的外观和感觉。我们将探讨如何利用主题化的力量,使 PrimeNG 组件真正成为你自己的。所以,准备好开始一段丰富多彩的定制和设计之旅吧!

# 第三部分:高级技术和最佳实践

在本部分,你将深入了解与 PrimeNG 一起工作的高级技术和最佳实践。你将探索定制、优化、重用、国际化以及测试策略,以增强你的 PrimeNG 驱动的 Angular 应用程序。

在本部分的结尾,你将深入理解这些高级主题,并具备构建稳健、高效和用户友好的应用程序的宝贵技能。

本部分包含以下章节:

+   *第九章*, *使用主题定制 PrimeNG 组件*

+   *第十章*, *探索 Angular 应用程序的优化技术*

+   *第十一章*, *创建可重用和可扩展的组件*

+   *第十二章*, *处理国际化和本地化*

+   *第十三章*, *测试 PrimeNG 组件*




# 第九章:使用主题定制 PrimeNG 组件

每个应用程序都有自己的独特身份,其视觉吸引力在定义该身份方面发挥着重要作用。虽然功能至关重要,但应用程序的外观和感觉可以显著影响用户体验。PrimeNG 提供了一个强大的主题系统,允许您定制组件的外观,确保应用程序不仅运行良好,而且看起来也符合要求。

在本章中,您将探索使用主题在 Angular 应用程序中定制 PrimeNG 组件外观的过程。通过掌握这些技术,您将能够根据应用程序的独特品牌和设计要求定制 PrimeNG 组件的视觉表现。

我们还将深入探讨各种主题,如使用预构建主题、创建自定义主题、利用主题设计工具以及覆盖组件样式。您将发现 PrimeNG 主题的强大和灵活性,并学习如何实现统一且个性化的用户界面。

本章将涵盖以下主题:

+   介绍 PrimeNG 主题

+   使用预构建主题

+   创建您自己的自定义主题

+   覆盖组件样式和其他技巧

# 技术要求

本章包含有关 PrimeNG 主题的各种工作代码示例。您可以在以下 GitHub 仓库的`chapter-09`文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-09`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-09)。

# 介绍 PrimeNG 主题

**主题**在创建视觉吸引力和一致的用户界面中起着至关重要的作用,允许您根据应用程序的品牌和设计要求自定义组件的外观。PrimeNG 提供了一个全面的主题系统,使您能够创建个性化的统一用户界面。

虽然 PrimeNG 主题不仅仅是改变颜色或字体,但它涉及修改组件外观的各个方面,如颜色、字体、间距和其他设计元素。PrimeNG 提供了一系列的工具、资源和指南,以简化主题过程,使开发者能够创建独特且视觉上吸引人的用户界面。

根据应用程序的具体要求,PrimeNG 主题被用于各种场景。以下是一些 PrimeNG 主题证明非常有价值的使用案例:

+   **品牌和定制**:当您需要将 PrimeNG 组件的外观与您应用程序的品牌指南保持一致时,主题允许您创建一致且个性化的外观和感觉。

+   **应用特定设计**:在某些情况下,PrimeNG 组件的默认样式可能不符合您应用程序特定的设计要求。主题化使您能够修改组件的外观,以匹配您应用程序的视觉设计语言,确保用户界面的一致性和和谐。

+   **一致的样式**:在构建由多个开发者或团队参与的大型应用程序时,主题化确保了不同组件之间的视觉样式一致性。通过遵循统一的主题化方法,您可以在整个应用程序中保持一致的用户体验。

在掌握了 PrimeNG 主题的基础知识后,是时候深入了解实际方面了。快速启动主题之旅的最快方法之一是利用 PrimeNG 丰富的预建主题,我们将在下一节中探讨。

# 使用预建主题

PrimeNG **预建主题**是一系列预定义的样式表,定义了 PrimeNG 组件的视觉外观。这些主题基于流行的设计框架,如 Bootstrap 和 Material Design,并附带一系列颜色方案和变体。每个主题都为所有 PrimeNG 组件提供一致的样式,确保您的应用程序具有一致和精致的外观。

预建主题作为 `npm` 分发的一部分随 PrimeNG 一起提供,易于导入,并且可以通过几个简单的步骤应用到您的应用程序中。这些主题也高度可定制,允许您根据项目需求调整颜色、字体和其他视觉属性。

## 何时使用 PrimeNG 预建主题

虽然定制化提供了独特的身份,但在某些情况下,预建主题确实可以挽救局面:

+   **快速原型设计**:当您处于应用程序开发的初期阶段,需要快速设计来可视化功能时

+   **一致的设计语言**:对于需要跨多个应用程序或模块保持一致设计的项目

+   **减少开发时间**:当项目时间表紧张,没有时间进行广泛的设计迭代时

## PrimeNG 预建主题示例

在前面的章节中,我们有机会使用 PrimeNG 主题。在本章中,让我们回顾一下如何将 PrimeNG 主题集成到我们的 Angular 应用程序中。

要使用预建主题,只需将其导入到您的项目中即可。让我们看看如何:

1.  导航到 PrimeNG 内置主题([`primeng.org/theming#builtinthemes`](https://primeng.org/theming#builtinthemes)),并选择与您的项目氛围产生共鸣的主题。

1.  一旦您选择了主题,将其集成到您的项目中。例如,如果您选择了 `lara-light-blue` 主题,请将以下行添加到 `styles.scss` 或 `styles.css`:

    ```js
    //styles.scss
    @import 'primeng/resources/themes/lara-light-blue/theme.css';
    @import 'primeng/resources/primeng.css';
    ```

    这个导入语句确保了 `lara-light-blue` 主题中定义的样式被应用到你的应用程序中。一旦主题被导入,所有 PrimeNG 组件将自动采用主题定义的样式。

1.  接下来,你可以在应用程序中使用 PrimeNG 组件,例如 `p-button`、`p-card` 和 `p-table` 组件。这些组件将继承预构建主题定义的样式,使它们具有一致且视觉上吸引人的外观。

注意

你的应用程序可能有自定义字体或样式。始终确保主题的 CSS 在默认 PrimeNG CSS 之后加载,以确保主题样式具有优先权。

我们已经探讨了 PrimeNG 预构建主题的便利性,它们提供了大量视觉上吸引人的样式。现在,让我们深入探讨切换主题的话题,我们将学习如何无缝地在不同的 PrimeNG 主题之间切换,以满足我们应用程序的设计需求和偏好。

## 切换主题

PrimeNG 预构建主题的一个关键优势是你可以即时切换到不同的主题。这个特性允许你的应用程序用户选择他们偏好的主题,为他们提供个性化的可定制体验。

要切换主题,通常需要替换项目中主题 CSS 文件的引用。例如,如果你目前使用的是 `lara-blue-light` 主题,并希望切换到 `bootstrap4-light-purple` 主题,你将在 `index.html` 文件中替换 CSS 文件引用。

这里是如何一步步操作的指南:

1.  为了切换主题,你需要将所有主题准备在 `assets` 文件夹中。你可以在 `node_modules/primeng/resources/themes` 文件夹下找到内置 PrimeNG 主题的完整列表:

![图 9.1 – PrimeNG 内置主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_01.jpg)

图 9.1 – PrimeNG 内置主题

之后,你可以将你想要在应用程序中使用的主题复制到你的 `assets` 文件夹中:

![图 9.2 – 复制的内置主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_02.jpg)

图 9.2 – 复制的内置主题

在这个例子中,我将四个内置主题复制到我们的应用程序中:`bootstrap4-light-blue`、`lara-light-blue`、`md-dark-indigo` 和 `viva-dark`。

1.  将你的默认主题引用添加到 `index.html` 文件中。因此,你将不再在 `style.scss` 文件中设置默认值,而是在 `index.html` 文件中设置:

    ```js
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>chapter-09</title>
        <base href="/" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" type="image/x-icon" href="favicon.ico" />
        <link
          id="theme-link"
          rel="stylesheet"
          type="text/css"
          href="assets/lara-light-blue/theme.css"
        />
      </head>
      <body>
        <primengbook-root />
      </body>
    </html>
    ```

    在这段代码中,我们添加了对 `lara-light-blue` 主题的 `theme-link` ID 的引用。

1.  现在,是时候给我们的组件添加切换主题的功能了。以下是代码:

    ```js
    <div class="flex align-items-center" *ngFor="let theme of themes">
      <p-radioButton
        [name]="theme.name"
        [value]="theme.value"
        [(ngModel)]="selectedTheme"
        inputId="{{ theme.value }}"
        (onClick)="changeTheme()"
      />
      <label for="{{ theme.value }}" class="ml-2">{{ theme.name }}</label>
    </div>
    ...
    themes = [
      { name: 'Lara Light Blue', value: 'lara-light-blue' },
      { name: 'Bootstrap4 Light Purple', value: 'bootstrap4-light-purple' },
      { name: 'Viva Dark', value: 'viva-dark' },
      { name: 'Material Dark Indigo', value: 'md-dark-indigo' },
    ]
    selectedTheme = 'lara-light-blue'
    changeTheme() {
      const themeLink = document.getElementById('theme-link')
      themeLink?.setAttribute('href', `assets/${this.selectedTheme}/theme.css`)
    }
    ```

    让我们分解一下代码:

    +   `<div class="flex align-items-center" *ngFor="let theme of themes">`: 这段代码使用 `ngFor` 指令遍历 `themes` 数组。对于数组中的每个主题,它使用 `p-radioButton` 组件创建一个单选按钮输入。

    +   `themes = [...]`:这定义了 `themes` 数组,它包含代表不同主题的对象。每个主题对象都有一个 `name` 属性,表示主题的显示名称,以及一个 `value` 属性,表示主题的唯一标识符。

    +   `selectedTheme`:这表示最初选择的主题值。

    +   `changeTheme() {...}`:当点击单选按钮时调用此方法。它检索具有 `theme-link` ID 的 `link` 元素。之后,它将 `link` 元素的 `href` 属性更新为指向所选主题的 CSS 文件。

让我们看看结果:

![图 9.3 – 切换主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_03.jpg)

图 9.3 – 切换主题

我们已经设置了一个主题切换器,允许用户使用单选按钮选择主题。当点击单选按钮时,我们将能够切换到所需的主题。您可以看到,在左侧我们正在使用 `Lara Light Blue` 主题,而在右侧,我们正在使用 `Bootstrap4 Light Purple`,这将给我们的用户带来不同的体验。

通常,在 PrimeNG 中使用预构建主题为您提供了方便且高效的方式来设计应用程序。在下一节中,我们将探讨在 PrimeNG 中创建自定义主题的过程,让您能够打造真正个性化且独特的用户体验。

# 创建您自己的自定义主题

虽然 PrimeNG 提供了大量的预构建主题,但可能存在一些情况,您希望拥有一个独特的外观和感觉,更紧密地与您的品牌或特定的设计要求相匹配。这就是自定义主题发挥作用的地方。这些主题允许您根据您的确切规格定制 PrimeNG 组件的外观。

## 什么是 PrimeNG 自定义主题?

PrimeNG 中的**自定义主题**本质上是一组 CSS 样式,它覆盖了 PrimeNG 组件的默认样式。通过创建自定义主题,您有灵活性来定义颜色、字体、间距和其他设计元素,这些元素与您的品牌身份或项目的特定设计语言相匹配。

## 何时使用 PrimeNG 自定义主题?

自定义主题在以下情况下特别有益:

+   您正在构建一个品牌应用程序,其视觉身份需要与其他品牌数字资产保持一致

+   预构建主题与您项目的特定设计要求不匹配

+   您的目标是创建一个独特的用户界面,使其与众不同于典型应用

+   需要遵守特定的可访问性指南,这些指南可能不在默认主题中涵盖

## 如何创建 PrimeNG 自定义主题

创建 PrimeNG 自定义主题可能听起来有些令人畏惧,但有了 PrimeNG 的结构,它相当简单。您有三个选择,每个选择都提供其自身的优点和灵活性。让我们深入了解每个选项:

+   **视觉编辑器**:视觉编辑器是 PrimeNG 提供的一个用户友好的工具,允许您直观地自定义和设计您的主题

+   **命令行 Sass 编译**:如果你更喜欢更手动的方法,你可以选择使用命令行 Sass 工具来编译你的主题

+   **在项目中嵌入 SCSS 文件**:第三种选择是将 SCSS 文件直接嵌入到你的项目目录结构中

注意

截至本书发布时,视觉编辑器目前处于禁用状态。然而,有一个好消息——PrimeTek 团队已经决定开源设计师,使其免费可用。这一激动人心的开发的预期发布时间定于 2024 年第一季度。有关最新信息,请访问官方 PrimeTek 网站。

在所有三种选项中,将生成的主题文件导入到你的项目中至关重要。这确保了自定义主题被正确应用于 PrimeNG 组件,让你能够享受到个性化视觉风格的好处。

选择与你的偏好和项目要求最匹配的选项,开始为你的 PrimeNG 应用程序创建独特且视觉吸引人的主题之旅。

## 通过视觉编辑器创建自定义主题

PrimeNG 的主题世界随着**视觉编辑器**的引入而发生了革命性的变化。你再也不需要深入到 Sass 或 CSS 的代码行中去,以获得组件的完美外观。使用视觉编辑器,创建一个自定义主题就像拖动滑块或从调色板中选择颜色一样直观。

通过其直观的界面,你可以修改主题的各个方面,如颜色、字体、间距等。视觉编辑器提供实时预览,使你在自定义主题时能够轻松看到变化。一旦你对修改满意,你可以导出主题文件,该文件可以直接导入到你的项目中。

使用视觉编辑器创建自定义主题非常简单。以下是一个逐步指南:

1.  导航到 PrimeNG 主题页面并启动视觉编辑器。

1.  从与你期望的外观最接近的预构建主题开始。这为你提供了一个基础,你可以在此基础上进行进一步的定制。在这个例子中,我将选择`lara-light`主题。

![图 9.4 – 选择基本主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_04.jpg)

图 9.4 – 选择基本主题

1.  使用视觉编辑器的直观控件来调整颜色、字体和其他设计元素。随着你的更改,你将看到组件的实时预览。

![图 9.5 – 定制你的主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_05.jpg)

图 9.5 – 定制你的主题

在左侧面板中,你可以修改基本主题。有许多选项供你自定义。

注意

在撰写本书时,当前的视觉编辑器被认为处于遗留状态。一个基于新先进 UI 的 Theme Designer 将很快发布。

1.  一旦你对自定义主题满意,只需导出它即可。视觉编辑器将为你生成所有必要的 CSS 文件。

![图 9.6 – 下载您的自定义主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_06.jpg)

图 9.6 – 下载您的自定义主题

在完成自定义后,您可以通过点击名为`Download`的文件夹来开始下载您的自定义主题`theme.css`。

1.  下载导出的文件后,您可以将它们包含到您的 Angular 项目中。请确保它们在默认 PrimeNG 样式之后加载,以确保您的自定义设置优先。

    如果您的应用程序中只有一个主题,您可以直接将其放入`styles.scss`文件或`index.html`中:

    ```js
    // styles.scss
    @import 'assets/my-awesome-theme/theme.css';
    ```

这里是结果:

![图 9.7 – 应用您的自定义主题](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_07.jpg)

图 9.7 – 应用您的自定义主题

现在,您的应用程序已经具有了您自定义主题的期望外观和感觉。

## 通过 Sass 编译创建自定义主题

第二种方法,使用 Sass 编译,让您完全控制主题自定义过程。您可以手动编辑主题的 SCSS 文件,调整变量以实现所需的视觉风格。一旦您进行了必要的修改,您可以使用命令行 Sass 编译器生成 CSS 输出。然后,可以将编译后的 CSS 文件导入到您的项目中,确保您的自定义主题应用于 PrimeNG 组件。

这里是如何操作的:

1.  从 GitHub 克隆`primeng-sass-theme`仓库:

    ```js
    git clone https://github.com/primefaces/primeng-sass-theme.git
    ```

1.  安装 NPM 包:

    ```js
    cd primeng-sass-theme
    npm install
    ```

1.  在安装必要的包后,我们可以在`themes` | `mytheme`目录下找到所有的 SCSS 文件:

![图 9.8 – mytheme 文件夹](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_08.jpg)

图 9.8 – mytheme 文件夹

在`mytheme`文件夹下,您有几个选项来自定义您的自定义主题:

+   `variables`:您可以在该文件夹下自定义 CSS 变量,更改按钮背景、主要文本颜色、边框半径等

+   `_extension.scss`:如果您想覆盖组件设计,请更新此文件

+   `_font.scss`:这是您可以定义项目自定义字体的地方

+   `theme.scss`:此文件导入主题文件,以及`theme-base`文件夹,以便将所有内容合并

1.  在对您的自定义主题进行更改后,通过运行以下命令来编译您的更改:

    ```js
    sass --update themes/mytheme/theme.scss:themes/mytheme/theme.css
    [2023-09-17 14:33:46] Compiled themes/mytheme/theme.scss to themes/mytheme/theme.css.
    ```

您可以看到,在编译后,我们在`mytheme`文件夹下创建了一个`theme.css`文件,现在可以将其添加到我们的项目中。

## 通过嵌入 SCSS 文件创建自定义主题

这种第三种方法允许您将主题自定义无缝集成到现有的构建环境中。通过将主题 SCSS 文件放置在项目中的指定位置,例如`assets`或`styles`文件夹,您可以在构建过程中利用您的构建工具自动将 SCSS 文件编译成 CSS。

这里是步骤:

1.  将`mytheme`和`theme-base`文件夹复制到我们的`assets`文件夹。此选项使您能够结合 Angular CLI 默认流程。

![图 9.9 – Angular 项目中的 mytheme](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_09.jpg)

图 9.9 – Angular 项目中的 mytheme

1.  然后,我们只需要从 `mytheme` 导入 `theme.scss` 到 `styles.scss`:

    ```js
    @import 'assets/mytheme/theme.scss';
    ```

1.  最后,当我们修改 `mytheme` 下的任何内容时,Angular CLI 会为我们自然地处理编译,无需任何手动工作。

在掌握 PrimeNG 主题化的旅程中,我们看到了创建自定义主题方法的强大功能和灵活性,使主题定制变得简单且愉快。现在,让我们将重点转移到覆盖组件样式和其他高级样式技术,以真正使我们的 UI 独具特色。

# 覆盖组件样式和其他技巧

虽然 PrimeNG 提供了大量的主题和定制选项,但总会有一些场景需要我们调整某些样式以适应我们应用程序的独特需求。本节将指导您通过覆盖组件样式的流程,并分享一些额外的技巧和窍门来增强您的主题体验。

## 如何覆盖组件样式

在 PrimeNG 中覆盖组件样式与样式任何其他 Angular 组件类似。关键是理解您想要样式的组件的结构并使用特定的 CSS 选择器。以下是我们的做法:

1.  **检查组件**:在您覆盖样式之前,您需要知道您正在针对什么。使用您浏览器的开发者工具检查组件并了解其结构。以下是检查浏览器的一个示例:

![图 9.10 – 浏览器检查示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_10.jpg)

图 9.10 – 浏览器检查示例

在提供的屏幕截图中,您可以观察到位于左侧的 **Panels** 菜单的详细 HTML 元素结构。

1.  `p-menuitem-link-active` 类,允许您选择性地应用 CSS 样式到它上。

1.  `styles.scss` 以将样式应用到您的组件上。例如,为了给活动的路由链接添加下划线,您可以在 `style.scss` 中添加以下 CSS:

    ```js
    .p-menu .p-menuitem-link.p-menuitem-link-active {
      text-decoration: underline;
    }
    ```

1.  `::ng-deep` 伪类确保样式渗透到子组件中。这在尝试样式化 PrimeNG 组件的内部部分时特别有用。以下是一个示例:

    ```js
    :host ::ng-deep my-component .p-button {
        background-color: #333;
        color: #fff;
    }
    ```

    在此示例中,我们可以在我们的组件中覆盖 PrimeNG 按钮的样式,这使得定位和维护变得更加容易。这些更改仅将不同的颜色和背景颜色应用到我们的按钮上,使其与您的应用程序中的其他按钮区分开来。

注意

Angular 团队决定在 Angular 的未来版本中弃用 `::ng-deep`,因此请谨慎使用。您可以在 [`angular.io/guide/component-styles#deprecated-deep--and-ng-deep`](https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep) 找到更多相关信息。

注意

避免使用 `!important`。虽然使用 `!important` 来强制样式可能很有吸引力,但我们应避免这种做法。它会使未来的更改变得困难,并可能导致不可预测的结果。

## 使用 PrimeNG 公共工具

PrimeNG **Utils** 是 PrimeNG 提供的一组实用类,用于帮助您完成常见的样式任务。这些实用类提供了一种快速将特定样式或行为应用于元素的方法,而无需编写自定义 CSS。以下是一些 PrimeNG utils 的简要概述:

![图 9.11 – 常见的 PrimeNG 工具类](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_09_11.jpg)

图 9.11 – 常见的 PrimeNG 工具类

## 使用 PrimeNG CSS 变量

在现代网络开发中,CSS 变量(也称为 CSS 自定义属性)已成为创建更灵活和可维护的样式表的有力工具。PrimeNG 通过提供一组定义颜色的 CSS 变量来利用这种力量,这使得您更容易自定义应用程序的主题。

PrimeNG 的颜色系统是基于一组预定义的 CSS 变量构建的。这些变量代表了一系列颜色,从主色和辅助色到各种色调和色调。通过利用这些变量,您可以在整个应用程序中确保颜色使用的统一性,并轻松地根据需要调整外观。

例如,PrimeNG 定义了如 `--primary-color` 这样的主颜色以及如 `--primary-color-text` 这样的主颜色文本。这只是冰山一角,因为还有用于文本颜色、表面和突出显示等变量的变量。

注意

有关颜色变量的完整列表,请访问 [`primeng.org/colors`](https://primeng.org/colors)。

使用这些颜色变量很简单。您不需要在样式表中硬编码颜色值,而是引用 PrimeNG 颜色变量。这不仅确保了一致性,还使得未来的颜色更改变得容易。

以下是一个简单的示例:

```js
.my-custom-button {
    background-color: var(--primary-color);
    border-radius: var(--border-radius);
}
```

在代码中,按钮的背景颜色设置为 PrimeNG 的 `--primary-color`,边框半径设置为 `--border-radius`。如果您将来决定更改主颜色或边框半径,按钮的外观将自动更新,无需修改 `.my-custom-button` 样式。

### 自定义 PrimeNG 颜色变量

CSS 变量的一个主要优点是它们可以被覆盖。如果您希望自定义 PrimeNG 提供的默认颜色,您可以通过在样式表中重新定义变量轻松地做到这一点。

例如,以下是更改主颜色的方法:

```js
// styles.scss
:root {
    --primary-color: #3498db; /* Your desired color */
}
```

通过在根级别设置,您实际上更改了所有使用 `--primary-color` 变量的组件和元素的主颜色。

## 其他技巧和窍门

在本节中,我们将了解一些有价值的技巧、技术和最佳实践,这些将在使用主题时非常有帮助:

+   **全局样式**:如果您想在应用程序中全局应用样式,您可以在全局 CSS 文件中定义它们,并将其包含在您的应用程序中。这样,您可以自定义诸如排版、颜色和布局等常见元素。

+   **保持更新**:PrimeNG 正在积极开发。新版本可能会引入变化。始终检查文档并相应地更新你的样式。

+   **使用基础主题**:在开始一个新项目时,考虑使用预构建的主题作为基础。它为你提供了一个坚实的基础,然后你可以根据需要覆盖特定的部分。你可以查看内置主题的列表:[`primeng.org/theming#builtinthemes`](https://primeng.org/theming#builtinthemes)。

+   `theme-base`:

    ```js
    .p-button {
      ...
        &:enabled:hover {
            ...
            border-color: $buttonHoverBorderColor;
        }
    }
    ```

    从代码中,你可以看到当按钮被悬停时,按钮使用了`$buttonHoverBorderColor`变量来设置边框颜色。这个变量在`mytheme`下的`variables/_button.scss`中声明:

    ```js
    /// Border color of a button in hover state
    /// @group button
    $buttonHoverBorderColor: $primaryDarkColor;
    ```

    当在`_button.scss`中更新`$buttonHoverBorderColor`的值时,它将反映在所有使用此`$buttonHoverBorderColor`变量的组件中,例如按钮悬停状态。

注意

提供的按钮组件变量示例只是一个小子集。更多信息和与按钮相关的完整代码,你可以查看以下链接:[`github.com/primefaces/primeng-sass-theme/blob/main/theme-base/components/button/_button.scss`](https://github.com/primefaces/primeng-sass-theme/blob/main/theme-base/components/button/_button.scss) 和 [`github.com/primefaces/primeng-sass-theme/blob/main/themes/mytheme/variables/_button.scss`](https://github.com/primefaces/primeng-sass-theme/blob/main/themes/mytheme/variables/_button.scss)。

+   **跨浏览器测试**:始终在不同的浏览器中测试你的样式以确保一致性。某些组件可能有浏览器特定的样式。

简而言之,最重要的是要跟上 PrimeNG 和 Angular 团队的变化。如果我们能利用最新的实践来改进我们的应用程序,这将是有益的。现在,让我们总结本章涵盖的关键点,并反思重要的收获。

# 摘要

在本章中,我们深入探讨了 PrimeNG 主题的世界,从理解 PrimeNG 主题的基础到掌握定制组件以适应我们独特需求的艺术。

我们首先介绍了 PrimeNG 主题的概念,强调了它在创建统一且视觉上吸引人的 Angular 应用程序中的重要性。通过利用 PrimeNG 的主题功能,我们可以确保应用程序的一致外观和感觉,从而提升用户体验。

我们随后探索了 PrimeNG 提供的丰富多样的预构建主题。这些主题从浅色到深色,应有尽有,提供了一种快速简单的方式,让我们的应用程序看起来专业,无需进行大量定制。在预构建选项之外,我们学习了如何使用可视化编辑器创建自己的自定义主题。这个强大的工具允许我们调整应用程序外观的每一个细节,确保它与我们的品牌或期望的美学完美契合。

随着我们不断进步,我们讨论了覆盖组件样式和 PrimeNG 提供的实用类的重要性。这些工具为我们提供了调整应用程序外观的灵活性,确保每一个细节都恰到好处。主题化不仅仅是让应用程序“看起来漂亮”。它关乎创造一个一致、直观且引人入胜的用户体验。通过理解和有效利用 PrimeNG 的主题化功能,我们可以打造出不仅外观惊艳,而且能与目标受众产生共鸣的应用程序。这种知识使我们能够提升我们的应用程序,在竞争激烈的市场中脱颖而出。

随着我们过渡到下一章节,我们将把焦点转向性能优化技术。在这里,我们将揭示确保我们的 Angular 应用程序运行顺畅和高效的战略和最佳实践。在主题化的基础现在稳固地掌握在我们手中后,我们准备好应对性能和可扩展性的复杂性。




# 第十章:探索 Angular 应用程序的优化技术

在当今的数字时代,用户期望应用程序快速且响应灵敏。轻微的延迟可能会导致用户满意度下降,甚至影响业务指标。通过理解和实施性能优化技术,你确保你的应用程序满足用户期望,从而提高用户参与度、提高留存率和积极的用户反馈。因此,当我们深入 Angular 和 PrimeNG 的世界时,确保我们的应用程序不仅功能齐全且美观,而且性能达到顶峰是至关重要的。本章致力于为你提供工具和技术,以优化使用 PrimeNG 组件的 Angular 应用程序的性能。

在本章中,你将深入了解性能优化的核心概念。我们将揭示一些流行的技术,例如懒加载、通过`*ngFor`的`trackBy`、纯管道、包优化等。随着我们的进展,你将了解变更检测策略的微妙之处,从而全面理解它们对性能的深远影响。最后,我们将通过 Angular 内置的性能工具进行实战演练。这些工具是真正的变革者,擅长定位和纠正性能瓶颈。

本章将涵盖以下主题:

+   介绍 Angular 性能优化

+   使用性能分析和分析

+   实现懒加载和延迟加载

+   使用变更检测

+   优化数据绑定

+   使用代码和包优化

# 技术要求

本章包含各种关于 Angular 优化的工作代码示例。你可以在本书 GitHub 仓库的`chapter-10`文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-10`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-10)。

# 介绍 Angular 性能优化

每个开发者都梦想着构建功能丰富且速度极快的应用程序。但随着我们在 Angular 应用程序中添加更多功能和复杂性,我们可能会无意中引入性能瓶颈。这就是**Angular 性能优化**发挥作用的地方。让我们深入了解 Angular 开发的这一关键方面。

## 什么是 Angular 性能优化?

**Angular 性能优化**是指通过应用各种技术、策略和最佳实践来提高 Angular 应用程序的性能和响应性。它涉及识别和解决性能瓶颈、减少不必要的计算、优化渲染过程和最小化加载时间。通过优化 Angular 应用的性能,我们可以提高用户满意度,减少用户退出,并提高整体应用程序的成功率。

## Angular 应用程序中的主要性能问题

Angular 是一个强大的框架,但像任何工具一样,它并非对性能问题免疫。一些常见的原因如下:

+   **缓慢的初始加载时间**:当一个 Angular 应用程序拥有庞大的代码库时,加载所有必需的资源可能会很耗时。缓慢的初始加载时间会显著影响用户的感知和参与度。

+   **过度变化检测**:Angular 的默认变化检测机制可能计算成本高昂,尤其是在大型应用程序中,频繁更新时。低效的变化检测可能导致不必要的渲染和性能下降。

+   **内存泄漏**:当组件或服务没有被正确销毁时,可能会发生这种情况,尤其是在你忽略取消订阅可观察对象的情况下,这会导致随着时间的推移内存使用量增加。

+   **复杂的计算**:运行复杂的算法或计算,尤其是在实时场景如动画中,可能会减慢应用程序。

+   **不足的懒加载**:即使组件不是立即需要的,也预先加载所有组件和模块,这可能会导致初始加载时间增加。缺乏适当的懒加载技术可能会对应用程序的性能产生负面影响。

## 流行的优化技术

为了解决这些性能问题并优化 Angular 应用的性能,开发者采用各种技术和策略。以下是一些流行的优化选项:

+   **性能分析和分析**:Angular 提供了内置的性能工具和技术,允许我们分析和分析我们应用程序的性能。例如,Angular DevTools 工具可以帮助识别性能瓶颈并提供优化的见解。

+   **懒加载和延迟加载**:它们是按需加载模块或组件的技术,而不是预先加载。通过在我们的 Angular 应用程序中实现懒加载和延迟加载,我们可以减少初始加载时间并提高整体性能。

+   `默认` 策略和 `OnPush` 策略。根据应用程序的需求选择适当的变化检测策略,我们可以最小化不必要的检测并提高性能。

+   `trackBy` 函数的使用,有助于最小化不必要的更新并提高渲染性能。优化的数据绑定确保在发生变化时,只有必要的组件被更新。

+   **代码和包优化**:最小化、打包 JavaScript 和 CSS 文件、摇树优化和代码拆分是用于优化 Angular 应用程序大小和加载速度的技术。这些优化减少了网络请求并提高了整体性能。

现在我们已经了解了 Angular 性能优化及其重要性,在接下来的章节中,我们将详细探讨这些技术。我们将从性能分析和分析开始。

# 引入性能分析和分析

**性能分析**类似于您应用程序的健康检查。它涉及监控应用程序的操作,了解它大部分时间花在哪里,并识别潜在的瓶颈。另一方面,**分析**是后续步骤,其中这些数据被解释,问题被定位,并制定性能提升策略。

## 性能分析和分析何时使用?

在以下情况下需要性能分析和分析:

+   应用程序的部分似乎比预期慢

+   有确保新实现不会降低性能的愿望

+   大型应用程序需要优化资源密集型操作

+   目标是向用户提供无缝体验,特点是快速加载时间和流畅的交互

## Angular 中性能分析和分析是如何工作的?

性能分析捕获了应用程序在运行时行为的数据。对于 Angular 开发者来说,这个领域的变革者就是 **Angular DevTools** 扩展程序 ([`angular.io/guide/devtools`](https://angular.io/guide/devtools))。这个针对 Angular 应用程序定制的浏览器扩展程序,提供了对组件树结构和变更检测周期的洞察,甚至提供了专门的性能分析功能。它是浏览器原生开发者工具的补充,不可或缺的工具。

要使用 Angular DevTools 扩展程序进行性能分析,请按照以下步骤操作:

1.  在您首选的网页浏览器中安装 Angular DevTools 扩展程序。扩展程序适用于 Chrome 和 Firefox,可以从相应浏览器的扩展程序市场安装。在本节中,我将使用 Chrome 浏览器。

1.  通过右键单击您的 Angular 应用程序中的元素并选择 **Inspect**(或使用 *Ctrl* + *Shift* + *I* 键盘快捷键)来打开 Chrome DevTools。

1.  在 Chrome DevTools 中,点击 **Angular** 选项卡以切换到 Angular DevTools 视图。

![图 10.1 – Angular DevTools 选项卡](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_01.jpg)

图 10.1 – Angular DevTools 选项卡

1.  在 Angular DevTools 中,您可以通过点击红色圆圈来启用性能分析功能,以收集与渲染、变更检测和其他 Angular 特定活动相关的性能数据:

![图 10.2 – Angular DevTools 性能分析](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_02.jpg)

图 10.2 – Angular DevTools 性能分析

1.  在启用性能分析的情况下,与你的 Angular 应用程序交互并执行典型的用户操作。Angular DevTools 扩展将实时收集性能数据。

1.  使用 Angular DevTools 检查各种性能指标并确定关注区域。例如,你可以分析渲染单个组件所花费的时间、变更检测周期的数量或网络请求的持续时间。

1.  根据收集到的性能数据,确定对性能瓶颈有贡献的组件、指令或服务。寻找渲染或变更检测花费大量时间或发生不必要的数据获取的区域。

1.  一旦确定了性能瓶颈,就对代码、组件结构或数据获取机制进行有针对性的优化。这可能涉及优化变更检测策略、实现记忆化技术或引入分页或缓存以进行数据获取。

1.  在应用优化后,使用 Angular DevTools 重新分析你的 Angular 应用程序的性能。验证优化是否改善了已识别的性能瓶颈,并且没有引入新的问题。如果需要进一步改进,请迭代优化过程。

通过遵循这些步骤并利用 Angular DevTools 扩展,你可以深入了解 Angular 应用程序的性能特征。这使你能够进行有针对性的优化并提高应用程序的整体性能。

注意

容易陷入过度优化的陷阱。始终权衡优化的好处与引入的努力和复杂性。例如,如果所讨论的组件相对简单,并且实施高级优化技术带来的性能提升微不足道或难以衡量,那么坚持使用 Angular 提供的默认变更检测策略可能更为实际。这确保了优化和开发努力之间的平衡,避免了不必要的复杂性以及代码可维护性和可读性方面的潜在权衡。

性能分析和分析就像放大镜,揭示了我们的应用程序行为和效率的复杂细节。在下一节中,我们将深入探讨懒加载这一技术,它承诺将进一步增强我们应用程序的响应性和速度。

# 实现懒加载和延迟加载

**懒加载**和**延迟加载**是设计模式,其中内容仅在需要或请求时加载,而不是一次性加载所有内容。这种方法优化了资源分配并提高了性能。以下是这些概念的详细解释:

+   **懒加载**:懒加载是一种设计模式,其中内容,如 Angular 路由、组件或服务,是动态和按需加载的,而不是一开始就上传所有内容。通过实现懒加载,初始时不会加载不必要的资源,从而减小初始包的大小并提高应用程序的启动时间。这种方法在大型应用程序中特别有用,因为一开始就加载所有资源可能会导致性能下降。

+   **延迟加载**:延迟加载是一种设计模式,涉及延迟加载或执行某些资源,如脚本或资产,直到它们被需要。在 Web 开发中,延迟通常指的是延迟加载对网页初始渲染和功能非必要的 JavaScript 文件或其他资源。通过延迟加载非关键资源,网页可以更快地渲染和显示,提高感知性能和整体用户体验。这种技术通常用于优先和优化关键资源的加载,允许页面尽快可用,同时非必要资源在后台加载。

## 懒加载和延迟加载何时使用?

懒加载和延迟加载在以下场景中特别有益:

+   **大型应用程序**:对于具有众多功能和组件的应用程序,一次性加载所有内容可能会消耗大量资源。懒加载确保只加载必要的组件,从而提高初始加载时间。

+   **用户角色和权限**:在具有不同用户角色和权限的应用程序中,可以使用懒加载根据用户角色加载组件,确保用户只下载他们可以访问的功能。

+   **网络效率**:对于网络连接较慢的用户,下载大型应用程序可能耗时。懒加载可以通过减少初始下载大小来帮助。

+   **功能丰富的平台**:在某些功能使用频率低于其他功能的平台上,将那些较少使用的功能延迟加载以提高平台的初始加载时间是有意义的,从而实现更快的渲染。

## 懒加载示例

想象一个使用 Angular 构建的电子商务平台。该平台有各种路由,如**首页**、**产品列表**、**产品详情**、**购物车**和**用户资料**。在传统的加载方法中,当用户访问网站时,他们会预先下载所有这些路由/组件,即使他们只是浏览**首页**。例如,如果没有使用懒加载,路由将看起来像这样:

```js
export const appRoutes: Route[] = [
  {
    path: 'home',
    component: HomeComponent,
  },
  {
    path: 'cart',
    component: CartComponent,
  },
  {
    path: 'user-profile',
    component: UserProfileComponent,
  },
]
```

在此代码中,路由名称与组件相关联。当用户导航到`home`页面时,将显示`HomeComponent`:

![图 10.3 – 未使用懒加载的页面](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_03.jpg)

图 10.3 – 未使用懒加载的页面

您会注意到,当应用程序加载时,它从开始就加载所有必要的脚本。如您所见,主包大小为 **820 kB**,加载时间为 **1.40 s**。当您导航到其他路由时,它们的脚本已经预先加载。这可能导致不必要的数据消耗和页面渲染延迟,尤其是如果用户在会话期间从未访问过某些部分,如 **购物车** 或 **用户资料**。让我们看看懒加载如何改进当前实现。

使用懒加载,应用程序可以被构建为更智能地加载路由:

+   初始情况下,当用户访问网站时,只加载 **主页**。

+   如果用户决定查看产品,**产品** 页面将即时加载。

+   当用户想要检查他们的购物车时,**购物车** 页面将及时加载。

+   如果他们决定查看或编辑他们的资料,那么随后将加载 **用户资料** 页面。

这是一个懒加载如何实现的基本示例:

```js
export const appRoutes: Route[] = [
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'home',
  },
  {
    path: 'home',
    loadComponent: () => import('./components/home.component'),
  },
  {
    path: 'cart',
    loadComponent: () => import('./components/cart.component'),
  },
  {
    path: 'user-profile',
    loadComponent: () => import('./components/user-profile.component'),
  },
]
```

在前面的代码片段中,使用 `loadComponent` 属性定义了将在路由激活时加载相应组件的函数。这确保了用户只下载他们正在积极交互的应用程序部分,从而加快加载时间并更有效地使用资源。

这是结果:

![图 10.4 – 带有懒加载的页面](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_04.jpg)

图 10.4 – 带有懒加载的页面

如您所见,网站仅在第一次导航时加载 `main.js` 和 `HomeComponent`。如果您导航到购物车路由,您将观察到主要包大小从 `CartComponent` 显著减少,该路由是从 `app.routes.ts` 启动的。

## PrimeNG 延迟渲染示例

PrimeNG 的 **Defer** 指令是一个旨在延迟内容加载直到它进入视口的工具。本质上,它是一种懒加载的形式,但适用于各种类型的内容。通过将内容的初始化推迟到需要时,我们可以显著提高页面的初始加载时间。

以电子商务网站为例。该网站在其 `Defer` 中列出了数百个产品,我们可以进行优化。

要这样做,首先,请确保您已从 PrimeNG 导入 `DeferModule`:

```js
import { DeferModule } from 'primeng/defer'
```

现在,让我们看看如何设置 PrimeNG 的 `Defer` 来显示产品列表:

```js
<div class="grid gap-4" pDefer>
  <ng-template>
    <p-card
      *ngFor="let product of products"
      [header]="product.name"
      [style]="{ width: '300px' }"
    >
     <!-- product content -->
    </p-card>
  </ng-template>
</div>
```

让我们分解一下:

+   `<div pDefer>`: `pDefer` 指令的目的是将内容的渲染推迟到渲染周期中的较晚阶段。此指令之后必须跟一个 `ng-template` 元素。

+   `<p-card *ngFor="let product of products">`: 此元素使用 `*ngFor` 指令遍历 `products` 数组,为数组中的每个产品渲染一个 `<p-card>`。

在实施后,产品列表的内容将不会显示,直到它在滚动中变得可见。

PrimeNG 的 `Defer` 组件在你希望在产品列表已进入视口后从 API 获取产品数据时也非常有益,这将大大提高你应用程序的性能。以下是一个示例:

```js
// products.components.ts
<div pDefer (onLoad)="loadAnotherProducts()">
  <ng-template>
    <primengbook-product-list [products]="anotherProducts" />
  </ng-template>
</div>
...
loadAnotherProducts() {
  this.anotherProducts = this.productService.loadProducts()
}
```

让我们分解代码:

+   `<div pDefer (onLoad)="loadAnotherProducts()">`:此 `<div>` 元素使用 `pDefer` 指令,该指令将产品的加载和内容渲染延迟到元素进入视口。

+   `loadAnotherProducts() { ... }`:当 `(onLoad)` 事件被触发时执行此方法。在方法内部,它调用 `this.productService.loadProducts()` 来加载产品并将结果分配给组件中的 `anotherProducts` 属性。

注意

如果你正在处理 `Table`、`Tree` 或长列表项,你可以实现 `Virtual Scroller` 或 `Pagination` 来帮助提高性能并增强用户体验。

利用懒加载和延迟加载的强大功能可以显著提高我们应用程序的性能,确保用户在需要时才加载他们所需的内容。在下一节中,我们将深入探讨 Angular 性能的另一个关键方面:理解和管理变更检测。

# 与变更检测一起工作

**变更检测**是 Angular 确定组件是否需要根据数据更改进行更新的过程。每当组件的数据绑定属性更改时,Angular 会检查视图是否需要更新以反映这些更改。此过程是自动的,但了解其工作原理对于优化性能至关重要。

## 变更检测是如何工作的?

Angular 中的变更检测过程遵循单向流。它从根组件开始,遍历组件树,检查每个组件中的更改。以下是 Angular 中变更检测工作原理的简要概述:

1.  **初始化**:在组件初始化期间,Angular 设置组件的变更检测器并初始化组件的属性和绑定。

1.  `Default` 策略在组件的任何输入属性更改或事件绑定被触发时触发变更检测。以下是一个组件树的示例:

![图 10.5 – 变更检测 – 默认策略](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_05.jpg)

图 10.5 – 变更检测 – 默认策略

当 `ProductList` 组件发生事件时,变更检测过程开始,从根级别(`AppComponent`)开始,通过所有其子组件进行传播。

+   另一方面,`OnPush` 策略仅在输入属性更改或组件从其模板或视图层次结构中的组件接收事件时触发变更检测。在之前图 10.5 中展示的相同场景中,向 `ProductListDefer` 组件引入 `OnPush` 变更检测策略会改变变更检测过程的行为:

![图 10.6 – 变更检测 – OnPush 策略](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_06.jpg)

图 10.6 – 变更检测 – OnPush 策略

现在,当 `ProductList` 组件上发生事件时,变更检测仅限于从 `AppComponent` 到 `Products` 以及到 `ProductList` 的层次结构。值得注意的是,在 `ProductListDefer` 组件上没有变更检测,因为没有将新的引用传递给 `ProductListDefer` 组件。

1.  **变更检测周期**: Angular 的变更检测系统遵循循环过程。在每个周期中,变更检测过程会对组件树中的所有组件执行。它包括以下步骤:

    1.  **变更检测检查**: Angular 检查组件的属性、绑定和其他输入以检测变更。它将当前值与上一次变更检测周期中存储的值进行比较。

    1.  **更新视图**: 如果检测到变更,Angular 通过更新与变更属性或绑定相关的 DOM 元素来更新组件的视图。

    1.  **传播**: 如果组件的视图被更新,Angular 将更改传播到组件树中的子组件,触发它们的变更检测过程递归执行。

1.  `ChangeDetectorRef` 类提供了一套与变更检测机制交互的函数:

    +   `markForCheck()`: 此函数将组件及其祖先标记为在下一个变更检测周期中需要检查。即使组件没有直接参与变更,标记为检查也确保其视图将被更新。

    +   `detach()`: 此函数将组件的变更检测器从变更检测树中分离出来。这意味着在重新附加之前,组件将在变更检测过程中被跳过。

    +   `detectChanges()`: 调用 `detectChanges()` 触发组件及其后代的变更检测周期。这在您希望针对特定事件手动检查变更时特别有用。

    +   `reattach()`: 此函数抵消了 `detach()` 的效果。它将组件的变更检测器重新附加到变更检测树中,使其能够参与后续的变更检测周期。

Angular 变更检测机制中的一个关键元素是 `zone.js`。这个库通过“猴子补丁”浏览器中的大多数异步操作(如用户交互、HTTP 请求和定时器)发挥关键作用。

注意

**猴子补丁**是一种技术,允许修改、扩展甚至抑制代码段默认行为,而无需对其源代码进行任何直接更改。

当这些操作完成后,`zone.js` 通知 Angular 运行变更检测。本质上,它充当看门狗,监视所有异步任务。一旦这些任务中的任何一个完成,`zone.js` 就会通知 Angular 检查组件并在必要时更新视图。

例如,当用户点击按钮时,`zone.js` 检测到这种交互并告诉 Angular 可能发生了变化。然后 Angular 运行变更检测过程,检查是否有任何实际更改需要更新到视图中。

注意

对于第三方脚本,可以帮助提高应用程序性能的技术之一是在 Angular NgZone 之外加载脚本并运行逻辑([`angular.io/api/core/NgZone#runoutsideangular`](https://angular.io/api/core/NgZone#runoutsideangular))。

## 变更检测策略如何影响性能

默认变更检测策略非常彻底,但对于大型应用程序或复杂的组件树可能不够高效。每次数据发生微小变化时,都会触发对所有组件的检查,这可能导致延迟或卡顿的动画,尤其是在较慢的设备上。

提高性能的一种方法是通过使用 `OnPush` 策略,其中我们告诉 Angular 更谨慎地选择何时检查更改。这可能导致明显的性能提升,因为 Angular 将跳过检查那些我们知道没有发生变化的组件。然而,这也意味着我们需要更加注意何时以及如何更改数据,以确保我们的视图保持最新。

假设我们有两个列表,使用 PrimeNG 组件显示产品列表。默认情况下,Angular 会在应用程序中发生任何事件时检查此组件及其所有子组件的更改。然而,如果我们的产品列表不经常更改,这可能过于冗余,并导致性能问题。

让我们深入了解默认变更检测策略的细节。以下是**产品**页面的示例代码:

```js
@Component({
  standalone: true,
  imports: [CommonModule, ProductListComponent, FormsModule, InputTextModule],
  template: `
    <h2>Products</h2>
    <div class="p-input-icon-left mb-8">
      <i class="pi pi-search"></i>
      <input
        type="text"
        pInputText
        [(ngModel)]="productName"
        (keyup)="filterProduct()"
      />
    </div>
    <primengbook-product-list [products]="filteredProducts" />
    <primengbook-product-list [products]="anotherProducts" />
  `,
})
export default class ProductsComponent {
  ...
  filterProduct() {...}
}
```

上述代码片段是一个名为 `ProductsComponent` 的 Angular 组件示例。让我们分析代码并解释其功能:

+   `<input type="text" pInputText (keyup)="filterProduct()" ... />`: 这定义了一个 PrimeNG 输入字段,每当在输入字段中释放按键时,都会执行 `filterProduct()` 方法

+   `<primengbook-product-list [products]="..." />`: 这代表一个名为 `primengbook-product-list` 的自定义组件,并将组件的 `filteredProducts` 或 `anotherProducts` 属性绑定到 `products` 输入属性

这是一个使用默认变更检测策略的独立组件示例,默认情况下设置为 `Default`。让我们观察在这个场景下应用程序的行为:

![图 10.7 – 使用默认变更检测策略的页面](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_07.jpg)

图 10.7 – 使用默认变更检测策略的页面

如您所见,在输入过滤产品关键字后,它重新渲染了两个产品列表,整个应用程序的总耗时为 4.4 毫秒,尽管我们只在一个产品列表上进行了搜索。

理想的情况是,当你搜索时,它只会触及一个产品列表。为了优化代码以遵循这一点,我们可以在`ProductListComponent`中将变更检测策略设置为`OnPush`:

```js
@Component({
  selector: 'primengbook-product-list',
  standalone: true,
  imports: [CommonModule, CardModule, ButtonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  ...
})
export class ProductListComponent {
  @Input() products: Product[] = []
  ...
}
```

在这种设置下,当输入属性更改时,Angular 只会检查一个`ProductListComponent`以查找更改。这意味着 Angular 不会浪费周期检查无关组件。让我们看看这次应用程序的表现:

![图 10.8 – 使用默认变更检测策略的页面](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_08.jpg)

图 10.8 – 使用默认变更检测策略的页面

你会注意到,当我们搜索时,我们的应用程序只会重新渲染一个产品列表,这使总耗时从 4.4 毫秒减少到 1.1 毫秒。如果你的应用程序很复杂,这将是一个显著的改进。

## OnPush 的潜在陷阱

虽然`OnPush`功能强大,但它并非没有问题。你可能遇到的一个常见问题是,在组件内部(如用户交互)所做的更改不会触发变更检测。这可能导致即使数据已更改,UI 也不会更新的情况。

另一个挑战是在处理对象和数组时。如果你修改了一个作为输入属性的数组或对象,但引用保持不变,Angular 将无法检测到变更。这就是像`immutable.js`或 Angular Signals 这样的库发挥作用的地方:

+   `List`、`Map`和`Set`在创建后不能被修改。这种不可变性确保了任何对数据的更改都会导致新的对象引用,这使得 Angular 检测差异更加高效。此外,`Immutable.js`鼓励在修改数据时使用纯函数,在保留原始数据的同时创建新的数据结构实例。然后,Angular 可以通过比较对象引用来有效地识别更改。

+   `update`还强制执行不可变方法,这在与`OnPush`变更检测一起工作时是一种常见做法。

我强烈建议在生产准备就绪时使用 Signals 来优化渲染更新,因为它是一个来自 Angular 的内置解决方案,而不是第三方库。

在 Angular 领域,理解变更检测策略对于确保我们的应用程序高效运行至关重要。接下来,让我们深入了解 Angular 性能的另一个关键方面:优化数据绑定,以进一步增强我们应用程序的响应性和速度。

# 优化数据绑定

在 Angular 中,**数据绑定**的核心是保持视图和组件数据的一致性。这是允许组件数据的变化立即反映在视图上,反之亦然的魔法。然而,并非所有数据更改都是平等的。有些是频繁且微小的,而有些则是罕见但重要的。优化的数据绑定是关于选择性的,只在真正必要时更新视图,并以最有效的方式进行。

## 何时使用优化的数据绑定?

优化的数据绑定技术通常在以下场景中应用:

+   **大数据集**:当处理大数据集时,每次数据的一小部分更改时更新整个视图可能效率低下。优化的数据绑定技术有助于识别和更新视图的相关部分,最小化不必要的更新并提高性能。

+   **频繁更新**:在数据频繁更改的应用程序中,传统的数据绑定方法可能导致过度重绘和不必要的 DOM 操作。优化的数据绑定技术有助于优化数据更改检测和更新过程,以减少开销并提高响应性。

+   **复杂计算**:在某些情况下,数据绑定涉及计算密集型操作,如对大数组进行排序或过滤。优化的数据绑定技术可以通过仅更新视图受影响的部分来优化这些操作,而不是重新渲染整个数据集。

## 一些优化的数据绑定技术

优化 Angular 中的数据绑定围绕几个关键策略:

+   `*ngFor` 用于遍历项目,默认情况下,Angular 会检查列表中的每个项目以检测更改。通过使用 `trackBy` 函数,您可以指示 Angular 根据其唯一 ID 跟踪项目,从而减少不必要的检查。

+   **纯管道**:管道在您的模板中转换数据。纯管道仅在输入更改时重新评估,这使得它比其不纯的对应物更高效。务必记住,在管道中执行外部请求可能会引起性能问题,应避免这样做。

+   `OnPush` 更改检测策略,Angular 仅在组件的输入属性更改或组件内部引发事件时触发更改检测。

+   **不可变数据结构**:使用不可变数据结构可以提高数据绑定的性能。不可变对象在更改发生时不会直接修改;相反,它们在更改发生时创建新的实例。这允许 Angular 更高效地检测更改并优化渲染。

在接下来的章节中,我们将探讨 `trackBy` 函数和纯管道的示例。

## 优化数据绑定示例 - `trackBy` 函数

想象一个电子商务平台显示产品列表。每个产品都有一个名称、价格和评分。随着产品数量的增加,任何产品列表中的更改,如价格更新,都可能触发大量的视图更新。

使用 `trackBy` 函数,我们可以确保只有受影响的产品在视图中得到更新:

```js
<div *ngFor="let product of products; trackBy: trackByProductId">
  {{ product.name }} - {{ product.price | currency }}
</div>
...
trackByProductId(_: number, product: Product): number {
  return product.id
}
```

在此示例中,`trackBy` 函数确保 Angular 只根据产品 ID 值的实际更改更新产品的视图。

PrimeNG 也无缝支持 `trackBy` 函数。自 16.x 版本以来,PrimeNG 提供了 `trackBy` 属性,您可以将 `trackBy` 函数传递到 `DataView`、`OrderList`、`PickList` 和 `Tree` 组件的数据绑定中。以下是如何使用它的示例:

```js
 <p-dataView
  ...
  [trackBy]="trackByProductId"
>
  <!-- DataView content -->
</p-dataView>
```

如`DataView`组件所示,你可以将`trackByProductId`函数分配给`trackBy`属性。这种配置确保只有当产品 ID 值发生变化时,视图才会更新。

## 优化数据绑定示例 – 纯管道

假设你有一个 Angular 应用程序,它显示产品列表。每个产品都有名称、价格和数量等属性。该应用程序还允许用户使用基于现有产品数量的计算来计算产品的定价。

不使用纯管道时,你的组件模板可能看起来像这样:

```js
<span class="text-2xl font-semibold">
{{
  '$' + calculateTotal(product.quantity)
}}
</span>
```

在这个例子中,`calculateTotal`函数为每个产品调用,根据现有产品数量计算总价。考虑以下场景,`calculateTotal`函数是一个执行复杂计算的定制函数。让我们看看结果:

![图 10.9 – 从模板计算总价](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_09.jpg)

图 10.9 – 从模板计算总价

如你所见,在没有任何优化的情况下,每次对产品或页面进行更改时,`calculateTotal`函数都会为所有产品调用,即使只有单个产品的数量发生变化。这会导致不必要的执行,即使只有单个产品的数量发生变化,也会导致 20 次执行,如控制台日志中所示。这可能导致冗余的重新计算并损害性能,尤其是在处理大量数据集时。

为了使用纯管道优化性能,你可以创建一个自定义纯`pipe`,称为`TotalPipe`,它计算给定产品的总价:

```js
import { Pipe, PipeTransform } from '@angular/core'
@Pipe({
  name: 'total',
  standalone: true,
  pure: true,
})
export class TotalPipe implements PipeTransform {
  transform(quantity: number): number {
    return calculateTotal(quantity)
  }
}
```

在这个例子中,`TotalPipe`通过在`@Pipe`装饰器中将`pure`属性设置为`true`被声明为一个纯管道。这确保了管道只有在数量值发生变化时才会重新计算其输出。

现在,你可以在你的组件模板中使用`TotalPipe`:

```js
<span class="text-2xl font-semibold">
  $ {{ product.quantity | total }}
</span>
```

通过使用`TotalPipe`,现在将总价的计算委托给了管道。如果更改了产品的数量,只有受影响产品的总价将被重新计算;其他产品将保持不变:

![图 10.10 – 从管道计算总价](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_10.jpg)

图 10.10 – 从管道计算总价

如我们所见,当产品质量或产品发生变化时,它只会影响一个产品,而不是像之前的方法那样有 20 个变化(**图 10.9**),这显著提高了性能,尤其是在处理大量数据集或复杂计算时。

总之,优化 Web 应用程序中的数据绑定对于提高性能和减少不必要的重新渲染至关重要。通过遵循最佳实践,你可以最小化数据绑定对应用程序性能的影响。现在,让我们过渡到下一个主题:代码和捆绑优化。

# 与代码和捆绑优化一起工作

**代码和包优化**是指优化 Web 应用程序中的代码库和生成的包的过程,以提高其性能和效率。它涉及分析、重构和最小化代码,以消除冗余、减少文件大小并提高执行速度。

优化代码和包大小对于提供快速加载的 Web 应用程序、减少带宽使用和提升整体用户体验至关重要。它有助于确保应用程序快速加载,对用户交互做出及时响应,并在各种设备和网络条件下高效运行。

## 何时使用代码和包优化?

优化不仅仅是一项一次性任务;它是一个持续的过程。一旦我们的应用程序开始增长,或者当我们注意到性能问题时,就是考虑优化的时间了。对于以下方面来说,它尤其重要:

+   拥有广泛代码库的大型应用程序

+   依赖于多个第三方库或框架的应用程序

+   针对网络连接较慢地区的应用程序

+   旨在实现更快的加载时间和改进用户体验的项目

## 利用 Source Map Explorer 进行包优化

理解您应用程序包组成的最高效工具之一是 **Source Map Explorer**。它提供了您包不同部分的视觉表示,使得识别可能影响应用程序性能的大量代码块或不必要的库变得更容易。

在深入了解其用法之前,让我们设置 Source Map Explorer:

1.  您可以通过使用 `npm` 或 `yarn` 将 Source Map Explorer 添加到您的项目中。在您的项目目录中运行以下命令:

    ```js
    npm install source-map-explorer --save-dev
    OR
    yarn add -D source-map-explorer
    ```

1.  在构建过程中生成源映射,您需要调整 Angular 的构建配置。在您的 `angular.json` 文件中,找到您应用程序的构建配置中的 `sourceMap` 选项,并将其设置为 `true`:

    ```js
    // angular.json or project.json
    "build": {
      "executor": "@angular-devkit/build-angular:browser",
      "outputs": [
        "{options.outputPath}"
      ],
      "options": {
        ...
        "sourceMap": true
      },
      ...
    }
    ```

在安装了工具并调整了配置后,您就可以准备分析您的包了:

1.  首先,您需要创建一个带有源映射的生产构建版本的 Angular 应用程序。使用以下命令来完成此操作:

    ```js
    ng build --prod
    ```

1.  构建(build)完成后,导航到 `dist` 文件夹(或您的构建工件所在的任何位置)。您将找到几个 `.js` 和 `.js.map` 文件。要分析特定的包,请使用 Source Map Explorer,如下所示:

    ```js
    // for single file
    npx source-map-explorer dist/{your-project-path}/main.2e8930c4811df7ab.js
    // for all files
    npx source-map-explorer dist/{your-project-path}/**/*.js
    ```

1.  Source Map Explorer 将展示您包的视觉树状图。每个部分代表您代码的一部分或一个导入的库。每个部分的大小和位置与其在包中的大小相对应。将鼠标悬停在一个部分上会显示更详细的信息。以下是一个示例:

![图 10.11 – 包的视觉映射](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_10_11.jpg)

图 10.11 – 包的视觉映射

树状图的视觉表示使得更容易发现大型库或代码块。如果你看到你不认识的库或看起来不成比例地大,可能值得进一步调查。你可以研究这个不熟悉的库,分析其依赖关系,审查它在你的代码库中的使用情况,并寻求社区反馈以了解其目的和性能影响。

注意

虽然源映射对于开发和分析来说非常有价值,但它们可能会暴露你的应用程序的代码结构。始终确保在生产环境中不提供源映射,除非你希望在生产环境中进行调试。

在网络开发世界中,优化你的代码和包对于实现最佳应用程序性能至关重要。通过使用源映射探索器等工具和技术,我们学会了如何剖析和精炼我们的应用程序包以实现峰值效率。在我们结束本章之前,让我们花点时间反思关键要点。

# 摘要

在本章中,我们深入探讨了性能和优化的世界。我们踏上了一段旅程,以了解 Angular 性能的复杂性,从懒加载的基础概念到代码和包优化的高级技术。

到目前为止,你已经掌握了如何优化 Angular 应用程序的知识,特别是那些使用 PrimeNG 组件的应用程序。我们亲眼见证了高效变更检测策略的变革力量,优化数据绑定的微妙之处,以及分析和精炼我们的应用程序包的重要性。这些技术不仅仅是理论上的;它们具有实际意义,可以极大地改善你应用程序的用户体验。

为什么这如此关键?在快节奏的数字时代,每一毫秒都很重要。用户期望无缝、闪电般快速的应用程序,即使是微小的延迟也可能影响用户保留率和满意度。通过实施我们讨论过的策略,你不仅提高了应用程序的性能;你还确保了用户获得最佳体验。

当我们过渡到下一章时,我们将把重点转向构建可重用组件,这些组件可以轻松集成和适应我们应用程序的各个部分。这将使你能够编写既高效又模块化、易于维护的代码。因此,准备好开始另一段激动人心的旅程,我们将深入探讨构建多才多艺的 Angular 组件的最佳实践和技术。




# 第十一章:创建可重用和可扩展组件

在现代网络开发中,可维护性和效率的本质在于能够制作出可以轻松重用和扩展的组件。这不仅简化了开发过程,还确保了应用程序的一致性和可维护性。因此,在本章中,我们将探讨利用 PrimeNG 构建块在 Angular 应用程序中创建可重用和可扩展组件的概念。

虽然 PrimeNG 提供了大量的预构建可重用组件,但在某些情况下,创建自定义 Angular 组件变得必要。自定义组件提供定制功能,允许您实现特定的应用程序需求并与现有代码集成。它们还支持 UI 定制,确保独特的视觉设计和用户界面。此外,自定义组件允许进行性能优化,并满足可能未由预构建组件覆盖的特定领域需求。通过权衡定制与开发工作之间的权衡,您可以利用 PrimeNG 的提供和自定义组件来构建高度适应性和高效的 Angular 应用程序。

在本章中,我们的目标是让您掌握设计能够轻松集成到各种项目、根据需求进行适应并扩展新功能的组件所需的技能。这种知识至关重要,因为它允许快速开发而不牺牲代码质量。

本章将涵盖以下主题:

+   介绍可重用和可扩展组件

+   了解 StyleClass

+   利用 PrimeBlocks 创建 UI 块

+   创建可重用和可扩展组件

+   设计您自己的组件

# 技术要求

本章包含各种工作代码示例,说明如何创建可重用和可扩展的组件。您可以在以下 GitHub 仓库的 `chapter-11` 文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-11`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-11)。

# 介绍可重用和可扩展组件

在 Angular 中,可重用和可扩展组件的核心是建立在 Angular 组件本身的基础之上。唯一的区别是我们以允许我们在应用程序的不同部分轻松重用和扩展功能的方式构建它们。通过结合可重用性和可扩展性,我们可以创建促进代码重用、可维护性和灵活性的组件。

在本节中,我们将探讨它们的重要性以及构建 Angular 组件的最佳实践。

## 为什么可重用和可扩展组件很重要?

可重用和可扩展组件在开发过程中提供了几个好处:

+   首先,它们通过将复杂功能分解成更小、更可重用的单元来促进代码组织和模块化。这提高了代码的可维护性,并使得调试和测试单个组件更容易。

+   其次,可重用和可扩展的组件通过允许开发者基于现有解决方案进行构建而不是从头开始,从而提高开发效率。这在需要在大规模应用程序中在多个地方实现类似功能时,可以节省时间和精力。

+   最后,可重用和可扩展的组件通过确保在整个应用程序中一致地使用常见的 UI 元素和功能,从而有助于提供一致的用户体验。这促进了用户与系统不同部分交互时的熟悉度和可用性。

注意

优化重用性可能是一种反模式——有时维护两个在不同上下文中使用的视觉上完全相同的组件更容易。随着需求的持续演变,组件可能会增加复杂性以覆盖每个上下文的各个情况。

## 创建可重用和可扩展组件的步骤

在 Angular 中创建可重用和可扩展的组件涉及几个步骤。以下是这个过程的一般概述:

1.  **识别常见模式**:在深入代码之前,彻底分析您的应用程序以识别在多个组件或页面中出现的重复 UI 模式和功能。

1.  **设计时考虑输入和输出**:计划和实现您的组件以使其灵活和适应性强,定义允许数据从父组件传递到组件的输入属性,并指定当组件内部发生某些操作或变化时,向父组件发出事件的输出属性。

1.  **保持样式一致但可定制**:利用 Angular 的封装样式确保组件样式不影响应用程序的其他部分。然而,还应考虑提供定制选项,例如 CSS 自定义属性,以便组件的用户在需要时可以轻松覆盖或修改其样式。

1.  **编写全面的测试**:开发一套全面的测试套件以验证组件的功能和行为。包括单元测试来评估组件逻辑的各个部分,集成测试来验证其与其他组件或服务正确工作的能力,以及端到端测试来模拟真实用户交互。

1.  **彻底文档化**:为您的组件创建清晰和全面的文档。解释其目的,提供如何使用的分步说明,并在必要时提供有关扩展或定制组件的指导。包括示例、代码片段和任何相关的 API 引用,以帮助用户在项目中有效地使用组件。

## Angular 组件最佳实践

在 Angular 中构建组件时,遵循最佳实践以最大化其有效性非常重要。以下是一些需要考虑的关键实践:

+   **智能组件和展示组件**:Angular 通过使用智能组件和展示组件来促进关注点的分离。智能组件处理逻辑和数据管理,而展示组件则专注于渲染用户界面并接收输入。这种分离使得代码组织更加合理,并促进了展示组件在不同智能组件之间的重用。

+   **组件组合**:组件组合涉及通过组合较小的、可重用的组件来创建组件。这种方法鼓励构建具有单一职责的组件,使其更容易理解、测试和重用。通过组合组件,您可以利用每个组件的优势,并通过组合它们来创建更复杂的功能。

+   **输入和输出**:在组件中使用输入和输出允许父组件和子组件之间进行轻松通信。输入使数据从父组件传递到子组件成为可能,而输出则便于子组件向父组件发射事件。这种交互使得组件更加灵活和适应不同的环境和需求。

+   **模板和样式封装**:Angular 提供了封装组件模板和样式的机制,以防止意外的样式冲突并保持组件的隔离。在组件内封装模板和样式确保它们是自包含的,并且可以轻松重用,而不会影响应用的其他部分。

通过创建封装特定功能且可轻松在不同应用部分复用的组件,我们促进了代码的可重用性和可维护性。

现在,让我们转向探索`StyleClass`功能,它允许我们以灵活和动态的方式进一步自定义和样式化我们的组件。

# 了解 StyleClass

PrimeNG **StyleClass**是 PrimeNG 库为 Angular 应用提供的一个强大功能。它允许您声明式地管理 CSS 类,这使得应用动态样式、处理动画和切换元素上的类变得更容易。此功能增强了 Angular 组件的灵活性和可定制性,使您能够创建视觉上吸引人且交互性强的用户界面。

## 为什么使用 PrimeNG StyleClass?

PrimeNG StyleClass 在处理 Angular 中的自定义组件时特别有用。它提供了一种简单高效的方法来根据各种条件或事件应用样式和操作类。使用 PrimeNG StyleClass,您可以动态更改组件的外观和行为,从而提升用户体验并增加应用的交互性。

PrimeNG StyleClass 的一个关键优点是它能够在组件转换期间处理动画。通过定义进入和离开类,您可以在组件显示或隐藏时创建平滑且视觉上令人愉悦的动画。这可以极大地提升整体用户体验,并使应用程序感觉更加精致和专业。

PrimeNG StyleClass 的另一个优点是其简单性和易用性。您可以使用选择器属性定义目标元素,这使得切换类或添加动画而无需创建自己的自定义函数变得更容易。

为了进一步参考,您可以在 [`primeng.org/styleclass`](https://primeng.org/styleclass) 上探索 StyleClass 属性和关键词的完整列表。现在,让我们深入研究一些实际示例,展示如何有效地在项目中使用 StyleClass。

## 示例:切换类

让我们考虑一个示例,其中我们想要创建一个基于用户交互切换暗黑模式的自定义 Angular 组件。我们可以使用 PrimeNG StyleClass 以干净和可维护的方式实现此功能。让我们看看代码:

```js
<p-button
    pStyleClass="@next"
    toggleClass="dark-mode"
    label="Toggle Dark Mode"
/>
<p>
    <!-- CONTENT --->
</p>
```

让我们分解每个元素及其用途:

+   `pStyleClass="@next"`:具有值 `@next` 的 `pStyleClass` 指令表示应将样式类应用于下一个兄弟元素。

+   `toggleClass="dark-mode"`:此属性指定当按钮被点击时应该切换的类。在这种情况下,类名为 `dark-mode`。

总体而言,代码片段展示了 PrimeNG 的 `<p-button>` 组件使用 `pStyleClass`、`toggleClass` 和 `label` 属性的用法。它表明按钮用于通过应用或移除 `dark-mode` 类到下一个元素来切换暗黑模式。

下面是结果:

![图 11.1 – 使用 StyleClass 切换暗黑模式](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_01.jpg)

图 11.1 – 使用 StyleClass 切换暗黑模式

点击添加 `dark-mode` 类后,元素将以暗黑模式渲染。

## 示例:动画

除了应用类和样式外,PrimeNG StyleClass 还支持在组件转换期间进行动画。这可以通过指定 `enter` 和 `leave` 类及其对应的 `active` 和 `completion` 类来实现。

让我们考虑一个示例,其中我们想要为自定义 Angular 组件创建淡入和淡出动画:

```js
<button
    pButton
    label="Show"
    pStyleClass=".box"
    enterClass="hidden"
    enterActiveClass="fadein"
></button>
<button
    pButton
    label="Hide"
    pStyleClass=".box"
    leaveActiveClass="fadeout"
    leaveToClass="hidden"
></button>
<div class="hidden animation-duration-500 box">
    <p>
     <!-- CONTENT --->
    </p>
</div>
```

让我们分解代码:

+   `<button pButton ...>`:这是 PrimeNG 按钮组件,在点击后会触发动画动作。

+   `pStyleClass=".box"`:此指令针对具有 `box` 在类名中的元素。

+   `enterClass="hidden"`:这定义了当按钮被点击且框内容开始在屏幕上出现时,应针对的类 `hidden`。

+   `enterActiveClass="fadein"`:这指定了在相关元素的 `enter` 动画期间应添加的类 `fadein`。

+   `leaveActiveClass="fadeout"`:这定义了在按钮点击时添加到`leave`动画期间的`fadeout`类;之后,相关元素开始变得隐藏。

+   `leaveToClass="hidden"`:这指定了在盒内容`leave`动画消失时添加的`hidden`类。

深入理解`StyleClass`后,我们解锁了在应用中创建视觉上统一且吸引人的组件的潜力。现在,让我们过渡到使用 PrimeBlocks 来创建 UI 块,我们将利用预先设计的块来进一步加速我们的 UI 开发过程。

# 利用 PrimeBlocks 创建 UI 时钟

由 PrimeNG 开发的`PrimeFlex`。这些块旨在通过提供在 Web 应用中常用的一套预定义 UI 元素来简化开发过程。PrimeBlocks 提供各种 UI 块,包括**导航栏**、**面包屑**、**标签页**、**页脚**、**通知**、**对话框**等。这些 UI 块高度可定制,并且可以轻松集成到您的 Angular 项目中:

![图 11.2 – PrimeBlocks 选项](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_02.jpg)

图 11.2 – PrimeBlocks 选项

PrimeBlocks 提供免费版和付费版,具有不同的功能。免费版提供有限的选项供选择,而付费版则提供超过 400 个预定义 UI 块的广泛选择。除了 UI 块的代码外,付费版还包括如 Figma 文件、终身支持和无限更新等有价值的额外功能。

注意

重要的是要记住,付费版有许可限制,禁止在代码公开可访问的开源项目中使用这些块。

在下一节中,我们将探讨利用 PrimeBlocks 的优缺点,以提供一个全面的概述,包括其优势和考虑因素。

## 使用 PrimeBlocks 的优缺点

与自定义 Angular 组件相比,PrimeBlocks 提供了几个优点:

+   **快速原型设计**:PrimeBlocks 是原型设计或创建概念验证应用的绝佳选择。该库提供多种组件,可以轻松组合以创建功能性的 UI。通过利用 PrimeBlocks,您可以快速创建交互式原型,以收集利益相关者的反馈或验证设计概念。这允许更快地迭代和改进应用的用户界面。

+   **一致的用户体验**:PrimeBlocks 在整个应用中提供一致且专业的用户体验。组件遵循 PrimeNG 设计语言,确保在不同屏幕和应用的各个部分中保持统一的视觉和感觉。这种一致性通过减少认知负荷和提高可用性来增强用户体验。通过使用 PrimeBlocks,您可以创建一个视觉上吸引人且直观的界面,符合行业最佳实践。

+   **定制和可扩展性**:尽管是预构建的 UI 块,但 PrimeBlocks 提供了高水平的定制和可扩展性。每个块都附带一组可配置选项和样式类,允许你根据特定要求调整组件。你可以轻松地自定义颜色、大小、布局和行为,以匹配你的应用程序的品牌和设计指南。

虽然 PrimeBlocks 提供了许多好处,但还有一些潜在的缺点需要考虑。这包括有限的定制选项,因为 UI 块是预先设计的,并且依赖于外部库或框架,许可限制禁止在开源项目中使用,以及实施块可能存在的学习曲线。将考虑这些因素与你的项目需求相对比,将有助于确定 PrimeBlocks 是否适合你的需求。

## 如何使用 PrimeBlocks

由于 PrimeBlocks 提供包含 HTML 元素的 UI 块,你只需将它们复制并粘贴到你的 Angular 组件中。在本节中,我将展示使用 PrimeBlocks 免费版本的示例。以下是一些可用的块:

+   **英雄块**:英雄块是网页顶部的突出区域,通常包含引人注目的图像或视频,以及简洁的标题和行动号召。它旨在吸引访客的注意力,并为内容创造一个视觉上有影响力的介绍。

+   **功能块**:功能块突出显示产品、服务或网站的关键功能或功能。它通常以视觉吸引人的方式展示这些功能,使用户更容易理解和评估所提供的方案。

+   **定价块**:定价块通常用于提供不同定价层级或计划的网站。它显示了各种定价选项,包括与每个计划相关的功能和好处,使用户能够比较和选择最合适的选项。

+   **行动号召块**:**行动号召**(**CTA**)块旨在提示用户采取特定行动,例如注册、购买或订阅通讯。它通常包括一个有说服力的信息,以及一个突出的按钮或链接来鼓励用户参与。

+   **横幅块**:横幅块通常放置在网页的顶部或底部,是一个水平的区域。它通常包含重要的公告或促销内容,以吸引用户的注意力并传达关键信息。

让我们看看如何使用它们。

### 开始使用

在开始之前,请确保你已经安装了正确的 PrimeNG 版本(v11.4.4+)和 PrimeFlex 版本(v3.2.0+)。如果你还没有安装 PrimeFlex,你可以输入以下命令来安装它:

```js
npm install primeflex --save
```

然后,你可以将 `primeflex.scss` 添加到 `styles.scss` 文件中:

```js
@import 'primeflex/primeflex.scss';
```

### 选择你的块

准备工作完成后,你可以实现块:

1.  在 PrimeBlocks 网站上,导航到**免费块**部分([`blocks.primeng.org/#/free`](https://blocks.primeng.org/#/free))并选择你想要实现的块。在这个例子中,我将选择**行动号召**块,你可以在这里看到:

![图 11.3 – PrimeBlocks 行动号召](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_03.jpg)

图 11.3 – PrimeBlocks 行动号召

1.  点击**代码**按钮,你将看到准备使用的代码:

![图 11.4 – PrimeBlocks 行动号召代码](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_04.jpg)

图 11.4 – PrimeBlocks 行动号召代码

1.  我们需要做的只是将代码复制粘贴到我们的应用程序中,它就会像魔法一样工作:

![图 11.5 – 实现行动号召代码](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_05.jpg)

图 11.5 – 实现行动号召代码

在深入研究 PrimeBlocks 的功能后,我们看到了这些构建块如何简化我们的 UI 开发,确保一致性并节省时间。它们为快速创建视觉吸引力和功能性的用户界面提供了一个坚实的基础。然而,有时我们的项目需要更定制化的方法。在下一节中,我们将探讨如何创建符合我们独特需求的组件,同时保持可重用性和可扩展性。

# 创建可重用和可扩展的组件

作为一名开发者,我们的一个关键目标是编写干净、可维护和可重用的代码。在本节中,我们将探讨创建可重用和可扩展组件的概念。我们将以 PrimeBlocks 中的一个块为例,讨论我们如何使其更加灵活和适应不同的用例。

这里是 PrimeBlocks 中的**统计数据**块,包含四个不同的统计数据:

![图 11.6 – PrimeBlocks 统计数据](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_06.jpg)

图 11.6 – PrimeBlocks 统计数据

在点击**代码**按钮后,我们可以看到四个不同的元素对应四个统计数据,这些可以减少并重用:

![图 11.7 – PrimeBlocks 统计数据代码](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_07.jpg)

图 11.7 – PrimeBlocks 统计数据代码

提供的图像展示了 UI 中的四个统计数据(从*1*到*4*进行标记),每个都是使用几乎相同的 HTML 块创建的。这种方法导致了重复,使得维护变得具有挑战性。例如,如果你需要做出样式更改,你将不得不在四个不同的地方修改代码。这种冗余可能导致更新或改进 UI 时增加工作量并可能产生不一致性。

在检查标记为*1*的设计和代码时,我们可以查看元素之间的相似性并确定所有标签的模式:

![图 11.8 – PrimeBlocks 统计数据模式](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_08.jpg)

图 11.8 – PrimeBlocks 统计数据模式

在这里,我们将模板提取为可重用和可定制的属性,允许轻松修改和灵活性:

+   `Orders`: `title`

+   `152`: `count`

+   `pi` `pi-shopping-cart`: `icon`

+   `bg-blue-100`: `iconBackground`

+   `24` `new`: `newCount`

+   `since last` `visit`: `newCountMessage`

一旦收集到这些信息,就会提取模式。然后我们可以创建一个如以下示例所示的可重用展示组件:

```js
@Component({
  selector: 'primengbook-stat',
  standalone: true,
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="surface-card shadow-2 p-3 border-round">
      <div class="flex justify-content-between mb-3">
        <div>
          <span class="block text-500 font-medium mb-3">{{ title }}</span>
          <div class="text-900 font-medium text-xl">{{ count }}</div>
        </div>
        <div
          class="flex align-items-center justify-content-center
{{ iconBackground }} border-round"
          style="width:2.5rem;height:2.5rem"
        >
          <i class="{{ icon }} text-blue-500 text-xl"></i>
        </div>
      </div>
      <span class="text-green-500 font-medium">{{ newCount }} </span>
      <span class="text-500">{{ newCountMessage }}</span>
    </div>
  `
})
export class StatComponent {
  @Input() title: string
  @Input() count: string
  @Input() icon: string
  @Input() iconBackground: string
  @Input() newCount: string
  @Input() newCountMessage: string
}
```

提供的代码是一个名为 `StatComponent` 的 Angular 独立组件。让我们分解其不同部分并了解它们的作用:

+   `selector: 'primengbook-stat'`: 这个属性指定了将用于在模板中表示此组件的 HTML 选择器。在这种情况下,选择器是 `primengbook-stat`,这意味着此组件可以在模板中作为 `<primengbook-stat />` 使用。

+   `changeDetection: ChangeDetectionStrategy.OnPush`: 这种变更检测策略告诉 Angular 只检查组件输入属性的变化,除非外部触发,否则不执行完整的变更检测周期。

+   `@Input()...`: 这个装饰器用于标记某些属性作为组件的输入。这些输入可以从组件的父组件或使用组件的模板中接收值。在这种情况下,组件有几个输入属性:`title`、`count`、`icon`、`iconBackground`、`newCount` 和 `newCountMessage`。

在我们的可重用组件准备就绪后,我们可以通过将其放入 `imports` 数组中在任何地方使用它:

```js
@Component({
  standalone: true,
  imports: [CommonModule, StatComponent],
  template: `
    <h2>Reusable Components</h2>
    <div class="surface-ground px-4 py-5 md:px-6 lg:px-8">
      <div class="grid">
        <primengbook-stat
          *ngFor="let stat of stats"
          [title]="stat.title"
          [count]="stat.count"
          [icon]="stat.icon"
          [iconBackground]="stat.iconBackground"
          [newCount]="stat.newCount"
          [newCountMessage]="stat.newCountMessage"
          class="col-12 md:col-6 lg:col-3"
        />
      </div>
    </div>
  `,
})
export default class ReusableComponent {
  stats = [
    {
      title: 'Orders',
      count: '152',
      icon: 'pi pi-shopping-cart',
      iconBackground: 'bg-blue-100',
      newCount: '24 new',
      newCountMessage: 'since last visit',
    },
    {
      title: 'Revenue',
      count: '$2.100',
      icon: 'pi pi-shopping-cart',
      iconBackground: 'bg-orange-100',
      newCount: '%52+',
      newCountMessage: 'since last visit',
    },
  ]
}
```

提供的代码是一个名为 `ReusableComponent` 的 Angular 独立组件。让我们分解其不同部分并了解它们的作用:

+   `imports: [CommonModule, StatComponent]`: `imports` 属性是一个数组,指定了此组件所需的模块。在这种情况下,`CommonModule` 被导入以提供常见的 Angular 指令和管道,而 `StatComponent` 被导入作为依赖项。

+   `stats = [...]`: `stats` 属性是一个包含 `title`、`count`、`icon`、`iconBackground`、`newCount` 和 `newCountMessage` 属性的对象数组。这些属性持有将传递给 `StatComponent` 以进行渲染的值。

+   `<primengbook-stat *ngFor="let stat of stats" ... />`: 这段代码将允许你遍历 `stats` 数组,并渲染 `primengbook-stat` 元素及其属性绑定。例如,`[title]="stat.title"` 将 `primengbook-stat` 组件的 `title` 属性绑定到当前 `stat` 对象的 `title` 属性。

这里是结果:

![图 11.9 – 可重用 StatComponent](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_09.jpg)

图 11.9 – 可重用 StatComponent

而不是使用重复的代码块(如*图 11.7*所示),使用可重用的组件,例如`primengbook-stat`,可以带来几个好处,包括代码可重用性、一致的外观和行为,以及增强的可维护性。通过封装功能和外观,组件可以轻松地在整个应用程序中重用,减少代码重复,并促进模块化代码库。它确保了应用程序不同部分的一致样式和交互,并通过允许在一个地方进行更新来简化维护。总的来说,它提高了开发效率,并提供了更好的用户体验。

在我们探索创建可重用和可扩展组件的过程中,我们发现了根据我们的特定需求定制组件的细微差别,同时不牺牲其适应性。这种知识使我们能够构建更高效、更易于维护的 Angular 应用程序。随着我们过渡到下一部分,我们将深入了解如何从头创建组件,确保它们与项目独特的需求完美匹配。

# 使用 PrimeNG 制作自己的组件

创建自己的组件是现代网络开发的基本方面。虽然预构建组件可能很方便,但在某些情况下,构建自定义组件是满足特定要求所必需的。在本部分中,我们将探讨创建自定义组件的原因,并学习如何利用 PrimeNG 来制作您的自定义组件。

注意

总是确保您的自定义组件是可访问的、响应式的和用户友好的。在不同的设备和浏览器上测试它们,以确保一致的用户体验。

## 为什么创建自定义组件?

预构建组件,如 PrimeNG、Bootstrap 或 Material 提供的,非常出色。它们节省时间,确保一致性,并且通常带有内置的访问性功能。然而,在某些情况下,它们可能不是完美的选择:

+   **独特的设计要求**:您的应用程序可能有一个与预构建组件的样式不一致的设计。在这种情况下,自己制作确保 UI 与您的设计指南保持一致。

+   **特定的功能需求**:预构建组件提供了一般功能。如果您的应用程序需要具有非常特定行为的组件,通常从头开始构建比修改预构建组件更容易。

+   **性能**:自定义组件可以根据您的应用程序特定需求进行优化,在某些情况下可能提供更好的性能。

+   **代码可维护性和可重用性**:通过在定义良好的组件中封装特定的功能,您可以模块化您的代码库。这种模块化方法使得管理和维护应用程序的代码变得更加容易,因为每个组件都可以独立地进行开发、测试和更新。

## 示例:利用 PrimeNG 创建登录组件

PrimeNG 提供了大量的 UI 组件和实用工具,可以帮助您创建自定义组件。在继续创建组件之前,考虑以下因素是至关重要的:

+   **准备好设计**:假设组件的设计已经使用设计工具(如 Figma 或 Sketch)准备就绪。这确保了有一个清晰的视觉参考来指导实现过程。

+   **表单和提交逻辑**:在此示例中,重点将放在组件结构和功能上,而不是实际的表单提交。实现将省略与表单提交相关的特定逻辑。

+   **使用 PrimeFlex 进行样式设计**:组件的样式将通过 PrimeFlex 实用类来实现。这些实用类提供了一种方便且一致的方式来应用样式和布局选项,确保设计的一致性和响应性。

+   **账户创建和密码重置链接**:本示例将不包括创建新账户或重置忘记的密码的链接实现。重点将放在组件本身的核心功能上。

考虑这些点有助于设定上下文并明确组件创建过程的范围。这确保了关注点始终集中在组件的结构、功能以及集成上,同时承认将不包括此特定示例的设计、样式和特定功能。

现在,让我们使用 PrimeNG 创建一个登录组件。首先,使用 Angular CLI 生成一个新的组件:

```js
ng g c sign-in 
```

然后,在 `sign-in.component.ts` 文件中,构建用户界面:

```js
@Component({
  selector: 'primengbook-sign-in',
  standalone: true,
  imports: [CommonModule, ButtonModule, InputTextModule, CheckboxModule],
  template: `
    <div class="surface-card p-4 shadow-2 border-round w-full lg:w-6">
      ...
      <div>
        <div class="p-inputgroup mb-3">
          <span class="p-inputgroup-addon">
            <i class="pi pi-user"></i>
          </span>
          <input id="username" type="text" pInputText placeholder="Username" />
        </div>
        <div class="p-inputgroup mb-3">
          <span class="p-inputgroup-addon">
            <i class="pi pi-lock"></i>
          </span>
          <input
            id="password"
            type="password"
            pInputText
            placeholder="Password"
          />
        </div>
        <div class="flex align-items-center justify-content-between mb-5">
          <div class="flex align-items-center">
            <p-checkbox
              id="rememberme"
              [binary]="true"
              class="mr-2"
            ></p-checkbox>
            <label for="rememberme" class="text-900">Remember me</label>
          </div>
          <a
            href="/forgot-password"
            class="font-medium no-underline ml-2 text-blue-500 text-right cursor-pointer"
            >Forgot password?</a
          >
        </div>
        <button pButton type="button" label="Sign In" class="w-full"></button>
      </div>
    </div>
  `,
})
export class SignInComponent {}
```

让我们分解代码并解释其功能:

+   `imports: [..., ButtonModule, InputTextModule, CheckboxModule]`:这一行指定了组件正确运行所需的模块。在这种情况下,组件需要从 PrimeNG 导入 `ButtonModule`、`InputTextModule` 和 `CheckboxModule` 模块。

+   `template: '...'`:这是一个 HTML 模板,由各种元素和类组成,这些元素和类定义了登录表单的视觉结构和行为,例如用户名字段、密码字段、记住我功能、密码重置以及提交按钮。

现在,要在您的应用程序中使用登录组件,打开目录中的任何组件/模板,并使用以下代码更新其内容:

```js
<primengbook-sign-in />
```

下面是结果:

![图 11.10 – 登录组件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_11_10.jpg)

图 11.10 – 登录组件

我们已成功使用 PrimeNG 组件渲染了登录组件。

总结来说,自己动手制作组件赋予你构建定制解决方案的能力,这些解决方案能够完美满足你应用的需求。通过利用 Angular 的组件架构和 PrimeNG 丰富的组件和功能集,你可以创建模块化、可扩展和可定制的元素,从而提升用户体验。现在,让我们继续到章节总结部分,我们将回顾本章涵盖的关键点,并强调本章的重要收获。

# 摘要

在我们的旅程中,我们深入研究了创建可重用和可扩展组件的领域。本章的核心是赋予你知识和技巧,以便你可以制作出可以在应用的多个部分或甚至在不同项目中重用的组件,同时保持根据需要扩展和定制的灵活性。

在本章中,我们探讨了尽管有大量的预构建组件可用,但为何我们仍然需要自己动手制作组件。无论是为了满足独特的设计要求、满足特定的功能需求,还是优化性能,自定义组件在我们的开发者工具箱中都有其位置。

此外,使用 PrimeNG,我们看到了如何增强我们的自定义组件。从创建自定义组件到理解 PrimeBlocks 和 StyleClass 的强大功能,PrimeNG 证明是一个无价的资产。

随着我们转向下一个主题,我们将深入探讨国际化与本地化。这将是一次激动人心的探索,旨在使我们的应用成为全球友好的,确保它们能够满足各种语言和区域偏好。这关乎提供无缝的用户体验,无论我们的用户来自何方或他们使用什么语言。所以,随着我们踏上新的旅程,让我们使我们的应用真正成为全球性的。




# 第十二章:使用国际化与本地化进行操作

在数字时代,应用程序被来自世界各地的用户访问。为了满足全球受众的需求,不仅仅是翻译内容;它还涉及到提供无缝的用户体验,尊重文化细微差别和用户偏好。确保您的应用程序在字面意义上和比喻意义上都能使用户的语言,可以显著提高用户满意度和参与度。

因此,在本章中,我们的主要目标是为您提供必要的知识和工具,使您的应用程序具有普遍的可用性和用户友好性。我们将通过深入探讨国际化与本地化的领域,指导您使用 PrimeNG 组件使您的 Angular 应用程序成为全球友好的。到本章结束时,您将能够熟练配置语言支持,确保您的应用程序无论用户的地理或文化背景如何,都能引起用户的共鸣。

本章将涵盖以下主题:

+   介绍国际化与本地化

+   探索 Angular 的流行国际化库

+   使用 ngx-translate 进行国际化

+   使用 PrimeNG Locale 进行操作

# 技术要求

本章包含各种关于国际化和本地化的工作代码示例。您可以在以下 GitHub 仓库的 `chapter-12` 文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-12`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-12)。

# 介绍国际化与本地化

在全球网络开发领域,确保应用程序满足全球受众的需求至关重要。这就是**国际化**(**i18n**)和**本地化**(**l10n**)发挥作用的地方,尤其是在**Angular**应用程序的背景下。

## 在 Angular 应用程序中介绍国际化(i18n)

国际化,通常缩写为 i18n(因为“i”和“n”之间有 18 个字母),是将您的应用程序设计并准备为在不同语言中使用的过程。例如,i18n 需要在产品设计时考虑语言和文化差异,包括使用**Unicode**字符编码、避免硬编码文本,并允许足够的文本扩展空间。

注意

Unicode 提供了通用的字符编码,这意味着每个字符都被分配了一个唯一的代码点,无论平台、语言还是应用程序。它确保软件可以处理和显示不同语言和脚本中的文本,使应用程序在多样化的语言和文化环境中可访问和可用。您可以在[`unicode.org`](https://unicode.org)了解更多信息。

有许多 Angular 库可以支持这一点,允许你定义内容翻译并在它们之间无缝切换。我们很快就会看看这些库。

## 理解本地化(l10n)在创建多语言体验中的作用

本地化,简称为 l10n(因为“l”和“n”之间有 10 个字母),是 i18n 的后续步骤。它涉及通过添加特定区域或语言的本地化翻译和调整格式来适应国际化的应用程序。本质上,虽然 i18n 是关于使应用程序可翻译的,但 l10n 是关于实际翻译和适应的。

在 Angular 中,一旦提供了翻译,就可以使用这些翻译来编译应用程序,以生成特定语言或地区的应用程序版本。

## 设计国际化友好应用程序的挑战和考虑因素

创建一个能够引起全球受众共鸣的应用程序并非没有挑战:

+   **文本扩展**:某些语言可能对相同内容的翻译更长。例如,在英语中,“I’m happy”可以翻译成越南语的“*Tôi đang cảm thấy hạnh phúc*”;如果不妥善处理,这可能会影响 UI 布局。

+   **从右到左的语言**:如阿拉伯语或希伯来语这样的语言是从右到左书写的,这可能需要重大的布局调整。例如,原本在左侧的菜单项需要移动到右侧在阿拉伯语中。

+   **文化细微差别**:颜色、符号和图像在不同地区可能有不同的文化含义。例如,竖起大拇指的手势在西方文化中通常用来表示赞同,但在一些其他国家可能会被视为冒犯或不恰当。

+   **日期、时间和数字格式**:不同地区显示日期、时间和数字的格式不同。例如,在美国,日期格式通常是“mm/dd/yyyy”,而在许多欧洲国家,则是“dd/mm/yyyy”。

+   **翻译管理**:有效地管理翻译可能具有挑战性,尤其是在具有大量文本字符串的大规模应用程序中。例如,使用翻译管理系统可以提供诸如翻译记忆等特性,其中以前翻译的短语被存储和重复使用,从而减少翻译重复内容所需的时间和精力。

重要提示

总是注意文化细微差别;在一个文化中可以接受或中性的东西,在另一个文化中可能就是冒犯的。

当使用 Angular 时,一开始就了解这些挑战是有益的。通过这样做,你可以设计你的应用程序,以最小化在添加对新语言或地区支持时可能出现的潜在问题。

# 探索 Angular 的流行国际化库

i18n 是构建面向全球受众的 Angular 应用程序的关键方面。幸运的是,有几个可靠的 i18n 库可供 Angular 使用,它们简化了翻译和本地化的过程。在本节中,我们将探讨一些流行的 Angular i18n 库,包括 `@angular/localize`、`ngx-translate`、`@ngneat/transloco` 和 `angular-i18next`。

下面是这些库在 NPM 趋势中的当前统计信息:

![图 12.1 - 来自 NPM 趋势的 Angular i18n 库统计信息](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_01.jpg)

图 12.1 - 来自 NPM 趋势的 Angular i18n 库统计信息

从趋势来看,`@angular/localize` 和 `ngx-translate` 是主要玩家,每天大约有 50 万次下载,而 `@ngneat/transloco` 大约有 10 万次下载,`angular-i18next` 大约有 1.3 万次下载。最新的更新也已经对 `@angular/localize` 和 `@ngneat/transloco` 进行了。

重要提示

考虑与第三方库相关的安全漏洞和更新是选择第三方库时的关键方面。您可以通过参考 [`snyk.io/advisor`](https://snyk.io/advisor) 来评估这些因素。

让我们比较它们的特性,讨论它们的优缺点,并提供不同场景下的推荐。

## @angular/localize

`@angular/localize` 利用 Angular 的编译器从模板中提取和替换可翻译文本。以下是其主要特性:

+   `@angular/localize` 通过静态分析模板在构建过程中执行翻译。这种方法允许高效翻译并消除了对运行时翻译库的需求。

+   `ng extract-i18n`) 从应用程序的模板中提取可翻译文本并生成翻译文件。这些文件随后可以被语言专家进行翻译。

+   `@angular/localize` 通过在模板表达式中提供特殊语法支持复数和性别一致。这允许在不同语言环境中进行准确的翻译。

尽管有一些缺点:

+   在构建过程中需要编译步骤,这可能会增加大型应用程序的构建时间

+   对动态内容翻译和运行时语言切换的支持有限

通常,`@angular/localize` 适用于在构建过程中优先考虑高效翻译且不需要动态翻译或运行时语言切换的项目。

## ngx-translate

**ngx-translate** ([`github.com/ngx-translate/core`](https://github.com/ngx-translate/core)) 是一个流行的 Angular 第三方 i18n 库,为 Angular 应用提供灵活且功能丰富的翻译解决方案。以下是其一些显著特性:

+   `ngx-translate` 在运行时执行翻译,允许动态语言切换和即时翻译更新。

+   **翻译加载**:翻译可以从各种来源加载,包括 JSON 文件、API 甚至内联定义。这种灵活性使得与不同的翻译管理系统集成变得容易。

+   `ngx-translate` 支持翻译中的复数形式和变量替换,提供了丰富的语言特定功能。

这里有一些缺点:

+   分割翻译效果不佳,设置复杂

+   在运行时加载翻译可能会导致**内容闪烁**(**FOC**)效果,即翻译被加载并应用,可能会造成瞬间的视觉闪烁

通常,`ngx-translate`适用于需要动态翻译更新、运行时语言切换和灵活翻译加载的工程。

## @ngneat/transloco

**@ngneat/transloco** ([`ngneat.github.io/transloco`](https://ngneat.github.io/transloco)) 是一个相对较新的 Angular 国际化库,旨在提供灵活且可扩展的翻译方法。它提供了独特的功能和现代的 i18n 方法。以下是它的主要特性:

+   `@ngneat/transloco` 支持翻译文件的懒加载,允许优化翻译的加载。这对于具有大量可翻译文本的大型应用程序尤其有益。

+   `@ngneat/transloco` 允许将翻译范围限定到特定的组件或模块。当应用程序的不同部分需要不同的翻译集时,这个特性非常有用。

+   `@ngneat/transloco` 提供了对内联翻译的支持,允许开发者直接在模板中定义翻译。这个特性简化了翻译过程,减少了单独翻译文件的需求。

只有一个主要缺点:

+   与其他成熟的 i18n 库相比,它是一个不太受欢迎的库,社区规模较小。由于社区规模较小,可能可用的资源、教程和示例较少,这可能会帮助开发者入门和解决问题。

通常,`@ngneat/transloco`适用于需要懒加载翻译、范围翻译和现代国际化(i18n)方法的工程。

## angular-i18next

`angular-i18next` ([`github.com/Romanchuk/angular-i18next`](https://github.com/Romanchuk/angular-i18next)) 是一个集成库,它将强大的 `i18next` 库与 Angular 结合。`i18next` 是 JavaScript 生态系统中广泛使用的 i18n 库。以下是 `angular-i18next` 的一些显著特性:

+   `angular-i18next` 利用 `i18next` 的广泛功能集,包括对插值、复数形式、上下文等的支持。

+   **灵活的翻译来源**:翻译可以从各种来源加载,如 JSON、XHR 甚至后端 API。这种灵活性使得与不同的翻译管理系统集成变得容易。

+   `angular-i18next`提供了额外的 l10n 功能,例如日期和数字格式化,这在某些场景中可能很有益。

这里有一些缺点:

+   需要熟悉 Angular 和`i18next`

+   它很复杂,与其它相比,捆绑包大小很大

通常,`angular-i18next`适用于需要广泛 i18n 功能、翻译加载灵活性和额外 l10n 功能的工程项目。

重要提示

重要的是要注意,这些比较和建议并不全面,一个库的适用性可能因具体项目需求和限制而异。在做出决定之前,您应该仔细评估每个库的功能、权衡和社区支持。

在下一节中,我们将使用`ngx-translate`库探索 i18n,该库也在官方 PrimeNG 网站上突出展示。

# 使用 ngx-translate 进行国际化工作

在上一节的基础上,`ngx-translate`提供了一个简单且可适应的方法来翻译 Angular 应用程序。在本节中,我们将深入了解将 ngx-translate 集成到 Angular 应用程序中的步骤,并提供有关如何最大化使用 ngx-translate 生产力的宝贵见解和策略。

## 将 ngx-translate 集成到 Angular 应用程序中

要开始使用`ngx-translate`,请按照以下步骤将其集成到您的 Angular 应用程序中:

1.  首先,安装`ngx-translate`库。打开终端并导航到您的 Angular 项目的根目录。然后,运行以下命令来安装`ngx-translate`:

    ```js
    npm install @ngx-translate/core @ngx-translate/http-loader --save
    ```

    此命令将安装`core`和`http-loader`包,用于使用`HttpClient`初始化和加载翻译文件。

1.  在您的 Angular 应用程序中,打开`app.config.ts`文件,并从`ngx-translate`导入必要的模块和配置:

    ```js
    // translation.provider.ts
    import { HttpClient } from '@angular/common/http'
    import { importProvidersFrom, makeEnvironmentProviders } from '@angular/core'
    import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
    import { TranslateHttpLoader } from '@ngx-translate/http-loader'
    export function HttpLoaderFactory(http: HttpClient) {
      return new TranslateHttpLoader(http)
    }
    export const provideTranslation = () =>
      makeEnvironmentProviders([
        importProvidersFrom(
          TranslateModule.forRoot({
            defaultLanguage: 'en',
            loader: {
              provide: TranslateLoader,
              useFactory: HttpLoaderFactory,
              deps: [HttpClient],
            },
          })
        ),
      ])
    // app.config.ts
    export const appConfig: ApplicationConfig = {
      providers: [
        ...
        provideHttpClient(),
        provideTranslation()
      ],
    }
    ```

    提供的代码负责在 Angular 应用程序中使用`ngx-translate`配置和提供翻译功能。让我们分解它以了解其目的:

    +   `export function HttpLoaderFactory() {...}`:此代码导出一个名为`HttpLoaderFactory`的工厂函数。此函数用于创建`TranslateHttpLoader`类的实例,该类负责使用 HTTP 协议从`/assets/i18n/[lang].json`文件(在这种情况下,lang 是`en`)加载翻译文件。它接受`HttpClient`类的实例作为参数,并返回一个新的`TranslateHttpLoader`实例。

    +   `export const provideTranslation =` `()...`:此函数返回调用`makeEnvironmentProviders`的结果,这是一个由 Angular 提供的实用函数,用于生成提供者。此函数从`TranslateModule.forRoot({...})`方法导入提供者,该方法负责在应用级别配置`ngx-translate`模块,以下为配置:

        +   `defaultLanguage:` `'en'`:此选项表示英语是默认语言。对于较大的项目,建议采用`<language>-<REGION>`模式,例如`en-US`(美国英语)或`en-CA`(加拿大英语)。随着公司的扩张,可能需要满足不同地区相似语言的变体。

        +   `loader:` `{...}`:此属性将提供属性设置为`TranslateLoader`,表示使用`TranslateLoader`类进行翻译加载。`useFactory`属性设置为`HttpLoaderFactory`,这是之前定义的工厂函数。`deps`属性指定了工厂函数所需的依赖项,在这种情况下是`HttpClient`。

    +   `export const appConfig...`:将`provideHttpClient()`和`provideTranslation()`提供者添加到应用程序的依赖注入系统中,使`HttpClient`和翻译功能在整个应用程序中可用。

1.  为每种支持的语言创建翻译文件。例如,为英文翻译创建一个`en.json`文件,为越南文翻译创建一个`vi.json`文件。将这些文件放置在适当的目录中,例如`src/assets/i18n/`。

    翻译文件应遵循键值结构,其中键代表翻译键,值代表翻译文本。以下是一个示例:

    ```js
    // en.json
    {
      "greeting": "Hello!",
      "welcome": "Welcome to our application."
    }
    // vi.json
    {
      "greeting": "Xin chào!",
      "welcome": "Chào mùng đên vói úng dung cúa chúng tôi."
    }
    ```

1.  现在,您可以使用`translate`管道将文本翻译到您的 Angular 模板中。只需将`translate`管道添加到您想要翻译的文本中,并将翻译键作为参数传递。以下是一个示例:

    ```js
    @Component({
      standalone: true,
      template: `
        <h2>Ngx Translate</h2>
        <h1>{{ 'greeting' | translate }}</h1>
        <p>{{ 'welcome' | translate }}</p>
      `,
      imports: [CommonModule, TranslateModule],
    })
    export default class NgxTranslateComponent {}
    ```

    通过导入`TranslateModule`,您将能够访问`translate`管道,该管道将替换翻译键为相应的翻译文本。

让我们看看结果:

![图 12.2 – 使用 ngx-translate 的英文翻译示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_02.jpg)

图 12.2 – 使用 ngx-translate 的英文翻译示例

从图像中,我们可以看到`translate`管道正在正确工作。`greeting`键现在从`en.json`文件变为`Hello!`。

## 使用 ngx-translate 的技巧和窍门

要有效地使用`ngx-translate`,请考虑以下技巧和窍门:

### 语言切换

`ngx-translate`提供了一个方便的方法在运行时切换不同的语言。您可以通过使用`TranslateService`的“use”方法来实现这一点。

以以下`LanguageComponent`为例:

```js
import { TranslateService } from '@ngx-translate/core'
import { DropdownChangeEvent, DropdownModule } from 'primeng/dropdown'
@Component({
  standalone: true,
  selector: 'primengbook-language',
  template: `
    <p-dropdown
      [options]="languages"
      optionLabel="label"
      optionValue="value"
      (onChange)="switchLanguage($event)"
    />
  `,
  imports: [CommonModule, DropdownModule],
})
export class LanguageComponent {
  private translationService = inject(TranslateService)
  languages = [
    {
      label: 'English',
      value: 'en',
    },
    {
      label: 'Tiếng Việt',
      value: 'vi',
    },
  ]
  switchLanguage(event: DropdownChangeEvent) {
    this.translationService.use(event.value)
  }
}
```

提供的代码是一个共享组件,它使用 PrimeNG 的`DropdownModule`和 ngx-translate 的`TranslateService`来表示语言选择下拉菜单。让我们更详细地分析一下:

+   `<p-dropdown ... />`:此代码使用 PrimeNG 的 `p-dropdown` 组件来渲染下拉列表。选项属性绑定到 `languages` 数组,该数组包含语言对象数组。`optionLabel` 属性指定用于每个选项的 `label` 属性,而 `optionValue` 属性指定用于每个选项的 `value` 属性。`(onChange)` 事件绑定在更改下拉选择时调用 `switchLanguage()` 方法,并传递 `event` 对象。

+   `switchLanguage(event) {...}`:当下拉选择更改时调用此方法。它接收类型为 `DropdownChangeEvent` 的 `event` 对象,并使用 `translationService` 将活动语言切换到下拉菜单中选择的值。

这是结果:

![图 12.3 – 切换语言为越南语](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_03.jpg)

图 12.3 – 切换语言为越南语

从图像中,您可以看到当我们从英语切换到越南语时,应用程序动态加载包含翻译的越南语版本的 `vi.json` 文件。因此,更改在运行时得到及时反映。

### 懒加载

`ngx-translate` 提供了一个有用的功能,称为懒加载,允许在需要时按需加载翻译。此功能对于具有多种语言的大型应用程序特别有益,因为它通过仅在需要时加载翻译来帮助优化初始加载时间。

要在 `ngx-translate` 中启用懒加载,您需要修改翻译加载器配置。而不是预先加载所有翻译文件,您可以设置加载器动态加载请求的翻译。以下是一个翻译文件的示例:

![图 12.4 – 重新组织翻译文件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_04.jpg)

图 12.4 – 重新组织翻译文件

根据图像,我们将位于根级别的所有翻译内容转移到 `assets/i18n/main` 目录。至于懒加载组件的翻译文件,我们可以将它们存储在单独的路径中,例如 `assets/i18n/lazy`,这将按需加载。

以下是我们的主要翻译加载器的更新代码:

```js
// translation.provider.ts
export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/main/', '.json')
}
```

您可以看到,加载翻译文件的路径已从 `./assets/i18n` 更改为 `./assets/i18n/main`。

之后,我们为懒加载组件创建翻译配置:

```js
// lazy-translation.provider.ts
function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/lazy/', '.json')
}
export const provideLazyTranslation = () =>
  makeEnvironmentProviders([
    importProvidersFrom(
      TranslateModule.forChild({
        defaultLanguage: 'en',
        isolate: true,
        loader: {
          provide: TranslateLoader,
          useFactory: HttpLoaderFactory,
          deps: [HttpClient],
        },
      })
    ),
  ])
// app.routes.ts
export const appRoutes: Route[] = [
  ...
  {
    path: 'ngx-lazy-translate',
    loadComponent: () => import('./pages/ngx-lazy-translate.component'),
    providers: [provideLazyTranslation()],
  },
]
```

在代码片段中,懒加载翻译的配置与主配置(`translation.provider.ts`)相同。但是,有一些区别:

+   `function HttpLoaderFactory(http: HttpClient) { ... }`:该函数返回一个配置为从 `'./assets/i18n/lazy/'` 目录加载具有 `.json` 文件扩展名的翻译文件的 `TranslateHttpLoader` 的新实例。

+   `TranslateModule.forChild({..})`:此函数用于懒加载模块/路由。它接受一个包含各种配置属性的 `options` 对象:

    +   `isolate: true`: 这表示翻译应该为懒加载的模块隔离,这意味着它们不会与其他模块共享

    +   `useFactory: HttpLoaderFactory`: 这指定了将加载翻译文件的翻译加载器,这些文件来自`HttpLoaderFactory`配置

+   `providers: [provideLazyTranslation()]`: 这指定了此路由的提供者数组。在这种情况下,它包括调用`provideLazyTranslation()`函数的结果,这将触发懒加载翻译过程,如前一个代码解释中所述。

让我们看看结果:

![图 12.5 – 懒加载翻译](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_05.jpg)

图 12.5 – 懒加载翻译

现在,在图片中,你会观察到应用程序的加载行为。在初始加载时,翻译文件(标记为`ngx-lazy-translate`路由),应用程序将动态加载特定于该路由的翻译文件(标记为**2**)并正确显示翻译的值。

### 从右到左的语言

在**从右到左**(**RTL**)语言中,用户界面的布局与**从左到右**(**LTR**)语言相比是镜像的。这包括元素顺序的颠倒,如文本、按钮和导航。主要目标是使内容对齐到屏幕的右侧,为 RTL 读者创造自然的流动。

当使用 i18n 库时,一个常见的做法是在切换语言后设置文本的方向。以下是一个示例代码:

```js
private updateHtmlTag(lang: string) {
  let direction = 'ltr'
  if (['ar'].includes(lang)) {
    direction = 'rtl'
  }
  this.document.getElementsByTagName('html')[0].setAttribute('lang', lang)
  this.document.getElementsByTagName('body')[0].setAttribute('dir', direction)
}
```

提供的代码代表一个方法,该方法根据指定的语言参数更新文档的 HTML 标签属性。默认情况下,文本方向是左到右(`ltr`)。然而,如果语言更改为阿拉伯语(`ar`),代码将更新 body 元素的文本方向为从右到左(`rtl`),并且`html`元素的`lang`属性也将更改为`ar`。

让我们看看结果:

![图 12.6 – 从右到左的语言](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_06.jpg)

图 12.6 – 从右到左的语言

从图片中,你可以看到当我们把语言切换到阿拉伯语(`ar`)后,布局从左到右变为从右到左。

总之,通过使用 ngx-translate,你可以轻松处理多语言支持并在不同语言版本之间动态切换。现在,让我们过渡到探索**PrimeNG Locale**,这是配置和自定义 PrimeNG 组件 l10n 设置的另一个重要工具。

# 与 PrimeNG Locale 一起工作

PrimeNG Locale 是 PrimeNG 提供的一个功能,它允许你配置和自定义其组件的 l10n 设置。它允许你定义特定于区域的配置,如语言、日期格式、时间格式和数字格式。通过使用 PrimeNG Locale,你可以确保你的应用程序满足来自不同地区和文化的用户的需求。

要使用 PrimeNG Locale,有必要理解在 Angular 项目中配置和应用的过程。默认情况下,PrimeNG 仅包含英文翻译的区域设置。要本地化 PrimeNG 组件,需要手动更新翻译。

例如,以下是与 PrimeNG `Calendar`组件对应的区域设置选项及其翻译:

![图 12.7 – 日历组件翻译示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_07.jpg)

图 12.7 – 日历组件翻译示例

根据代码,很明显 PrimeNG 在`Calendar`组件中包含了这些英文翻译的措辞。这意味着在打开`Calendar`组件时,你会看到相关的文本显示:

![图 12.8 – 英文中的日历组件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_08.jpg)

图 12.8 – 英文中的日历组件

在打开日期选择器后,你可以观察到月份和日期来自`monthNames`和`dayNamesMin`选项。

重要提示

PrimeNG 还提供了一个社区支持的`PrimeLocale`仓库([`github.com/primefaces/primelocale`](https://github.com/primefaces/primelocale)),你可以使用或为其翻译内容做出贡献。

现在,让我们通过一个例子来看看如何将翻译添加到 PrimeNG 的`Calendar`组件中:

1.  要整合 PrimeNG 的区域设置选项的翻译版本,你有两种选择:要么雇佣翻译人员提供翻译,要么从`PrimeLocale`仓库中检索。以下是从`PrimeLocale`获得的越南文翻译版本(*图 12**.7*)的区域设置选项:

    ```js
    {
        "vi": {
            "dayNames": ["Chủ nhật", "Thứ hai", "Thứ ba", "Thứ tư", "Thứ năm", "Thứ sáu", "Thứ bảy"],
            "dayNamesShort": ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
            "dayNamesMin": ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
            "monthNames": ["Tháng Giêng", "Tháng Hai", "Tháng Ba", "Tháng Tư", "Tháng Năm", "Tháng Sáu", "Tháng Bảy", "Tháng Tám", "Tháng Chín", "Tháng Mười", "Tháng Mười một", "Tháng Mười hai"],
            "monthNamesShort": ["Giêng", "Hai", "Ba", "Tư", "Năm", "Sáu", "Bảy", "Tám", "Chín", "Mười", "Mười một", "Mười hai"],
             }
       }
    ```

1.  然后,我们将它放入我们的翻译文件中:

    ```js
    // assets/i18n/main/vi.json
    {
      "greeting": "Chào mừng đến chương 12",
      "welcome": "Chào mừng đến với ứng dụng của chúng tôi.",
      "primeng": {
        "accept": "Có",
        "reject": "Không",
        "choose": "Chọn",
        "cancel": "Hủy",
        "dayNames": [
          "Chủ nhật",
          "Thứ hai",
          "Thứ ba",
          "Thứ tư",
          "Thứ năm",
          "Thứ sáu",
          "Thứ bảy"
        ],
        …
      }
    }
    ```

    确保这些翻译位于翻译文件中的`primeng`键下。

1.  最后,在切换语言时,我们需要通过`PrimeNGConfig`提取翻译内容并设置全局翻译。以下是一个示例代码:

    ```js
    // language.component.ts
    private primeNgConfig = inject(PrimeNGConfig)
    switchLanguage(event: DropdownChangeEvent) {
    ...
      this.translateService.get('primeng').subscribe((res) => {
        this.primeNgConfig.setTranslation(res)
      })
    }
    primeng key using the translateService.get() method. Once the translation value is obtained, it is passed to the primeNgConfig.setTranslation() method to set the translation for the primeng library. This ensures that PrimeNG components and features display their text and messages in the appropriate language.
    ```

让我们看看结果:

![图 12.9 – 越南文中的日历示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_12_09.jpg)

图 12.9 – 越南文中的日历示例

从图片中,你可以看到在将语言选项从英文切换到越南文后,PrimeNG 的`Calendar`组件也会更新其翻译。

总结来说,使用 PrimeNG Locale 使你能够在 Angular 应用程序中无缝处理本地化。通过利用 PrimeNG 的功能,开发者可以轻松配置语言设置,自定义日期和时间格式,并根据区域特定约定格式化数字。使应用程序适应不同地区和文化的能力确保了更包容和用户友好的体验。在我们总结这一章时,暂停并回顾总结部分中的主要观点是非常有价值的。

# 摘要

在本章中,我们踏上了 Angular 框架中 i18n 和 l10n 领域的旅程,重点关注如何利用 PrimeNG 组件创建与全球受众产生共鸣的应用程序。我们深入研究了适应各种地区的应用细节,确保每位用户都能以对他们来说感觉自然的方式体验应用程序。

我们揭示了 i18n 和 l10n 的重要性,理解到虽然它们密切相关,但它们服务于不同的目的。i18n 是设计软件应用程序的过程,以便它可以适应各种语言和地区,而无需进行工程更改。另一方面,l10n 是通过添加特定地区的组件和翻译文本,将国际化软件适应特定地区或语言的过程。

我们讨论了 Angular 中 i18n 的不同选项,并展示了 ngx-translate 库作为管理 Angular 应用程序翻译的强大工具。此外,我们还探讨了 PrimeNG Locale 及其配置过程,以实现有效的 l10n。

在接下来的章节中,我们将把重点转向测试和调试 PrimeNG 组件。测试是应用程序开发的关键方面,因为它确保了代码库的可靠性和稳定性。我们将探讨各种测试技术,并学习如何有效地调试 PrimeNG 组件以识别和解决问题。




# 第十三章:测试 PrimeNG 组件

在本章中,我们将深入探讨由 PrimeNG 组件驱动的 Angular 应用程序测试的关键方面。在整个过程中,你将学习如何有效地测试你的 PrimeNG 组件,确保它们的可靠性和功能性。

通过理解测试的原则、技术和工具,你可以提高 Angular 应用程序的质量、稳定性和可维护性。在本章中,你将通过各种示例获得设置测试和高效测试 Angular 组件的基本知识。此外,你还将熟悉可以增强和支持你的测试工作的最佳实践和库。

在本章中,我们将涵盖以下主题:

+   开始基本的 Angular 测试

+   编写 PrimeNG 组件的测试用例

+   利用测试技巧和窍门

# 技术要求

本章包含各种测试代码示例。你可以在以下 GitHub 仓库的 `chapter-13` 文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-13`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-13)

# 开始基本的 Angular 测试

在本节中,我们将向您介绍 Angular 应用程序测试的基础知识。测试是开发过程中的一个重要部分,它允许早期发现错误、加快反馈周期并提高代码稳定性。此外,随着时间的推移,当你持续对代码进行更改时,遵循测试最佳实践可以有效地减轻潜在问题,保留现有功能,并确保向用户交付高质量的软件。

## Angular 测试基础介绍

Angular 提供了一个强大的测试框架,允许你为你的组件、服务和应用程序的其他部分编写测试。Angular 中的测试基于单元测试的原则,其中单个代码单元在隔离状态下进行测试。这种方法有助于确保每个单元正确运行并满足预期要求。

在 Angular 中进行测试涉及编写模拟用户交互、验证组件行为和断言预期结果的测试用例。这些测试可以帮助你识别和修复错误、验证业务逻辑,并确保你的应用程序按预期工作。

当你首次安装 Angular 项目时,它包含以下代码块中的 `package.json` 和 `angular.json`:

```js
// package.json
"devDependencies": {
  ...
  "@types/jasmine": "~4.3.0",
  "jasmine-core": "~4.6.0",
  "karma": "~6.4.0",
  "karma-chrome-launcher": "~3.2.0",
  "karma-coverage": "~2.2.0",
  "karma-jasmine": "~5.1.0",
  "karma-jasmine-html-reporter": "~2.1.0",
}
// angular.json
"test": {
  "builder": "@angular-devkit/build-angular:karma",
  "options": {
    "polyfills": [
      "zone.js",
      "zone.js/testing"
    ],
    "tsConfig": "tsconfig.spec.json",
    ...
  }
}
```

此代码片段是设置 Angular 应用程序测试的配置文件的一部分。让我们分析这个代码块:

+   `devDependencies: {...}`: 这包含在 Angular 中运行测试的包。例如,`karma-jasmine-html-reporter` 在测试运行后生成详细的 HTML 报告。

+   `karma`:这是一个流行的 JavaScript 应用程序测试运行器。它允许你在多个浏览器中执行测试,捕获结果并报告。当你运行`ng test`时,Karma 启动开发服务器,打开指定的浏览器,并在这些浏览器中执行 Jasmine 测试。

+   `jasmine`:这是一个**行为驱动开发**(**BDD**)的 JavaScript 测试框架。在 Angular 的上下文中,Jasmine 通常用作编写和运行单元测试的测试框架。

+   `"builder": "@angular-devkit/build-angular:karma"`:这是一个由 Angular DevKit 提供的构建器,用作运行 Karma 测试的目标。

为了在 Angular 中运行测试,你可以运行`npm run test`命令,这将生成以下结果:

```js
npm run test
> primeng-book@0.0.0 test
> ng test
 Browser application bundle generation complete.
15 11 2023 09:42:30.460:WARN [karma]: No captured browser, open http://localhost:9876/
15 11 2023 09:42:30.500:INFO [karma-server]: Karma v6.4.2 server started at http://localhost:9876/
15 11 2023 09:42:30.500:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
15 11 2023 09:42:30.503:INFO [launcher]: Starting browser Chrome
15 11 2023 09:42:33.251:INFO [Chrome 119.0.0.0 (Mac OS 10.15.7)]: Connected on socket 3D7e73rrBAAAB with id 37006674
Chrome 119.0.0.0 (Mac OS 10.15.7): Executed 0 of 3 SUCCESS (
Chrome 119.0.0.0 (Mac OS 10.15.7): Executed 1 of 3 SUCCESS (
Chrome 119.0.0.0 (Mac OS 10.15.7): Executed 2 of 3 SUCCESS (
Chrome 119.0.0.0 (Mac OS 10.15.7): Executed 3 of 3 SUCCESS (
Chrome 119.0.0.0 (Mac OS 10.15.7): Executed 3 of 3 SUCCESS (0.139 secs / 0.12 secs)
TOTAL: 3 SUCCESS
```

测试结果也将出现在终端和浏览器中:

![图 13.1 – Karma 测试结果](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_13_01.jpg)

图 13.1 – Karma 测试结果

因此,当你运行测试命令时,Karma 将打开浏览器并成功执行来自`AppComponent`的测试。此外,你还将能够观察执行测试及其结果。

## 使用 Jest 进行 Angular 测试

在 Angular 中,Karma 和 Jasmine 一直是默认的测试框架选择,但随着 Angular v16 的推出,**Jest**已被包含作为一个实验性的替代方案用于编写单元测试。采用 Jest 的决定源于 Karma 虽然有效,但依赖于真实浏览器,导致测试速度较慢且较重。此外,使用真实浏览器在**持续集成**(**CI**)中引入了复杂性。另一个需要考虑的因素是 Karma 已被弃用([`github.com/karma-runner/karma`](https://github.com/karma-runner/karma))。

注意

持续集成是一种开发实践,涉及将多个贡献者的代码更改自动集成到一个共享仓库中。它包括构建和测试等自动化流程,以在开发周期的早期检测集成问题。持续集成促进协作,加速开发,并通过提供对代码库健康状况的即时反馈来确保代码质量。流行的 CI 工具有 Jenkins、Travis CI、CircleCI 和 GitHub Actions。

在运行 Jest 时,还有一些其他好处:

+   **快照测试**:这简化了视觉检查 UI 变化的过程,并有助于防止意外的回归。

+   **快速并行测试执行**:Jest 以其速度和高效的测试执行而闻名。它可以在并行中运行测试,在开发过程中提供更快的反馈。

+   **易于集成**:Jest 可以轻松地与 Angular 项目集成,无需复杂的配置。

+   **内置代码覆盖率报告**:Jest 包括生成代码覆盖率报告的内置支持。这允许开发者评估他们的代码有多少被测试覆盖,有助于识别可能需要额外测试的区域。

+   **简化模拟和监视**:Jest 提供了一个方便的 API 来创建模拟和监视器,这使得在测试期间隔离组件或服务变得更加容易。

+   **智能测试重跑的监视模式**:Jest 的监视模式智能地只重新运行受代码更改影响的测试,显著加快了开发期间的反馈循环。

+   **生态系统和社区**:Jest 拥有一个充满活力和活跃的社区,以及不断增长的插件和扩展生态系统。当在更广泛的 JavaScript 和测试生态系统中寻找解决方案、支持或集成时,这可能是有利的。

因此,让我们将我们的测试框架更改为 Jest。首先,你需要通过运行以下命令来安装 Jest 包:

```js
npm install jest jest-environment-jsdom --save-dev
```

此命令将安装 `jest` 和 `jest-environment-jsdom`,这是一个使用 JSDOM 模拟浏览器环境的 Jest 环境。

`jest-environment-jsdom`,Jest 测试可以在模拟的浏览器环境中运行,允许你编写和运行与 DOM 交互、处理事件和执行其他浏览器相关操作的测试。

之后,让我们更新我们的测试目标:

```js
// angular.json
"test": {
  "builder": "@angular-devkit/build-angular:jest",
  "options": {
    "polyfills": ["zone.js", "zone.js/testing"],
    "tsConfig": "tsconfig.spec.json"
  }
}
```

通过将构建器从 `@angular-devkit/build-angular:karma` 更改为 `@angular-devkit/build-angular:jest`,我们现在可以利用 Jest 来运行我们的单元测试。

通过运行 `npm run test` 来查看测试结果:

```js
> primeng-book@0.0.0 test
> ng test
NOTE: The Jest builder is currently EXPERIMENTAL and not ready for production use.
Application bundle generation complete. [1.619 seconds]
(node:22351) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS  dist/test-out/app.component.spec.mjs
  AppComponent
     should create the app (123 ms)
     should have as title 'Welcome to chapter-13' (27 ms)
     should render title (25 ms)
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.398 s
Ran all test suites.
```

你会注意到编写单元测试的过程和测试的执行速度没有差异。

## 编写你的初始 Angular 测试的逐步指南

既然我们已经涵盖了准备部分,让我们深入编写你的第一套测试。在单元测试中,你可以利用 **安排、行动和断言**(**AAA**)模式,这是一种常见的结构化和组织单元测试的方法。AAA 的每个部分都服务于一个独特的目的:

1.  **安排**:在这个步骤中,你设置测试环境。这包括创建你想要测试的组件或服务的实例,提供任何必要的依赖项,并配置测试的初始状态。

1.  **行动**:这是你执行动作或触发你想要测试的行为的地方。这可能涉及调用方法、与组件交互或模拟事件。

1.  **断言**:在最后一步,你检查动作的结果以确保它与预期的结果相匹配。这是你根据动作执行后的应用程序状态进行断言的地方。

使用 AAA 模式有助于保持测试的有序性、可读性和专注于特定的行为。它为编写和理解测试提供了一个清晰的架构,使得随着代码库的发展,维护和调试它们变得更加容易。

在下一节中,让我们看看一个真实的 Angular 测试。

## 简化简单的单元测试

当我们首次创建一个 Angular 应用程序时,CLI 帮助生成一个用于您的 `AppComponent` 的测试文件。让我们看看这个示例测试文件:

```js
// app.component.spec.ts
describe('AppComponent', () => {
  // Arrange
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [AppComponent]
    }).compileComponents()
  })
  it('should render title', () => {
    // Arrange
    const fixture = TestBed.createComponent(AppComponent)
    const compiled = fixture.nativeElement as HTMLElement
    // Act
    fixture.detectChanges()
    // Assert
    expect(compiled.querySelector('h1')?.textContent).toContain(
      'Welcome to chapter-13'
    )
  })
})
```

提供的代码片段是`AppComponent`组件的 Angular 测试脚本。让我们以下面的列表形式分解这段代码,以便我们更好地理解它:

+   `describe('AppComponent', ()...`: 这是一个测试套件,用于分组相关的测试。在这种情况下,它将`AppComponent`的测试分组在一起。

+   `beforeEach(async () => ...)`: 这个函数是一个设置函数,在每次单独的测试用例之前运行。它是一个异步函数,使用`TestBed.configureTestingModule`配置测试模块,这是由`TestBed`实用程序提供的 Angular 测试基础设施的关键部分。它允许你通过指定测试组件所需的依赖项、提供者和导入来配置测试模块。在这种情况下,它导入了`AppComponent`。这也是 AAA 模式中的安排步骤。

+   `it('should render title'...`: 这个函数是一个测试用例,定义了要测试的特定行为。在这种情况下,测试用例被命名为`should render title`。让我们根据 AAA 模式来分解这个函数:

    +   使用`TestBed.createComponent`创建`AppComponent`,并使用`fixture.nativeElement`检索编译后的 HTML 元素

    +   `fixture.detectChanges()`

    +   `<h1>`元素包含字符串`Welcome` `to chapter-13`

总体而言,这个测试验证了`AppComponent`能够正确渲染带有预期文本内容的标题。它设置了组件,触发变更检测,并断言预期的结果。

简而言之,通过使用基本的测试技术,我们可以为我们的 Angular 应用程序的质量和可靠性打下坚实的基础。现在我们已经掌握了基本的测试概念,让我们深入一个具体用例:编写 PrimeNG 组件的测试。我们还将利用 Jest 作为我们的单元测试框架来编写单元测试。

# 为 PrimeNG 组件编写测试

测试 PrimeNG 组件遵循与测试常规 Angular 组件相同的原理。这种相似性源于 PrimeNG 组件本质上是在 Angular 组件的底层。在接下来的章节中,我们将探讨这些测试中的几个,以获得对这个领域的洞察和知识。

## PrimeNG 如何测试其组件

首先,让我们看看 PrimeNG 是如何测试它自己的组件的。由于本书篇幅限制,无法展示整个测试文件,因为它相当长。然而,我们可以关注一个特定部分,该部分展示了 PrimeNG 如何在源代码中测试其组件。以下是 PrimeNG `Button`组件的示例代码:

```js
describe('Button', () => {
    let button: Button;
    let fixture: ComponentFixture<Button>;
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [NoopAnimationsModule],
            declarations: [Button]
        });
        fixture = TestBed.createComponent(Button);
        button = fixture.componentInstance;
    });
    it('should disabled when disabled is true', () => {
        button.disabled = true;
        fixture.detectChanges();
        const buttonEl = fixture.debugElement.query(By.css('.p-button'));
        expect(buttonEl.nativeElement.disabled).toBeTruthy();
    });
    it('should display the label and have a text only class', () => {
        button.label = 'PrimeNG';
        fixture.detectChanges();
        const buttonEl = fixture.debugElement.query(By.css('.p-button'));
        expect(buttonEl.nativeElement.textContent).toContain('PrimeNG');
        expect(buttonEl.nativeElement.children.length).toEqual(1);
    });
    ...
})
```

让我们以下面的列表形式分解代码,以便我们理解其不同部分:

+   `beforeEach(...)`: 此函数用于在每个测试用例之前设置测试环境。在这种情况下,它通过调用 `TestBed.configureTestingModule` 配置测试模块。`imports` 属性指定任何所需的模块,而 `declarations` 属性指定正在测试的组件。在这种情况下,它导入 `NoopAnimationsModule` 并将 `Button` 添加到声明数组中。

+   `当 disabled 为 true 时应禁用`: 此测试用例将 `Button` 组件的 `disabled` 属性设置为 `true`,使用 `fixture.detectChanges()` 触发变更检测,然后使用 `fixture.debugElement.query` 查询 DOM 中的按钮元素。它断言按钮元素的 `disabled` 属性为 `true`,使用 `expect(...).toBeTruthy()`。

+   `应显示标签并具有纯文本类`: 此测试用例将 `Button` 组件的 `label` 属性设置为 `PrimeNG`,触发变更检测,并查询 DOM 中的 `button` 元素。它断言 `button` 元素的文本内容包含 `PrimeNG`,使用 `expect(...).toContain(...)`,并验证 `button` 元素只有一个子元素,使用 `expect(...).toEqual(1)`。

这些测试的目的是验证 `Button` 组件的预期行为。通过更改其属性并触发变更检测,测试确保组件正确渲染并按预期行为。

注意

在 PrimeNG 源代码 ([`github.com/primefaces/primeng/tree/master/src/app/components`](https://github.com/primefaces/primeng/tree/master/src/app/components)) 中,存在你可以学习和用作参考的现有测试,以增强你的理解。

## 创建和测试我们自己的组件

将前面的示例和知识付诸实践,让我们为以下卡片组件创建一个测试:

![图 13.2 – 示例卡片组件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_13_02.jpg)

图 13.2 – 示例卡片组件

通过检查截图,你可以观察到我们创建的卡片组件的视觉表示。为了确保此组件的正常运行,编写验证其标题、副标题和按钮正确渲染的测试是至关重要的。以下代码块是测试代码的示例:

```js
// sample-test.component.spec.ts
describe('SampleTestComponent', () => {
  let component: SampleTestComponent
  let fixture: ComponentFixture<SampleTestComponent>
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [SampleTestComponent],
    }).compileComponents()
    fixture = TestBed.createComponent(SampleTestComponent)
    component = fixture.componentInstance
    fixture.detectChanges()
  })
  it('should create', () => {
    expect(component).toBeTruthy()
  })
  it('should display the product title and subtitle', () => {
    fixture.detectChanges()
    const card = fixture.debugElement.query(By.css('p-card'))
    expect(card.nativeElement.textContent).toContain('Super Laptop PRO X')
    expect(card.nativeElement.textContent).toContain('Best for Nomads')
  })
  it('should have a footer', () => {
    fixture.detectChanges()
    const footerCard = fixture.debugElement.query(By.css('.p-card-footer'))
    const ctaButtons = fixture.debugElement.queryAll(By.css('.p-button'))
    expect(footerCard).toBeTruthy()
    expect(ctaButtons).toBeTruthy()
    expect(ctaButtons.length).toEqual(2)
  })
})
```

让我们将以下列表中的代码分解,以便我们可以理解其不同的部分:

+   `应创建`: 测试用例检查 `component` 实例是否为 `Truthy`,这意味着它已被成功创建。

+   `应显示产品标题和副标题`: 此测试用例触发变更检测,然后使用 `fixture.debugElement.query` 查询 DOM 中的 `p-card` 元素。它断言 `p-card` 元素的内容包含预期的产品 `title` 值和 `subtitle` 值,使用 `expect(...).toContain(...)`。

+   `应该有一个页脚`:这个测试用例触发变更检测,然后查询 DOM 中具有`.p-card-footer`类的页脚元素以及所有具有`.p-button`类的按钮。它使用`expect(...).toBeTruthy()`断言页脚元素和按钮都是`Truthy`,并且还通过检查`ctaButtons`数组的长度来验证有两个按钮存在,使用`expect(...).toEqual(2)`。

运行测试后,你可以看到它成功通过:

```js
PASS   chapter-13  apps/chapter-13/src/app/pages/sample-test/sample-test.component.spec.ts
SampleTestComponent
   should create (95 ms)
   should display the product title and subtitle (23 ms)
   should have a footer (17 ms)
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        6.411 s
```

总之,为 PrimeNG 组件编写测试使你能够在 Angular 应用程序中确保这些组件的功能性和可靠性。现在我们已经探讨了为 PrimeNG 组件编写测试的过程,让我们通过利用有用的技巧和窍门来进一步改进我们的测试实践,以提高测试的有效性和效率。

# 利用测试技巧和窍门

尽管测试使我们能够确保代码的正确性和稳定性,但编写有效的测试有时可能具有挑战性。在本节中,我们将探讨各种可以帮助你改进测试实践的技巧和窍门,以下是一个可编辑表格的示例:

![图 13.3 – 样本可编辑表格](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_13_03.jpg)

图 13.3 – 样本可编辑表格

表格列出了可编辑和可删除的产品列表。点击删除图标时,会出现一个确认对话框以验证删除操作。

因此,现在让我们看看一些测试技巧。

## 隔离单元测试

在编写单元测试时,将正在测试的组件或服务与其依赖项隔离至关重要。Angular 的**TestBed**提供了一套强大的工具,用于创建和配置测试模块。通过利用 TestBed,我们可以模拟依赖项并提供模拟实现,使我们能够专注于我们想要测试的单元。

考虑以下示例:

```js
// tips.component.spec.ts
const productsStub = [
  ...
]
describe('TipsComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TipsComponent],
      providers: [
        ...
        {
          provide: ShopService,
          useValue: {
            getProducts: jest.fn().mockReturnValue(productsStub),
          },
        },
      ],
    }).compileComponents()
  })
  ..
})
```

在本例中,我们使用`TestBed.configureTestingModule`来设置测试模块。通过指定`imports`和`providers`,我们可以模拟依赖项并确保它们被正确注入到正在测试的组件或服务中。在这种情况下,我们不是从`ShopService`调用`getProducts()`函数,而是用`productsStub`值替换结果,这使得测试更加独立且易于进行。

## 使用 NO_ERRORS_SCHEMA

在测试 Angular 组件时,我们经常遇到不需要断言子组件(如 Angular Material 组件)的行为或渲染的情况。在这种情况下,`NO_ERRORS_SCHEMA`可以是一个方便的工具,用于简化我们的测试设置。

`NO_ERRORS_SCHEMA`告诉 Angular 的编译器忽略组件模板中未识别的元素和属性。这使我们能够专注于测试组件的逻辑,而无需提供子组件的详细模拟实现。

这里有一个例子:

```js
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AppComponent],
      schemas: [NO_ERRORS_SCHEMA]
    }).compileComponents();
  });
  // Additional tests go here
});
```

在本例中,我们在测试模块配置中指定了 `schemas: [NO_ERRORS_SCHEMA]`。这允许我们在测试 `AppComponent` 时无需担心任何子组件的存在或行为。

注意

如果你在编写集成测试,请避免使用 `NO_ERRORS_SCHEMA`。这是因为 `NO_ERRORS_SCHEMA` 选项会忽略未知元素和属性的错误模板。它允许 Angular 即使子组件模板存在问题也能运行测试。你可以在[`angular.io/guide/testing-components-scenarios#no_errors_schema`](https://angular.io/guide/testing-components-scenarios#no_errors_schema)了解更多信息。

## 利用 `spyOn` 方法

使用 `spyOn` 函数以及间谍对象来促进方法监视。

通过使用 `spyOn`,我们可以用一个记录所有调用的间谍函数替换一个方法,并提供额外的功能,例如返回特定值或抛出异常。这使得我们能够验证方法是否被调用,调用了多少次,以及使用了哪些参数。

考虑以下示例:

```js
// tips.component.spec.ts
beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [TipsComponent],
    ...
  }).compileComponents()
  fixture = TestBed.createComponent(TipsComponent)
  confirmDialog = fixture.debugElement.query(
    By.css('p-confirmdialog')
  ).componentInstance
})
it('should show accept message on delete', () => {
  const messageSpy = jest.spyOn(messageService, 'add')
  component.onRowDelete(1)
  fixture.detectChanges()
  confirmDialog.accept()
  expect(messageSpy).toHaveBeenCalledWith({
    severity: 'info',
    summary: 'Confirmed',
    detail: 'Your product is deleted',
  })
})
```

让我们分解以下列表中的代码,并了解它的作用:

+   `('should show accept message on delete', () => { ... })`:这是一个测试用例描述。在这种情况下,它是在检查删除行后是否显示了特定消息。

+   `const messageSpy = jest.spyOn(messageService, 'add')`:这一行使用 Jest 的 `spyOn` 函数创建了一个间谍。它监视 `messageService` 对象的 `add` 方法。这使我们能够在测试期间跟踪此方法是否以及如何被调用。

+   `component.onRowDelete(1)`:这一行调用正在测试的组件的 `onRowDelete` 方法,并将 `1` 作为参数传递。这模拟了删除索引为 `1` 的行。

+   `fixture.detectChanges()`:这一行触发了测试组件的变化检测。它确保组件模板中的任何更改都得到应用,并在测试环境中反映更新。

+   `confirmDialog.accept()`:这一行模拟用户接受确认对话框。它假设组件有一个具有 `accept` 方法的 `confirmDialog` 对象,该方法是用来确认删除的。

+   `expect(messageSpy).toHaveBeenCalledWith({ ... })`:这一行使用了 `expect` 函数来进行断言。它检查 `messageService` 对象的 `add` 方法是否以预期的参数被调用。在这种情况下,它期望该方法以包含特定属性(如 `severity`、`summary` 和 `detail`)的对象被调用。

总之,间谍方法是在测试期间跟踪和控制函数调用的强大工具,它为你的应用程序的行为提供了洞察力。随着我们过渡到下一节,让我们探讨如何在 Angular 测试中处理异步代码和管理与时间相关的操作。

## 与 `fakeAsync` 一起工作

`fakeAsync` 是 Angular 测试中的一个实用工具,它使得异步代码的同步测试成为可能。它在一个特殊的“模拟”区域内运行测试,在这个区域内可以使用 `tick()` 来控制异步操作。以下是一个 `fakeAsync` 和 `tick` 的简单演示:

```js
import { fakeAsync, flush, tick } from '@angular/core/testing'
describe('FakeAsync Example', () => {
  it('should test asynchronous code using fakeAsync', fakeAsync(() => {
    let value: string | undefined
    // Simulate an asynchronous operation
    setTimeout(() => {
      value = 'completed'
    }, 1000)
    // Use tick to simulate the passage of time
    tick(500) // Simulate 500 milliseconds passed
    expect(value).toBeUndefined() // Value should still be undefined
    tick(500) // Simulate another 500 milliseconds passed
    expect(value).toBe('completed')
    flush()
  }))
})
```

在这个例子中,`fakeAsync` 用于包装 `test` 函数。`setTimeout` 用于模拟异步操作,而 `tick` 用于模拟时间的流逝。通过调用 `tick(500)`,你模拟了 500 毫秒的流逝,然后你可以对应用程序的状态进行断言。这对于以同步方式测试异步行为非常有用。

现在,让我们看看一些测试 PrimeNG `MessageService` 在 `TipsComponent` 中行为的更多示例代码:

```js
// tips.component.spec.ts
import {
  fakeAsync, flush, tick
} from '@angular/core/testing'
it('should show close message on delete', fakeAsync(() => {
  const messageSpy = jest.spyOn(messageService, 'add')
  component.onRowDelete(1)
  fixture.detectChanges()
  tick(300)
  // Send Escape event
  const escapeEvent: any = document.createEvent('CustomEvent')
  escapeEvent.which = 27
  escapeEvent.initEvent('keydown', true, true)
  document.dispatchEvent(escapeEvent as KeyboardEvent)
  expect(messageSpy).toHaveBeenCalledWith({
    severity: 'warn',
    summary: 'Cancelled',
    detail: 'You have cancelled',
  })
  flush()
}))
```

这个测试验证了在按下 *Esc* 按钮时消息是否显示正确的值。以下要点概述了测试的流程:

+   首先,我们通过 `component.onRowDelete(1)` 函数触发删除操作,因此确认对话框出现。

+   之后,我们使用 `tick` 来根据指定的时间量推进虚拟时钟。在这个例子中,我们使用 `tick(300)` 来模拟 300 毫秒的延迟。

+   在使用 `tick` 函数之后,我们通过创建一个 `KeyboardEvent` 对象并在文档上分发它来模拟 `Escape` 键盘按键事件。

+   在 `Escape` 事件被分发之后,我们可以通过 `expect(messageSpy).toHaveBeenCalledWith({...})` 来断言消息服务。

+   最后,我们使用 `flush` 来清除任何挂起的异步任务,确保在 `test` 函数退出之前所有异步操作都已经完成。

在本节中,我们了解了如何在 Angular 测试中管理异步操作。在下一节中,我们将深入探讨一个强大的测试实用工具,它可以简化并增强我们的测试能力:Spectator。

## 利用第三方库 – Spectator

**Spectator** ([`ngneat.github.io/spectator`](https://ngneat.github.io/spectator)) 提供了一套实用工具和技术,这些工具和技术有助于编写简洁且表达力强的 Angular 组件测试。它允许你创建组件实例,模拟依赖项,访问组件的 DOM,以及用最少的样板代码断言组件的行为。它还提供了一个干净且直观的语法,使得测试用例更加可读和维护。

要使用它,首先,我们需要通过运行以下命令来安装 `spectator` 包:

```js
npm install @ngneat/spectator --save-dev
```

之后,让我们创建一个示例测试,在这个测试中我们使用 `Spectator` 来测试一个 Angular 组件:

```js
// spectator.component.spec.ts
import {
  Spectator,
  createComponentFactory,
  mockProvider,
} from '@ngneat/spectator/jest'
describe('TipsComponent', () => {
  let spectator: Spectator<TipsComponent>
  const createComponent = createComponentFactory({
    component: TipsComponent,
    providers: [
      mockProvider(ShopService, {
        getProducts: () => productsStub,
      }),
    ],
  })
  beforeEach(() => (spectator = createComponent()))
  it('should show table content', () => {
    const table = spectator.query('p-table')
    expect(table?.textContent).toContain('Product 1')
    expect(table?.textContent).toContain('Product 2')
  })
})
```

给定的代码利用 Spectator 来测试 `TipsComponent` 实例的行为。让我们以下面的列表形式分解代码块,以便我们理解每一部分:

+   `const createComponent = createComponentFactory({ ... })`: 这定义了一个使用 `createComponentFactory` 从 `Spectator` 中创建的 `createComponent` 函数。此函数用于创建 `TipsComponent` 的实例以进行测试。它还通过 `providers` 数组提供了一个 `ShopService` 的模拟实例。

+   `beforeEach(() => (spectator = createComponent()))`: 这是一个在每次测试用例之前运行的设置步骤。它使用 `createComponent` 函数创建一个新的 `TipsComponent` 实例,并将其分配给 `spectator` 变量。

+   `it('should show table content', () => { ... })`: 这行代码定义了一个带有描述 `should show table content` 的测试用例。此测试用例验证 `TipsComponent` 中的表格是否包含预期的内容。以下是对其更详细的分解:

    +   `const table = spectator.query('p-table')`: 这使用 `spectator` 对象中的 `query` 方法在组件的模板中查找 `<p-table>` 元素。

    +   `expect(table?.textContent).toContain(...)`: 这条语句断言,如果存在,`table` 元素的 `textContent` 包含字符串 `Product 1` 或 `Product 2`。

在我们结束对 `Spectator` 的探索后,我们发现它在简化并改进我们的 Angular 组件测试工作流程方面非常有效。现在,让我们看看另一个有助于增强我们的测试实践的库:`ng-mocks`。

## 利用第三方库 – ng-mocks

另一个用于测试 Angular 应用程序的有力第三方库是 `ng-mocks`,它通过提供灵活的模拟和存根功能简化了测试过程,使得在测试期间隔离组件和服务变得更加容易。

使用 ng-mocks,我们可以创建 Angular 组件和服务的模拟实现,为方法定义自定义行为,并验证交互。这使我们能够专注于测试特定的单元,而无需担心真实实现的复杂性。

要使用它,首先,我们需要通过运行以下命令来安装 `ng-mocks` 包:

```js
npm install ng-mocks --save-dev
```

现在,让我们看看如何在 Angular 测试中使用 ng-mocks:

```js
// ng-mocks.component.spec.ts
import { MockInstance, MockProvider } from 'ng-mocks'
describe('TipsComponent', () => {
  beforeAll(() =>
    MockInstance(ShopService, () => ({
      getProducts: () => productsStub,
    }))
  )
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TipsComponent],
      providers: [
provideNoopAnimations(),
MockProvider(ShopService)
],
    }).compileComponents()
    fixture = TestBed.createComponent(TipsComponent)
    component = fixture.componentInstance
    fixture.detectChanges()
  })
})
```

让我们逐行分析以下列表中的代码块,以便我们了解它所执行的操作:

+   `providers: [MockProvider(ShopService)]`: 这用于创建 `ShopService` 的模拟版本,并将其配置为测试环境中的提供者。这确保测试使用 `ShopService` 的模拟版本而不是实际实现。

+   `beforeAll(() => MockInstance(ShopService, () => ({ ... })))`: 这个 `beforeAll` 钩子在套件中的所有测试用例之前执行一次。它通过使用 `ng-mocks` 中的 `MockInstance` 函数来模拟 `ShopService` 依赖项。它用返回 `productsStub` 的模拟实现替换了原始的 `getProducts` 方法。

注意

`ng-mocks` 也可以与 `spectator` 一起很好地工作。您可以在 [`ng-mocks.sudo.eu/extra/with-3rd-party`](https://ng-mocks.sudo.eu/extra/with-3rd-party) 上了解更多信息。

由此,我们深入探讨了 Angular 测试的复杂性,揭示了实用的技巧和窍门,这些技巧和窍门能让你增强测试套件的效率和弹性。

# 摘要

在本章中,你学习了如何在 Angular 应用程序中有效地测试 PrimeNG 组件。通过利用 Jest 这个强大的测试框架,你获得了确保这些组件功能性和可靠性的能力。

在本章中,你探索了与测试 PrimeNG 组件相关的各种概念和技术。你首先了解了测试的重要性以及它为你的开发工作流程带来的好处。然后,你深入了解了编写 PrimeNG 组件单元测试的具体步骤,包括组件设置、测试组件行为以及验证组件外观和交互。

此外,你还遇到了实际示例、代码片段和最佳实践,展示了如何有效地测试 PrimeNG 组件。通过跟随并实施这些测试策略,你在验证 PrimeNG 组件的正确性、可靠性和性能方面获得了实践经验。

随着我们过渡到下一章,我们将把重点转向利用 PrimeNG 组件构建响应式 Web 应用程序的世界。

# 第四部分:真实世界应用

在本最终部分,你将应用在前几章中学到的所有知识和技能来构建一个真实的响应式 Web 应用程序。本部分将为你提供实践经验,让你能够将 PrimeNG 和 Angular 的专业知识付诸实践。

到本部分结束时,你将完成一个使用 PrimeNG 和 Angular 的完全功能性的响应式 Web 应用程序。

本部分包括以下章节:

+   *第十四章*, *构建响应式 Web 应用程序*




# 第十四章:构建响应式 Web 应用程序

在本章中,您将通过使用 Angular 和 PrimeNG 组件构建响应式 Web 应用程序来应用您现有的知识。重点将放在创建能够无缝适应不同屏幕尺寸和设备的应用程序上。在本章结束时,您将深入了解如何使用 Angular 和 PrimeNG 设计和开发响应式 Web 应用程序。

本章的目标是为您提供构建响应式 Web 应用程序所需的知识和技能。您将了解如何创建一个项目结构,该结构实现响应式布局,能够适应不同的屏幕尺寸,并利用 PrimeNG 和 PrimeFlex 来提升用户体验。此外,您还将深入了解如何部署应用程序,确保它能够触及广泛的受众。

在本章中,我们将涵盖以下主题:

+   响应式 Web 应用程序构建简介

+   介绍我们的响应式 Web 应用程序项目

+   创建网站的布局

+   开始开发网站

+   部署响应式 Web 应用程序

# 技术要求

本章包含 Angular 应用程序的代码示例。您可以在以下 GitHub 仓库的`chapter-14`文件夹中找到相关源代码:[`github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-14`](https://github.com/PacktPublishing/Next-Level-UI-Development-with-PrimeNG/tree/main/apps/chapter-14)。

# 响应式 Web 应用程序构建简介

响应式 Web 应用程序已成为当今数字景观的必需品。随着用户在多种设备上访问网站和应用,您创建能够无缝适应不同屏幕尺寸和方向的体验至关重要。在本节中,我们将概述响应式 Web 应用程序,并探讨为什么它们对于提供满意的用户体验至关重要。

## 为什么响应式 Web 应用程序很重要

在过去,网站主要是为具有固定屏幕尺寸的台式电脑设计的。然而,随着智能手机、平板电脑和其他移动设备的快速普及,网络浏览的格局发生了巨大变化。用户现在期望网站和应用能够在各种设备上访问,从大桌面显示器到小智能手机屏幕。以下是一个在移动设备上布局损坏的网站示例:

![图 14.1 – 移动设备上布局损坏的示例](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_01.jpg)

图 14.1 – 移动设备上布局损坏的示例

根据图示,很明显,尽管该网站在较大屏幕上运行正常,但在较小设备上布局损坏,导致**开始**按钮无响应,阻碍了用户交互。

响应式网页应用通过根据用户的设备和屏幕尺寸动态调整布局和内容来解决这个问题。采用响应式设计,你可以确保应用在不同设备上都具有可用性和视觉吸引力,从而提高用户参与度和满意度。

## 响应式网页应用的优势

从提升用户体验到提高 SEO 排名,响应式网页应用提供了许多优势,这些优势可以极大地增强你的开发工作。让我们回顾这些好处并了解它们的影响:

+   **提升用户体验**:响应式网页应用的主要好处之一是提供跨设备的一致和优化的用户体验。通过调整布局、内容和功能以适应不同的屏幕尺寸,用户可以轻松导航并与应用程序进行交互,无论他们使用的是哪种设备。这种无缝体验增强了用户满意度,并鼓励他们更长时间地与应用程序互动。

+   **扩大覆盖范围和可访问性**:响应式网页应用具有更广泛的覆盖范围,因为它们服务于多个设备上的用户。通过在不同平台上提供一致的经验,你可以确保应用对更广泛的受众是可访问的。这种可访问性对于面向移动用户的业务尤为重要,因为移动设备已成为全球许多人访问互联网的主要手段。

+   **搜索引擎优化(SEO)优势**:搜索引擎,如谷歌,在搜索结果中优先考虑移动友好型网站。提供跨设备无缝用户体验的响应式网页应用更有可能排在**搜索引擎结果页面(SERPs)**中更高的位置。通过采用响应式设计原则,你可以提高应用的可见性并吸引更多有机流量。

## 响应式网页设计的核心原则

响应式网页设计遵循几个关键原则以确保对不同屏幕尺寸和设备的有效适应。让我们看看这些原则:

+   *流体网格*:流体网格是响应式网页设计的基础。而不是使用基于像素的固定布局,你可以使用比例单位,如百分比,来定义元素的宽度和高度。这允许内容灵活调整并填充可用的屏幕空间,创建一个流畅且可适应的布局。

+   *灵活的图片和媒体*:图片和媒体在网页应用中扮演着至关重要的角色。为了确保它们适应不同的屏幕尺寸,你可以利用 CSS 技术,如`max-width: 100%`,使图片和媒体元素在容器内按比例缩放。这防止了图片在较小屏幕上溢出或太小。

+   *媒体查询*:媒体查询允许您根据用户设备的特征应用特定的 CSS 规则,例如屏幕大小、分辨率和方向。通过在特定的屏幕宽度上定义断点,您可以修改布局、字体排印和其他元素,以优化应用程序在不同设备上的外观。

+   *移动优先方法*:移动优先方法是一种设计理念,它优先考虑为移动设备设计,然后逐步增强应用程序以适应更大的屏幕。通过为较小的屏幕开始一个简约和专注的设计,您可以确保核心功能和内容对所有用户都是可访问的。随着屏幕尺寸的增加,可以引入额外的功能和布局改进。

现在您已经了解了构建响应式 Web 应用程序的要点,让我们深入了解我们的项目具体细节。理解基础为创建高效的项目结构和制作一个视觉吸引人且用户友好的网站奠定了基础。

# 介绍我们的响应式 Web 应用程序项目

我们的项目专注于创建一个响应式着陆页,有效地展示**软件即服务**(**SAAS**)产品。着陆页包括几个关键部分,包括页眉、英雄、功能、客户评价、定价和页脚。每个部分都是战略性地设计的,旨在吸引访客并推动转化,无论是将他们转变为客户还是将他们作为有价值的潜在客户。

![图 14.2 – 项目初始草图](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_02.jpg)

图 14.2 – 项目初始草图

根据草图,让我们检查每个部分的目的:

+   **页眉**:我们的应用程序的入口,页眉将展示一个干净直观的导航菜单,确保用户可以轻松探索着陆页的各个部分。

+   **英雄部分**:我们的英雄部分将是一个视觉盛宴,通过引人入胜的图像和简洁的信息立即吸引注意力,传达我们产品或服务的精髓。此外,通过放置战略性的、有说服力的行动号召按钮,我们将鼓励访客采取下一步行动,例如注册、请求演示或进一步探索我们的产品。

+   **功能**:在这里,用户将深入了解使我们的产品脱颖而出的核心功能。每个功能都将优雅地呈现,配有吸引人的视觉元素和简洁的描述。

+   **客户评价**:建立信任至关重要,客户评价部分将作为我们用户积极体验的见证。真实的引言甚至可能还有图片,将增添个人化的触感。

+   **定价**:对于准备迈出下一步的用户,定价部分将提供关于我们产品或服务的透明细节。我们将努力追求清晰和简洁,使用户能够轻松选择适合他们的计划。

+   **页脚**:在故事的结尾,页脚将包含必要的链接、联系方式,也许还有一个行动按钮,确保用户可以无缝地导航到我们网站的其它部分。

这是我们最终产品的预览:

![图 14.3 – 最终项目](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_03.jpg)

图 14.3 – 最终项目

这是本章将要构建的最终产品。为了更好地理解代码,请查看我们 GitHub 仓库中的本章内容。请注意,我们将主要关注 UI,这意味着像`登录`、`注册`或`购买`这样的功能将不会实现。

在心中有了清晰的项目概述后,我们准备好过渡到创建我们网站基础布局的激动人心阶段。

# 创建网站的布局

一旦我们有了我们网站的初步想法,将这些部分转换为 Angular 组件就相对简单了。让我们看看以下转换:

![图 14.4 – 将部分拆分为 Angular 组件](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_04.jpg)

图 14.4 – 将部分拆分为 Angular 组件

如图中所示,每个部分都是独特的,可以轻松地映射到一个 Angular 组件。例如,`header`部分可以分配给`header.component.ts`。通过遵循这种方法,我们可以建立以下代码结构作为示例:

```js
// app.component.ts
<primebook-header />
<primebook-hero />
<primebook-features />
<primebook-testimonials />
<primebook-footer />
```

每个选择器代表我们在草图或设计中的相应部分。

通过运行以下命令开始创建这些组件非常简单:

```js
primengbook$ ng g c header --inline-template --inline-style
CREATE src/app/header/header.component.spec.ts (596 bytes)
CREATE src/app/header/header.component.ts (295 bytes)
```

该命令将生成一个具有内联样式和内联模板的独立`Header`组件。如果您希望为`header.component.html`和`header.component.scss`生成单独的文件,可以省略`--inline-template`和`--inline-style`选项。一旦生成了`Header`组件,您就可以继续生成其他组件,如`Hero`、`Features`等。

现在我们已经建立了我们网站的必要布局,是时候深入实际开发过程了。我们将把设计转化为代码,为每个部分注入生命。

# 开始开发网站

到目前为止,我们已经为我们的响应式 Web 应用程序制定了蓝图。现在,让我们深入了解每个部分的实现细节。记住,我们的目标是使用 Angular、PrimeFlex、PrimeIcons 和 PrimeNG 组件创建一个无缝且视觉上吸引人的体验。

## 前置条件

在开始之前,请确保您已经在项目中配置了一切,例如`primeng`、`primeflex`和`primeicons`。如果没有,您可以运行以下命令:

```js
npm i primeng primeflex primeicons
```

之后,检查您的样式和主题是否已更新。以下是一个`styles.scss`文件的示例:

```js
// src/styles.scss
@import 'primeflex/primeflex.scss';
@import 'primeng/resources/themes/lara-light-blue/theme.css';
@import 'primeng/resources/primeng.css';
@import 'primeicons/primeicons.css';
```

提供的代码从不同的库中导入样式文件。通过导入这些文件,您将 `primeflex`、`primeng` 和 `primeicons` 提供的样式、类工具、主题和图标纳入您的应用程序中,使您能够利用它们的功能和视觉样式。

## 带有导航的页眉

页眉部分在为用户提供便捷的导航和无缝的用户体验方面发挥着至关重要的作用。为了实现页眉,我们将首先创建 HTML 结构并应用 CSS 类工具。以下是如何构建页眉的示例:

```js
// src/app/components/header/header.component.ts
imports: [CommonModule, ButtonModule, RippleModule, StyleClassModule],
...
<header id="header">
  <a class="flex align-items-center" href="#">
    PRIMEBOOK
  </a>
  <a
    class="cursor-pointer block lg:hidden"
    pStyleClass="@next"
    enterFromClass="hidden"
    leaveToClass="hidden"
  >
    <i class="pi pi-bars text-4xl"></i>
  </a>
  <nav
    class="hidden lg:flex absolute lg:static w-full"
    style="top:120px"
  >
    <ul ...>
      <li>
        <a ...>
          <span>Home</span>
        </a>
      </li>
      ...
    </ul>
  ...
  </nav>
</header>
```

让我们分解代码:

+   `<ul ...>...</ul>`:我们使用 `<ul>` 元素创建了一个导航项的无序列表。每个项由一个 `<li>` 元素表示,相应的链接被 `<a>` 标签包裹。以下是桌面上的导航结果:

![图 14.5 – 桌面上的导航布局](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_05.jpg)

图 14.5 – 桌面上的导航布局

+   `<a class="... lg:hidden" pStyleClass="@next" ...>`:这是使导航在小设备上响应式的代码行。让我们进一步分解它:

    +   `lg:hidden`:这利用了 `primeflex` 工具,将在大屏幕上隐藏汉堡菜单。

    +   `pStyleClass="@next"`:这个 `StyleClass` 功能针对下一个元素,即 `nav` 元素。在这种情况下,当在移动设备上点击汉堡菜单时,它将显示或隐藏导航菜单。

+   `<nav class="hidden lg:flex ...>`:这代表一个在小型屏幕上隐藏(`hidden`)并在大屏幕上可见(`lg:flex`)的导航菜单。

这是移动设备上的导航:

![图 14.6 – 移动设备上的导航](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_06.jpg)

图 14.6 – 移动设备上的导航

在图中,可以明显看出,不是显示完整的导航菜单,而是只显示一个汉堡图标来切换导航菜单的可见性,这样可以节省空间并提供一个干净的用户界面。

## 英雄部分

英雄部分是一个视觉上突出的部分,可以立即吸引访客的注意力,通常包括一个引人注目的标题、一个简洁的副标题,以及一个视觉上吸引人的图像或视频。英雄部分旨在创造一个强烈的第一个印象,并有效地传达产品或服务的价值主张。以下是我们的英雄部分的简化代码:

```js
// src/app/components/hero/hero.component.ts
<section id="hero">
  <div>
    <h1>
      <span class="font-light block">Revolutionize Your Workflow</span
      >All-in-One Solution
    </h1>
    <p>
      Unlock efficiency with our feature-packed SaaS platform. Take your
      business to new heights.
    </p>
    <button
      pButton pRipple
      type="button" label="Get Started"
    ></button>
  </div>
  <div class="hidden md:flex">
    <img
      src="img/hero-image.png"
      alt="Hero Image"
      class="w-9 md:w-auto"
    />
  </div>
</section>
```

总体而言,这段代码片段代表了一个带有标题、描述和 PrimeNG 调用到行动按钮的英雄部分。它还包括一个在中等尺寸(`md:flex`)和大屏幕上显示的图像,而在小型屏幕上隐藏(`hidden`)。

注意

PrimeFlex 中使用了以下屏幕尺寸断点:

- `sm`:小型屏幕(576 像素及以上)

- `md`:中等屏幕(768 像素及以上)

- `lg`:大型屏幕(992 像素及以上)

- `xl`:超大屏幕(1,200 像素及以上)

让我们看看结果:

![图 14.7 – 英雄部分(感谢 Adobe Stock)](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_07.jpg)

图 14.7 – 英雄部分(版权所有 Adobe Stock)

## 特性部分

特性部分突出了推广的产品或服务的核心特性或好处,旨在展示产品或服务的独特卖点并说服访客其价值。它通常包括一组视觉上吸引人的图标或图像,并附有每个特性的简要描述。让我们看看我们特性部分的简化代码:

```js
// features.component.ts
<section id="features">
  <div class="grid justify-content-center">
    <div class="col-12">
      <h2>Our Features</h2>
      <span class="text-600 text-2xl">Discover What Sets Us Apart</span>
    </div>
    <div
      class="col-12 md:col-12 lg:col-4"
      *ngFor="let feature of features"
    >
    <div class="{{ feature.bg }}">
      <i class="{{ feature.icon }}"></i>
    </div>
    <h5>{{ feature.heading }}</h5>
    <span>{{ feature.content }}</span>
    </div>
  </div>
</section>
...
features = [
  {
    heading: 'Intuitive Interface',
    content: 'User-friendly design with seamless navigation.',
    icon: 'pi pi-cog',
    bg: 'bg-yellow-200',
  },
  ...
]
```

提供的代码代表了一个展示一组特性的 HTML 结构。它还包括一个 Angular 指令(`*ngFor`),用于根据`feature`对象数组动态生成特性元素。让我们将其分解:

+   `<section id="features">`: 这行代码定义了一个具有`id`属性且设置为`"features"`的`<section>`元素。

+   `<div class="grid justify-content-center">`: 这段代码创建了一个类似网格的布局,并在网格内水平居中内容。

+   `<div class="col-12 md:col-12 lg:col-4" *ngFor="let feature of features">`: 这些类定义了不同屏幕尺寸的列布局,在大屏幕上显示三列(`lg:col-4`),在中等和小屏幕上显示一列(`col-12`)。`*ngFor`指令用于遍历`features`数组,渲染多个`feature`实例。

+   `features = [...]`: 这个`features`数组包含多个`feature`对象。每个`feature`对象代表一个单独的特征,并包括如`heading`、`content`、`icon`和`bg`等属性,这些属性将在模板中显示。

注意

12 网格系统是网页设计和布局中广泛使用的框架。它将屏幕划分为 12 个相等的列,提供了一个灵活且响应式的网格结构。这个系统允许设计师和开发者通过在网格中分配和排列元素来轻松创建响应式设计。我们讨论了这一点,以及 PrimeFlex,在*第六章*。

这里是结果:

![图 14.8 – 特性部分](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_08.jpg)

图 14.8 – 特性部分

## 推荐部分

推荐部分通过展示满意的客户的积极反馈或评论来提供社会证据。它包括以引言、客户姓名和可能还有他们的个人照片形式的推荐。本部分旨在建立信任和信誉,向潜在客户保证他们做出了正确的选择。以下是我们的推荐部分的简化代码:

```js
// testimonials.component.ts
<section id="testimonials">
  <p-carousel
    [value]="testimonials"
    [numVisible]="1"
    [numScroll]="1"
    [circular]="true"
    [autoplayInterval]="3000"
  >
    <ng-template let-testimonial pTemplate="item">
      <h3>{{ testimonial.name }}</h3>
      <span>{{ testimonial.title }}</span>
      <p>
        {{ testimonial.content }}"
      </p>
      <h4>Cool Company</h4>
    </ng-template>
  </p-carousel>
</section>
...
testimonials = [
  {
    name: 'Alice Johnson',
    title: 'CEO',
    content:
      'Exceptional service! The team went above and beyond to meet our requirements. Highly recommended.',
    company: 'Tech Innovators Inc.',
  },
  ...
]
```

提供的代码代表了一个利用 PrimeNG `Carousel`组件的推荐部分结构。让我们将其分解:

+   `<p-carousel ...>`: 这段代码将创建一个轮播图,并接受多个输入属性:

    +   `[value]`设置为`testimonials`数组,它提供了在轮播图中显示的推荐内容的数据。

    +   `[numVisible]`设置为`1`,表示每次只应显示一个推荐项目。

    +   `[numScroll]`设置为`1`,表示轮播图应每次滚动一个简介项目。

    +   `[circular]`设置为`true`,表示轮播图应循环并从开始处重新开始,当达到末尾时。

    +   `[autoplayInterval]`设置为`3000`,表示轮播图应每 3 秒自动过渡到下一个简介。

+   `<ng-template let-testimonial pTemplate="item">`:这段代码将每个`testimonial`对象的值设置为`testimonial`变量,并将用于在项目模板内渲染简介详情。

+   `testimonials = [...]`:这个数组包含多个简介对象。每个简介对象代表一个单独的简介,并包括如`name`、`title`、`content`和`company`等属性。

这里是结果:

![图 14.9 – 简介部分](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_09.jpg)

图 14.9 – 简介部分

## 价格部分

价格部分展示了产品或服务可用的不同定价选项或套餐。它通常包括一个比较表格,概述了每个套餐的功能和定价细节。价格部分的目的是帮助潜在客户做出明智的决定,并选择最适合他们需求的套餐。以下是我们的简化版定价表格代码:

```js
// pricing.component.ts
<section id="pricing">
  <div class="text-center">
    <h2>Choose the Perfect Plan</h2>
    <span>
      Select a plan that suits your needs and unlocks a world of
      possibilities.
    </span>
  </div>
  <div class="grid">
    <div class="col-12 lg:col-4" *ngFor="let plan of plans">
      <div>
        <h3>{{ plan.name }}</h3>
        <div>
          <span>$ {{ plan.price }}</span>
          <span>per month</span>
          <button pButton pRipple [label]="plan.cta"></button>
        </div>
        <p-divider class="w-full bg-surface-200" />
        <ul>
          <li *ngFor="let feature of plan.features">
            <i class="pi pi-fw pi-check"></i>
            <span>{{ feature }}</span>
          </li>
        </ul>
      </div>
    </div>
  </div>
</section>
...
plans = [
  {
    name: 'Basic Plan',
    price: 19.99,
    features: [
      'Access to core features',
      'Email support',
      'Standard storage',
      'Limited bandwidth',
    ],
    cta: 'Get Started',
  },
  ...
]
```

提供的代码代表一个使用网格布局显示不同套餐的价格部分。让我们来分解它:

+   `<div class="grid">`:这将为我们创建一个类似网格的布局。

+   `<div class="col-12 lg:col-4" *ngFor="let plan of plans">`:这些类定义了不同屏幕尺寸的列布局,在小屏幕和中屏幕上显示一个套餐,在大屏幕上显示三个定价套餐。`*ngFor`指令用于遍历`plans`数组,将我们的定价表格渲染到 DOM 中。

+   `<li ... *ngFor="let feature of plan.features">`:这将遍历套餐中的功能,并将每个功能渲染到无序列表下。

+   `plans = [...]`:这段代码代表包含多个`plan`对象的`plans`数组。每个`plan`对象代表一个单独的套餐,并包括如`name`、`price`、`features`和行动号召按钮等属性。

让我们来看看我们的价格部分:

![图 14.10 – 价格部分](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_10.jpg)

图 14.10 – 价格部分

## 页脚部分

页脚部分是着陆页的最低部分,通常包含额外的导航链接、联系信息和版权声明。它作为导航辅助工具,使用户能够访问重要链接或联系公司进行进一步咨询。以下是我们的页脚部分的简化代码:

```js
// footer.component.ts
<footer>
  <div class="grid justify-content-between">
    <div class="col-12 md:col-2">
      <a>
        <h4>PRIMEBOOK</h4>
      </a>
    </div>
    <div class="col-12 md:col-10 lg:col-7">
      <div class="grid text-center md:text-left">
        <div class="col-12 md:col-4">
          <h4> Company </h4>
          <a>About Us</a>
          ...
        </div>
        <div class="col-12 md:col-4">
          <h4> Support </h4>
          <a>Discord</a>
          ...
        </div>
        <div class="col-12 md:col-4">
          <h4> Social Media </h4>
          <a>
            <i class="pi pi-facebook"></i> Facebook
          </a>
          ...
        </div>
      </div>
    </div>
  </div>
</footer>
```

提供的代码代表页脚部分。让我们来分解它:

+   `<div class="grid justify-content-between">`:这一行创建了一个具有项目在容器起始和结束之间对齐的类似网格的布局。

+   `<div class="col-12 md:col-2">`: 这行代码定义了不同屏幕尺寸下标志的列布局。在这种情况下,它指定列应占据小屏幕的全宽(`col-12`),以及中等屏幕的 12 列中的 2 列(`md:col-2`)。

+   `<div class="col-12 md:col-10 lg:col-7">`: 这行代码定义了不同屏幕尺寸下导航的列布局。在这种情况下,它指定列应占据小屏幕的全宽(`col-12`),中等屏幕的 12 列中的 10 列(`md:col-10`),以及大屏幕的 12 列中的 7 列(`lg:col-7`)。让我们也看看页脚的导航细节:

    +   `<div class="grid text-center md:text-left">`: 这段代码创建了一个具有文本居中对齐的网格布局,适用于小屏幕(`text-center`),而对于中等屏幕则左对齐(`md:text-left`)。

    +   `<div class="col-12 md:col-4">`: 这行代码定义了每个导航部分的布局。在小屏幕(`col-12`)上,导航将占据全宽;然而,在中等或大屏幕上,导航将分为三个部分(`md:col-4`)。

让我们看看结果:

![图 14.11 – 页脚部分](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_11.jpg)

图 14.11 – 页脚部分

随着开发阶段的结束,我们的响应式 Web 应用程序现在已准备好进行实时部署。让我们开始我们旅程的最后一步:生产部署过程。

# 部署响应式 Web 应用程序

恭喜!您已成功使用 Angular、PrimeFlex、PrimeIcons 和 PrimeNG 组件构建了一个响应式 Web 应用程序。现在,是时候将您的作品与世界分享了。部署 Web 应用程序涉及几个关键步骤,从为生产准备项目到选择正确的部署平台。让我们探讨这些方面,以确保部署过程顺利。

## 准备项目以投入生产

在部署您的 Web 应用程序之前,确保一切已优化并准备好投入生产至关重要。以下是需要遵循的几个关键步骤:

+   *优化资源*:确保优化您的图片以减小其文件大小并提高加载时间。您可以使用图像压缩工具来实现这一点。以下是如何在不同屏幕尺寸下提供不同图片的示例:

    ```js
    <picture>
      <source srcset="image-small.jpg" media="(max-width: 576px)">
      <source srcset="image-medium.jpg" media="(max-width: 992px)">
      <source srcset="image-large.jpg" media="(min-width: 993px)">
      <img src="img/image-large.jpg" alt="Example image">
    </picture>
    ```

    在代码中,使用`<picture>`元素来定义多个图像源的容器。在`<picture>`元素内部,使用`<source>`元素根据媒体查询条件指定不同的图像源。例如,当屏幕宽度最大为 576 像素时,将提供`image-small.jpg`图像源。

+   *配置环境变量*:如果您的应用程序依赖于环境变量,请确保它们已为生产环境正确配置。这可能包括 API 密钥、数据库连接字符串或其他敏感信息。

+   *设置错误日志*: 实现错误日志来捕获和跟踪生产环境中可能发生的任何运行时错误。Sentry 或 Rollbar 等工具可以帮助你有效地跟踪和诊断问题。

通过遵循这些步骤,你可以确保你的 Web 应用程序已优化并准备好部署。

之后,你可以运行以下命令来构建你的应用程序:

```js
> ng build
Initial Chunk Files   | Names         |  Raw Size | Estimated Transfer Size
styles-QKTSICQI.css   | styles        | 497.28 kB |                33.26 kB
main-A7MWZFB4.js      | main          | 343.22 kB |                88.63 kB
polyfills-LZBJRJJE.js | polyfills     |  32.69 kB |                10.59 kB
                      | Initial Total | 873.20 kB |               132.48 kB
Application bundle generation complete. [11.871 seconds]
```

你可以看到,在运行构建后,我们创建了 3 个块文件,总耗时为 11.871 秒。

如果你使用的是 Angular 17,你的编译文件可能位于 `dist/chapter-14/browser`,如下所示:

![图 14.12 – 编译后的项目文件夹](https://github.com/OpenDocCN/freelearn-fe-framework-zh/raw/master/docs/nxtlv-ui-dev-primeng/img/B18805_14_12.jpg)

图 14.12 – 编译后的项目文件夹

对于小于 17 版本的 Angular,它将位于 `dist/chapter-14`。

## 部署的不同选项

一旦你的项目准备就绪,下一步就是选择一个部署平台。几个平台为 Angular 应用程序提供无缝部署。让我们探索一些流行的选项:

+   *Firebase Hosting*: Firebase Hosting ([`firebase.google.com/docs/hosting`](https://firebase.google.com/docs/hosting)) 提供了一种快速且安全的方式来托管你的 Web 应用程序。它支持持续部署、SSL 和自定义域名。以下是初始化和部署项目到 Firebase 的示例:

    ```js
       # Install Firebase CLI
       npm install -g firebase-tools
       # Set up a project directory
       firebase init
       # Deploy to Firebase
       firebase deploy
    ```

+   *Vercel*: Vercel ([`vercel.com`](https://vercel.com)) 提供了一个零配置的平台,用于部署前端应用程序。它与你的版本控制系统集成,实现自动部署。以下是使用 Vercel CLI 部署项目的示例:

    ```js
       # Install Vercel CLI
       npm install -g vercel
       # Deploy to Vercel
       vercel
    ```

+   *Netlify*: Netlify ([`www.netlify.com`](https://www.netlify.com)) 是一个强大的平台,它可以自动化你的构建和部署流程。它支持持续集成,并可直接从你的 Git 仓库进行部署。以下是从本地机器部署到 Netlify 的一个示例:

    ```js
       # Netlify CLI (if needed)
       npm install -g netlify-cli
       # Deploy to Netlify
       netlify deploy --prod
    ```

+   *GitHub Pages*: 如果你的代码托管在 GitHub 上,GitHub Pages 是一个简单直接的选择。当你向 `gh-pages` 分支推送更改时,它会自动部署你的应用程序。你可以在 [`docs.github.com/en/pages/getting-started-with-github-pages`](https://docs.github.com/en/pages/getting-started-with-github-pages) 上了解更多信息。

这些是建议的托管站点,你可以在这里部署你的 Angular 应用程序,但你也可以自由选择其他任何你喜欢的位置。如果你有自己的托管提供商,只需将编译文件的 内容上传到你的托管提供商,它将无缝地为你提供服务。

## 部署后的注意事项

恭喜,你的 Web 应用程序已上线!在庆祝之前,请考虑以下注意事项:

+   *监控性能*: 定期使用 Google Lighthouse 或 WebPageTest 等工具监控你的应用程序性能。解决可能影响用户体验的任何问题。

+   *安全考虑*:确保您的部署应用程序遵循 Web 安全的最佳实践。使用 HTTPS,保持依赖项更新,并在适用的情况下实施安全的身份验证机制。

+   *用户反馈*:鼓励用户对您的应用程序提供反馈。监控用户评价、评论和错误报告,以持续改进用户体验。

+   *文档更新*:更新您项目的文档以反映部署期间所做的任何更改。包括有关他人如何贡献或报告问题的信息。

+   *保持更新*:关注 Angular、PrimeNG 以及您使用的任何第三方库的更新。定期更新您的依赖项,以从新功能和安全补丁中受益。

总之,部署 Web 应用程序涉及仔细的准备和选择合适的平台。通过遵循最佳实践和选择可靠的部署选项,您可以确保您的响应式 Web 应用程序无缝地达到其受众。

# 摘要

恭喜您使用 Angular 和 PrimeNG 组件完成响应式 Web 应用程序的构建之旅!在本章的最后一部分,您已经获得了创建高效项目结构、实现响应式布局、集成各种 PrimeNG 元素以及将应用程序部署到与全世界分享的必要见解。

在本章中,我们了解到在当今的数字景观中,构建响应式 Web 应用程序至关重要。用户从各种设备访问应用程序,响应性确保了桌面、平板电脑和移动设备上的一致且愉悦的用户体验。本章中获得的知识使您能够创建适应不同屏幕尺寸的应用程序,为更广泛的受众提供可访问性。

随着您继续前进,这段旅程提供了成长和探索的机会。持续提升您的应用程序,关注 Angular 和 PrimeNG 的发布,并探索高级主题,如 PWA 和服务器端渲染。考虑为开源社区做出贡献,并将您的技能扩展到相关技术。拥抱在 Web 开发中持续学习和创新的路径。

总之,构建响应式 Web 应用程序是一项有益的尝试,它打开了众多可能性的大门。无论您是为客户、用户还是为了个人满足感创建项目,本书中获得的知识都为您提供了提供卓越用户体验的工具。随着您继续前进,拥抱在动态的 Web 开发领域中持续学习和创新的旅程。祝您编码愉快!
posted @ 2025-09-07 09:18  绝不原创的飞龙  阅读(58)  评论(0)    收藏  举报