原文:
zh.annas-archive.org/md5/C3E0BC11B26050B30F3DD95AAA2C59BD译者:飞龙
前言
近年来,现代 Web 应用程序开发已经发展了很多。这些应用程序采用了移动优先的方法进行设计和开发。Angular 是每个开发人员梦寐以求的框架,用于快速原型设计,以部署复杂的企业应用程序。本书将指导您使用 Bootstrap CSS 框架来快速启动 Angular 应用程序开发。本书的每一章都被构建为让您充分了解 Angular 框架的特定主题和方面,以及其他相关的库和框架。这些方面对于设计和开发强大的 Angular 应用程序至关重要。通过逐步的方法,您将学习以下各个阶段:TypeScript、Bootstrap 框架、Angular 路由、组件、模板、Angular Material、依赖注入等等。
到本书结束时,您将能够使用各种 CSS 框架(包括 Bootstrap、Material Design 和 NG Bootstrap)创建现代、响应式、跨平台的 Angular 应用程序。
这本书适合谁
本书经过精心思考,其内容涵盖了从适合绝对初学者到一些最复杂用例的主题。详细的解释、逐步的实际用例示例和本书的流畅易懂使其成为您收藏的必备品。
本书将使有少量或没有编程背景的开发人员受益,同时也将使经验丰富的开发人员受益。
本书涵盖的内容
第一章,快速入门,通过一个快速入门章节开始您的旅程,向您展示可用的可能性,并激发您的创造力。
第二章,ECMAScript 和 TypeScript 速成课,审查了 TypeScript 语言,从语言的基础到高级方面,这些方面在编写 Angular 应用程序时至关重要。
第三章,Bootstrap - 网格布局和组件,介绍了令人惊叹的 Bootstrap CSS 框架,并解释了您如何使用该超级库提供的一些组件和实用程序。
第四章《路由》使你能够通过学习有关路由的所有知识来熟悉 Angular 框架。从定义简单的路由路径到复杂的路由守卫等等,你将掌握构建应用程序坚固路由系统的知识。
第五章《Flex 布局- Angular 的响应式布局引擎》涵盖了替代布局和一个名为 Flex 布局的网格设计库,并解释了 Flex 布局如何为你的 Angular 应用程序提供强大的布局。
第六章《构建 Angular 组件》涵盖了 Angular 组件,这是现代渐进式 Web 应用程序的主要构建模块。你将学会构建多个组件,并将它们整合在一起构建视图和功能。
第七章《模板、指令和管道》介绍了 Angular 模板引擎、指令和管道。你将探索内置的指令、管道和模板。
第八章《使用 NG Bootstrap 工作》介绍了 NG Bootstrap,这是另一个你可以考虑在项目中使用的超强大框架。
第九章《使用 Angular Material 工作》解释了我们如何使用 Angular Material 提供的组件、指令和实用工具来开发我们的 Angular 应用程序。
第十章《使用表单》介绍了任何动态应用程序的核心。你将了解构建表单的不同方法,探索基于模板的表单、响应式表单等等。
第十一章《依赖注入和服务》涵盖了依赖注入、服务以及幕后的设计哲学。
第十二章《集成后端数据服务》是整个章节中你学到的所有代码、知识和实际示例的结合点。你将把端到端的屏幕,从 UI 到组件,再到服务和模型等等连接起来。你将学到一切你需要的知识,以便吸收 Angular 的所有方面。
第十三章,单元测试,解释了测试是现代软件开发中最重要的方面之一。本章将教你如何测试 Angular 应用程序。
第十四章,高级 Angular 主题,讨论了用户认证和强大的用户管理系统,还涵盖了将应用程序与 Google Firebase 和 Auth0 集成。
第十五章,部署 Angular 应用程序,探讨了如何部署您的 Angular 应用程序并使其达到生产就绪状态。
充分利用本书
为了充分利用本书中学到的信息,建议您快速复习编程基础知识,包括类、对象、循环和变量。这可以用您选择的任何语言来完成。Angular 应用程序不限于任何特定操作系统,所需的只是一个体面的代码编辑器和一个浏览器。在整本书中,我们使用了 Visual Studio Code 编辑器,这是一个开源编辑器,可以免费下载。
下载示例代码文件
您可以从您的帐户在www.packt.com下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support注册,直接将文件发送到您的邮箱。
您可以按照以下步骤下载代码文件:
-
在www.packt.com登录或注册。
-
选择“支持”选项卡。
-
点击“代码下载”
-
在搜索框中输入书名,然后按照屏幕上的说明操作。
文件下载后,请确保使用最新版本的解压缩或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Web-Development-with-Angular-and-Bootstrap-Third-Edition。如果代码有更新,将在现有的 GitHub 存储库上进行更新。
我们还有来自丰富书籍和视频目录的其他代码包,可在**github.com/PacktPublishing/**上找到。去看看吧!
使用的约定
本书中使用了许多文本约定。
CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:“截图显示了编辑过的 angular.json 文件。”
代码块设置如下:
"styles": [
"styles.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.min.js"
]
当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { JacketListComponent } from '../../projects/jackets/src/app/jacket-list/jacket-list.component';
import { VendorsComponent } from '../../projects/vendors/src/lib/vendors.component';
任何命令行输入或输出都以如下方式书写:
ng new realtycarousel
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。例如:“我们已添加了菜单链接,添加新列表。”
警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。
第一章:快速开始
你准备好迈向 Angular 的精通了吗?我猜你是的,有了这本书和你的决心,你一定会成功的。你购买了这本书,我不仅要感谢你,还要在这里向你做出承诺。事实上,是两个承诺。第一个是,如果你认真阅读材料,应用你在学习过程中获得的知识,并且在这些页面中和我一起构建示例应用程序,你就会在迈向 Angular 的精通之路上取得很大进展。
如果你和我一样,你的书架上堆满了成百上千本技术书籍,而你已经读过大部分。有些书开始的速度极其缓慢,深陷于理论和历史细枝末节,而其他书则开始得太快,让读者不知所措,想知道自己是不是太笨,无法理解材料。事实上,介绍读者可能全新的材料,而不让他们在阅读新获得的 400 多页技术书时打瞌睡,是一件棘手的事情。所以,我尊敬的初学者 Angular 大师,这就是我对你的第二个承诺。我承诺尽我所能找到技术和实际之间的平衡,让这本书在向你介绍新材料的同时,尽可能地有趣。
承诺已经说得很清楚了,让我们一起开始 Angular 精通之旅,快速浏览一下我们将在这个简洁但非常重要的第一章中涵盖的内容。
我们将迅速设置你的开发环境,并构建我们的第一个 Angular 应用程序,以便立即获得一些成就感。在编写它时,我们会略过细节,但在那之后,我们将更详细地介绍一些关键的 Angular 基础知识,然后结束本章。这些最基本的知识是你应该熟悉的第一件事情,因为在学习更高级的内容时,我们会一遍又一遍地使用它们。
在我们涵盖了这些基础知识之后,我们将从 Angular 语言转换,并且我们将看一下我们将在本书的其余部分一起构建的完整应用程序。作为一个奖励(在本书中有一些奖励,我希望能给您带来很多价值),我们还将涉及设计原则、线框图和一种很少使用的设计策略,称为纸质原型设计——其重点是可用性测试。纸质原型设计大约自 1985 年左右开始流行,并且在大约 2008 年左右被精益 UX 设计所取代。然而,我总是惊讶于我的许多客户甚至从未听说过纸质原型设计,但当他们尝试时,他们发现它给他们带来的价值,我也感到很高兴。
我们将在本章末尾对纸质原型设计进行高层次的介绍,紧接着线框图部分,这是讨论纸质原型设计的最合逻辑的地方。我们还将涉及一些 UX 设计原则,但不涉及精益 UX 设计过程本身,因为那会让我们偏离本书的重点。然而,如果您对精益 UX 设计过程感兴趣,这是一个很好的起点:www.interaction-design.org/literature/article/a-simple-introduction-to-lean-ux。
好的,我尊敬的初学者 Angular 大师,你准备好开始了吗?太好了!让我们开始吧!
本章我们将涵盖的主题有:
-
Angular 的发展
-
Angular 的构建模块
-
设置您的开发环境
-
编写您的第一个 Angular 应用程序
-
Angular 基础知识
-
我们的示例项目
-
纸质原型设计的过程
Angular 的发展
Angular 是一个基于前端 JavaScript 的 Web 应用程序框架,为您提供了构建强大的单页应用程序(SPA)所需的一切,包括厨房水槽。我们将一起构建的应用程序是一个 SPA,并且我们将在此过程中讨论 SPA 策略。
虽然 Angular 不是第一个基于 JavaScript 的前端 Web 应用程序框架,但它很可能是其中最强大的一个。这可能是因为 Angular 专注于 SPA,因为构建 SPA 应用程序比在您的网页上提供双向数据绑定要复杂得多。
Angular 最初发布于 2010 年晚秋。自那时以来,已经出现了数十个竞争库和框架,包括一些也具有大规模采用和大规模生产实施的库,如 Ember.js、Backbone.js 和 React.js。尽管 Angular 可能具有最高的学习曲线(我们将看到为什么会这样),但它仍然是其中最强大的一个。
乍一看,Angular 的命名和版本控制可能会令人困惑。这有几个原因,如下:
-
Angular 的 1.x 版本:基本上,任何在 Angular 2 之前发布的版本通常被称为 AngularJS。
-
AngularJS 不再处于积极开发模式。它已被置于长期支持模式下。
-
Angular 框架正在积极开发,因此开发人员在讨论它们时需要明确指出他们所指的是两个 Angular 框架中的哪一个。幸运的是,它们分别有两个完全专门的网站:
angularjs.org/和angular.io。Angular 团队采用了语义化版本控制,从 2.0.0 版本开始。您可以在这里阅读更多关于语义化版本控制的信息:semver.org。 -
Angular 2 是对 Angular 1.x(即 AngularJS)的完全重写,因此与 AngularJS 不兼容。虽然 Angular 4 并不是对 Angular 2 的完全重写,但它的核心库有一些变化,需要团队将其主要版本号从 2 增加到 4。版本 3 被完全跳过。
-
从 Angular 2 开始的所有发布通常被称为 Angular 2+,或者简单地称为 Angular。
-
由于采用了语义化版本控制,Angular 团队从未发布过 Angular 3,因此直接从 Angular 2 到 Angular 4。具体来说,路由器包的版本存在不一致,已经分发为版本 3.3.0。我们将在第四章中详细介绍 Angular 中的路由。不要让这使您感到困惑。只需知道从未有过 Angular 3。没什么大不了的。在 Windows 操作系统世界中,也从未有过 Windows 9。这些事情都会发生。
阅读完上述列表后,您可以看到为什么在 Angular 周围往往会有一些混淆。然而,只要记住以下两点,就会变得非常简单:
-
您真的应该只使用 Angular,而不是 AngularJS(除非您有一个非常好的理由)。
-
除了没有 Angular 3 之外,每年将有两个主要版本发布;它们应该在编号方案上是连续的(即 8、9 等),并且预计它们将向后兼容——至少在相同的主要版本号内(根据语义版本控制的精神)。
您可以在这里查看官方的 Angular 发布时间表:github.com/angular/angular/blob/master/docs/RELEASE_SCHEDULE.md。由于 Angular 是对 AngularJS 平台的完全重写,与 AngularJS 相去甚远,我们将完全跳过 AngularJS,首先看一下 Angular 的构建块——组件。跟上我吗?好的,让我们快速前进。
Angular 的构建块
添加新功能是发布新框架的事情,但幸运的是,基本的底层架构并不经常改变。当它改变时,通常不是完全的彻底改变。除了完全不同于其前身的 Angular 2.0 之外,到目前为止,所有主要版本发布基本上包含相同的架构。
现在让我们来看看框架的核心架构组件。
组件
组件就像小部件一样,负责在屏幕上的视图区域显示自己以及它们消耗和/或操作的数据。Angular 应用程序就像一个组件树,Angular 提供了组件之间双向通信的机制——从父级到子级和从子级到父级。
模板
组件依赖于它们的模板来呈现它们的数据。模板是您定义组件外观的地方,您可以添加样式来装饰您喜欢的任何方式。组件可以包含其模板(即 HTML)和其样式(即 CSS),直接在自身内部,或者引用模板和样式文件在自身外部。归根结底,世界上最花哨的前端框架产生 HTML、CSS 和 JavaScript,因为这三样是浏览器唯一理解的东西。
指令
在您为组件创建的模板中,Angular 使您能够使用称为指令的强大构造来更改 DOM。有用于控制屏幕上的渲染方式(即组件视图)的指令,例如重复 HTML 片段,根据条件逻辑显示内容,隐藏或显示内容,过滤数据数组等等。
模块
Angular 是模块化的。也就是说,它的功能被封装在称为 NgModule 的模块中,并且它们本身就是库。模块非常适合以有组织的方式将代码组合在一起。例如,有用于帮助处理表单、路由和与 RESTful API 通信的模块。许多第三方库被打包为 NgModule,因此您可以将它们整合到您的 Angular 应用程序中。其中两个例子是 Material Design 和 AngularFire - 我们将在后面的章节中查看这两个库。
服务
服务实际上并不是 Angular 的一个特定部分,而是一个非常普遍的概念,代表着应用程序组件可能需要消耗的封装功能、函数和特性。诸如日志记录、数据检索或几乎任何计算或查找服务等功能可以被编写为服务 - 这些服务可以存在于您的应用程序中,也可以存在于外部。您可以将服务视为提供某种服务(例如查找两个邮政编码之间的距离)并且做得很好的高度专业化的类。与组件一样,不仅有大量的第三方服务可以在您的 Angular 应用程序中使用,而且您还可以创建自己的自定义服务。我们将在第十二章中学习如何做到这一点,集成后端数据服务。
依赖注入
依赖注入(DI)或控制反转(IoC)是一种非常有用和常见的软件设计模式。这种模式用于将对象注入到依赖于它们的对象中。依赖于其他对象的对象可以直接使用它,而不需要担心它在哪里加载,或者如何实例化它 - 你只需在需要时使用它,就好像它在你需要它的时候就出现了。服务非常适合注入到我们的应用程序中。我们将学习如何在 Angular 中使用 DI,以及如何使用 Angular 的命令行界面(CLI)来生成我们自己设计的可注入服务。
在我们继续设置开发环境之前,这里有一些关于 Angular 的有趣事实:
-
AngularJS 是使用 JavaScript 构建的,而 Angular 是使用 TypeScript 构建的。虽然在编写 Angular 应用程序时这增加了一定程度的抽象,但使用 TypeScript 在构建更大的应用程序和更大的团队时提供了一些重要的优势-我们很快就会谈到这些。
-
AngularJS 基于控制器,而 Angular 是基于组件的。您将在第六章中学习有关组件的所有必要知识,构建 Angular 组件。
-
单页应用程序以难以实现搜索引擎优化(SEO)而臭名昭著,但 Angular 对 SEO 友好。
-
使用 Angular 也可以构建原生移动应用程序。
-
使用 Angular 也可以构建跨平台的桌面应用程序。
-
Angular 也可以在服务器上运行,使用 Angular Universal。
您必须承认,这是一个相当令人印象深刻和令人兴奋的清单。这些事情以及更多其他事情使学习 Angular 成为一项值得的努力,市场正在寻求 Angular 的专业知识。
设置您的开发环境
要开始使用 Angular,您需要安装Angular CLI;要安装它,您首先需要安装 Node.js 和npm(node 包管理器)。如果您已经安装了 Node.js 和 npm,太好了!如果没有,不用担心-它们很容易安装,我将在书的后面附录 A“使用 Angular 进行 Web 开发的工具链”中带您完成安装过程。在附录 A 中,我还将带您安装 Angular CLI 以及如何使用它构建 Angular 应用程序。为了简洁起见,从现在开始我将简称 Angular CLI 工具为 CLI。
如果您不确定是否已安装 NodeJS 和 npm,您可以通过在命令行上分别输入$ node -v和$ npm -v来快速检查。同样,您可以在命令行上输入$ ng -v来查看是否已安装 CLI。如果您收到版本号,那么您已安装了该特定工具(如我所示的下面的截图)。
注意:不要在命令开头输入$。$表示命令提示符,您要输入的命令的入口点。基于 Unix 的操作系统,如 macOS 和 Linux 系统,通常使用$或%作为命令提示符,具体取决于所使用的 shell,或者系统上的配置文件中指定的任何自定义设置。Windows 操作系统通常使用大于号>作为命令提示符。

如果其中任何命令无法识别,请快速跳转到附录 A,安装工具,然后立即回到这里。我会等着你。
我们还需要一个代码编辑器。今天有许多代码编辑器可用,包括一些免费的。虽然任何代码编辑器都可以,但我建议您在编写本书时至少使用 Visual Studio Code 进行 Angular 开发。原因是 Visual Studio Code 是免费的,跨平台的,是一个优秀的代码编辑器。这也是我在写这本书时使用的代码编辑器,所以当我建议使用某个扩展时,您可以轻松安装相同的扩展。
上述内容就是本章的全部内容。当我们开始构建示例项目时,需要我们有一个本地数据库,您还需要安装 MongoDB。MongoDB,也称为 Mongo,是一个很棒的免费跨平台 NoSQL 数据库。我会在附录 B,MongoDB中带您完成 Mongo 的安装过程。
此外,还有其他软件需要安装,例如 Chrome 扩展程序,我会在适当的时候告诉您它们是什么以及在哪里找到它们。现在,让我们开始编写一些 Angular 代码。
编写您的第一个 Angular 应用程序
当您开始尝试 Angular 代码时,作为您掌握这个强大的框架,通常有两种选择。第一种是使用在线代码编辑器,如 JSFiddle、Plunker、StackBlitz 等。在附录 C 中,使用 StackBlitz,您将学习如何基本使用 StackBlitz,以便您可以不时地使用它来测试一些快速代码,而无需在开发环境中需要测试项目。您可以在 StackBlitz 网站上访问:stackblitz.com。
第二种方法是使用您自己的本地开发环境——因为我们已经在前一节中设置了它,您可以创建一个项目,其唯一目的是运行一些快速示例代码,如果您宁愿使用本地开发环境而不是在线代码编辑器。我的目标是向您展示您有选择的余地——学习 Angular 并不只有一种方法来尝试一些代码。
当您使用在线代码编辑器(如 StackBlitz)时,您唯一需要安装的软件是浏览器——没有任何其他工具。虽然这使事情变得非常容易,但代价是您在所能做的事情上受到极大限制。话虽如此,我鼓励您尝试在线代码编辑器,但在本书中我们将只使用我们的开发环境。所以,让我们做到这一点,并在短短几分钟内一起创建一个小应用程序——我们将构建一个待办事项列表应用程序。
使用您的开发环境
从现在开始,我们将使用我们的终端、CLI 和 Visual Studio Code。前往code.visualstudio.com,在那里您可以下载适用于您选择的操作系统的 Visual Studio Code 安装包。
您的文件位置
在设置本地环境时,您当然可以将目录和文件放在任何您喜欢的地方。如果您有一个存放 Web 应用项目的文件夹,请立即转到该文件夹。如果您没有专门的项目存放位置,现在是养成有条理习惯的好时机。例如,在我的电脑上,我有一个名为dev的文件夹,用于我所做的任何开发。在我的dev文件夹中,我有一个名为playground的文件夹,其中有一个我正在学习或玩耍的每种技术的子文件夹。我喜欢在编写代码时使用 Mac,因此我存放 Angular play stuff的完整路径是/Users/akii/dev/playground/angular(如前几页终端屏幕截图底部所示)。同一屏幕截图还显示了我在写作时安装的 Node.js、npm 和 CLI 的版本。如果这样的目录结构适合您,请尽管使用。如果您已经有组织工作的方式,请使用它。重要的是要非常有纪律性和一致性地组织您的开发环境。
生成我们的待办事项列表应用程序
现在我们需要的安装已经完成 - 这意味着我们可以使用 CLI 工具 - 转到您的终端并在命令提示符处键入以下内容$ ng new to-dolist --style=scss --routing,然后按Enter。
ng命令运行 CLI 工具,其new命令指示它创建一个新的 Angular 应用程序。在这种情况下,应用程序的名称是to-dolist。您会注意到还有两个命令行标志,这是new命令的特殊选项。style 标志告诉 CLI 我们想要使用scss,而不是css,routing 标志告诉 CLI 我们希望它默认集成和设置路由。在本书中,我们将使用 SASS,而不是 CSS,并且 SCSS 是 Sass 文件的文件扩展名。作为提醒,我们将在第三章中进行 Sass 的速成课程,Bootstrap - 网格布局和组件。
第一次使用 CLI 创建您的 Angular 应用程序时,它将花费 45 秒到一分钟多的时间为您创建项目。这是因为它需要为您下载和安装各种东西,然后再创建项目的文件夹结构。但是,创建后续的 Angular 应用程序时,CLI 不会花费太长时间。
提供我们的待办事项应用程序
一旦 CLI 完成创建应用程序,您可以通过转到项目目录($ cd to-dolist)并发出$ ng serve命令来运行它。这将使 CLI 运行您的应用程序。CLI 的内置 Web 服务器默认情况下将在 localhost 端口4200上监听。顺便说一句,CLI 的 Web 服务器会监视您的项目文件,当它注意到文件中的更改时,它会重新加载应用程序 - 您无需停止服务器并再次发出服务器命令。这在开发过程中进行大量更改和调整时非常方便。接下来,打开浏览器并访问http://localhost:4200,您应该会看到类似以下内容的东西,这证明了 CLI 正在正确工作:

现在 CLI 已为您创建了待办事项列表应用程序,请在 Visual Studio Code 中打开该文件夹(注意:为简洁起见,我将把 Visual Studio Code 称为 IDE)。您应该在 IDE 的左侧面板中看到待办事项列表项目的文件夹结构,类似于以下内容(除了待办事项文件夹,您目前还没有;我们将在即将到来的组件子部分中介绍如何使用 CLI 生成它)。
以下是 IDE 中 to-do 列表项目的屏幕截图(app.component.ts文件已打开):

在开发 Angular 应用程序时,您将花费大部分时间在src | app文件夹中工作。
Angular 基础知识
组件是 Angular 的基本构建块。实际上,您可以将 Angular Web 应用程序视为一个组件树。当您使用 CLI 为 Angular 应用程序生成外壳时,CLI 还会为您自动生成一个组件。文件名为 app.component.ts,位于src/app文件夹中。应用程序组件是 Angular 应用程序的引导方式,意味着它是加载的第一个组件,所有其他组件都被拉入其中。这也意味着组件可以嵌套。之前的屏幕截图显示了我们的项目目录结构,src/app文件夹已展开,并且app.component.ts在 IDE 的文件编辑器窗口中打开。.ts文件扩展名表示它是一个 TypeScript 文件。有趣的是,当您编写 Angular 应用程序时,您使用的是 TypeScript 而不是 JavaScript。实际上,Angular 团队使用 TypeScript 编写 Angular!
在以下组件部分之后,您将找到我们 Angular 应用程序的完整代码清单。有六个文件需要编辑。其中三个已经在您使用 CLI 生成的应用程序中可用。另外三个将在您使用 CLI 生成待办事项组件后在项目中可用,这是您比较项目结构和之前屏幕截图时目前缺少的目录。您将在以下组件部分中了解如何做到这一点,这就是为什么完整的代码清单被插入在后面。不要担心 - 跟着走,相信自己可以掌握 Angular,一切都会好起来的。如果你不相信我,就躺在地板上,慢慢地喃喃地说这些话,“这也会过去”,三次。
组件
这一部分是关于 Angular 组件的高层次概述 - 对 Angular 组件是什么的足够覆盖。《第六章》《构建 Angular 组件》完全专门讨论了 Angular 组件,我们将深入研究它们。可以将本节视为窥探组件幕后的一点,当我们讨论组件时,我们将拉开窗帘,好好看看“奥兹国的组件巫师”。请记住,在《奥兹国的巫师》故事中,多萝西和小伙伴们都害怕巫师,但当他最终在窗帘后面显露出来时,他们很快就不再害怕了。
正如前面提到的,你可以将组件视为 Angular 的基本构建块,将你的 Angular 应用程序视为嵌套组件树。按钮、进度条、输入字段、整个表格、高级的东西如轮播图,甚至自定义视频播放器 - 这些都是组件。你网页上的组件可以相互通信,Angular 有一些规则和协议来指导它们如何进行通信。在本书结束时,你将对组件的方方面面非常熟悉。你必须熟悉,因为这就是 Angular 大师的方式!
当你编写一个组件文件时,就像下面的代码一样,它有三个主要部分。第一部分是导入部分。中间部分是组件装饰器,你可以在这里指定组件的模板文件(定义组件的外观)和组件的样式文件(用于为组件设置样式)。
注意:由于我们使用了style=scss标志,我们得到的文件是 SCSS,而不是传统的 CSS 类型文件。导出部分是组件文件中的最后一部分,是组件所有逻辑的放置位置。组件的 TypeScript 文件中可以放入比下面代码片段中显示的更多内容,我们将在《第六章》《构建 Angular 组件》中看到。
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';
}
CLI 在创建我们的应用程序时默认为我们创建了应用程序组件,但是我们如何创建自己的组件呢?生成新组件的最简单方法是使用 CLI 并发出以下命令:$ ng generate component name-of-component。因此,要生成一个名为to-doitem的新组件,我们将在命令提示符中键入$ ng generate component to-doitem。请记住要在src | app文件夹内执行此操作。CLI 将生成此组件并将其插入到自己的文件夹中,新创建的文件夹的名称将与组件相同。
在这个文件夹中,您将看到四个新文件,它们的名称都以to-doitem.component开头,因为我们的组件名称是todoitem,嗯,它是一个组件。我们将在后面讨论以spec.ts结尾的文件用于什么,但您可能已经猜到其他三个文件的用途。让我们验证您可能已经在想的内容;组件文件本身的确是名为todoitem.component.ts的文件。该文件包含对其他两个文件的引用:todoitem.component.html,它是组件的模板(HTML 代码,用于定义其标记结构),以及todoitem.component.scss文件,它将保存组件的样式。此外,CLI 修改了一个名为app.module.ts的现有文件。我们将在稍后更详细地讨论这个文件,但现在,您需要知道的是,该文件充当了应用程序组件的注册表。
您可能会想,“这是很多文件。它们都是必需的吗?”对此的简短回答是否定的。在第五章中,Flex-Layout – Angular 的响应式布局引擎,我们将看到如何消除.html文件和.scss文件,只需将所有组件的内容(HTML 和样式)放入组件文件中。然而,Angular 团队提供了将所有这些内容分开的机制的原因是为了使应用程序的代码整洁有序。稍后您可以感谢他们。
在使用 CLI 生成组件时的一个很好的快捷语法是键入$ ng g c name-of-component,其中g是生成的缩写,c是组件的缩写。
除了从头开始创建自己的组件,我们将在第五章中深入研究,Flex-Layout – Angular 的响应式布局引擎。
待办事项列表应用程序的代码清单
现在您已经生成了待办事项组件,您在todo文件夹内有四个新文件。您将编辑其中三个文件,使其看起来像下面的代码清单。您还需要编辑项目中已经存在的三个文件,(在这里我们将打开窗帘,见到巫师),我们还可以将其他库和框架的组件集成到我们的应用程序中。我们将在第六章中看看如何使用 NG Bootstrap 进行此操作,以及在第七章中使用 Angular Material,模板、指令和管道。Angular 的组件不少,随着时间的推移可用的数量只会增加。
每当我学习新技术并跟着书籍、博客文章或其他内容时,我都会手动输入所有内容,即使文件可以下载。是的,手动输入可能是一个乏味的过程,但它会激发您的大脑,并且材料和概念开始被吸收。简单地下载文件并将内容剪切粘贴到您的应用程序中并不会产生同样的效果。我会让您决定您想要走哪条路。如果您选择下载代码,本书开头有相应的说明:
todo.component.html(在src | app | todo文件夹内)的代码清单如下所示:
<div class="container dark">
<div class="column">
<p>Add a todo item</p>
</div>
<div class="column">
<p>Todo list ({{ itemCount }} items)</p>
</div>
</div>
<div class="container light">
<div class="column">
<p class="form-caption">Enter an item to add to your todo list</p>
<form>
<input type="text" class="regular" name="item" placeholder="Todo item ..."
[(ngModel)]="todoItemText">
<input type="submit" class="submit" value="Add todo" (click)="addTodoItem()">
</form>
</div>
<div class="column">
<p class="todolist-container" *ngFor="let todoItem of todoItems">
{{ todoItem }}
</p>
</div>
</div>
todo.component.ts(在src | app | todo文件夹内)的代码清单如下所示:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.scss']
})
export class TodoComponent implements OnInit {
itemCount: number;
todoItemText: string;
todoItems = [];
ngOnInit() {
this.itemCount = this.todoItems.length;
}
addTodoItem() {
this.todoItems.push(this.todoItemText);
this.todoItemText = '';
this.itemCount = this.todoItems.length;
}
}
todo.component.scss(在src | app | todo文件夹内)的代码清单如下所示:
.container {
display: grid;
grid-template-columns: 50% auto;
}
.column {
padding: .4em 1.3em;
}
.dark {
background: #2F4F4F;
}
.light {
background: #8FBC8F;
}
input.regular {
border: 0;
padding: 1em;
width: 80%;
margin-bottom: 2em;
}
input.submit {
border: 0;
display: block;
padding: 1em 3em;
background: #eee;
color: #333;
margin-bottom: 1em;
cursor: pointer;
}
.todolist-container {
background: rgb(52, 138, 71);
padding: .6em;
font-weight: bold;
cursor: pointer;
}
.form-caption {
}
- 以下是
app.component.html(在src | app文件夹内)的代码清单。第一章,快速入门:待办事项列表(快速示例应用):
<br> <br>
<app-todo></app-todo>
<router-outlet></router-outlet>
app.module.ts(在src | app文件夹内)的代码清单如下所示:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { TodoComponent } from './todo/todo.component';
@NgModule({
declarations: [
AppComponent,
TodoComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
styles.scss(在src文件夹内)的代码清单如下所示:
/* You can add global styles to this file, and also import other style files */
body {
font-family: Arial, Helvetica, sans-serif;
color: #eee;
background: #869bbd;
padding: 4em;
}
a {
color: #fff;
text-decoration: none;
}
ul {
list-style-type: none;
margin: 0 0 2em 0;
padding: 0;
}
ul li {
display: inline;
margin-right: 25px;
}
ul li a {
font-size: 1.5em;
}
太棒了!现在您已经把所有的代码放在了正确的位置。您还记得如何运行您的 Angular 应用程序吗?在命令提示符处输入$ ng serve,一旦出现编译成功的消息,打开浏览器并转到http://localhost:4200。应用程序是否正常工作?如果是,恭喜您建立了您的第一个 Angular 应用程序!如果不是,请检查拼写错误。
玩一下您的新应用程序。我们还没有花时间添加编辑待办事项或删除它们的功能,但您可以通过点击浏览器的刷新按钮来清除它。
为什么刷新页面后会清空数据?这是因为我们使用的是单页应用,没有将输入的数据持久化到数据库中。当我们构建更大的应用程序时,我们一定会确保在本章末尾向您介绍的应用程序中添加持久化数据的能力。
插值
插值是从组件类中的变量获取值并在组件模板中呈现的方法。如果您还记得,组件的逻辑放在组件类的导出部分。这也是您想要使用插值的变量所在的地方,以便在模板中呈现它们的值(即在网页上呈现)。假设您有一个名为items的变量,其值目前为4。要在模板中呈现该值,您可以使用一对双大括号,变量位于其中。变量和组件逻辑都写在类内部。
别担心——在整本书中,我们会看到很多使用插值的代码片段,但现在,您可以看到这个示例代码,展示了它的作用。目前,这段代码是无意义的,是硬编码的,但它确实演示了插值。
第一个屏幕截图是组件文件(home.component.ts);变量在第 10 行声明:

第二个屏幕截图是组件的模板文件(home.component.html)。注意第 6 行的一对双大括号:

最后一个屏幕截图显示了呈现的值,这里是4。这就是插值的基础。在整本书中,随着我们在我们的注释相册上的工作,我们会看到更高级的用法:

模板化和样式
在组件部分的最后几段中,我们已经提到了有关模板和样式的内容。现在我们有一个小项目可用——我们用 CLI 创建的一个——我们可以看看这在代码中是什么样子。在 IDE 中打开您的 Angular 项目,并打开app.component.ts文件。这个应用组件文件的第 5 和第 6 行包含了它关联模板(.html文件)和样式文件(.scss)的引用。以下是我 IDE 中打开项目的屏幕截图,app.component.ts文件已打开:

属性绑定
在 Angular 中,我们可以进行两种数据绑定,即单向和双向。内插类似于单向数据绑定。这是因为在这两种情况下,数据都是从组件类流向组件模板,而不是相反。属性绑定是数据绑定,因为数据被绑定到属性。
也可以进行双向属性绑定,意思是不仅可以将组件属性的值绑定到模板,而且模板也可以改变组件属性的值。这在 Angular 中通过ngModel实现。不用担心这个,我们稍后会看到双向属性绑定。只需知道在 Angular 中,单向和双向属性绑定都是可能的。
实现单向属性绑定非常简单。您只需要在组件模板中的 HTML 属性周围加上方括号,并将变量分配给它。要看一下单向属性绑定在代码中是什么样子的快速示例,请查看接下来的三张屏幕截图。
第一张屏幕截图是组件文件(home.component.ts);变量txtPlaceholder在第 11 行声明:

下一张屏幕截图是组件的模板文件(home.component.html)。在第 14 行,您可以看到输入元素的占位符属性周围有方括号:

最后一张屏幕截图是应用程序在浏览器中运行的情况。您可以看到文本“在此输入您的待办事项”被插入为文本框的占位文本,通过单向属性绑定:

事件绑定
在 Angular 中,事件绑定简单地意味着在组件内的元素上注册一个事件,当该事件发生时,将触发调用一个函数。换句话说,一个事件将调用一个函数。你可以让 Angular 监听大量的事件,比如按钮被点击,鼠标悬停在图像上,或者当用户在文本框中按下键时,还有很多其他事件。当然,你可以编写任何你能想到的函数来实现其他功能,比如调用 web 服务,改变背景页面的颜色,计算 Pi 的值到 1000 位小数,或者几乎任何你想要的其他功能。但是,我们如何在我们的 Angular 应用程序中设置事件绑定,以便将我们感兴趣的事件,绑定到我们感兴趣的元素,运行我们想要的函数呢?幸运的是,Angular 团队为我们做到了这一点。
假设我们想通过点击或悬停鼠标等事件进行一些用户交互 - 我们可以使用事件绑定来映射功能。
现在,这个例子并不是很有趣,但我们有我们的待办事项列表应用程序,可以查看我们已经编写的代码。如果你已经输入了代码清单。
我们的示例项目
学习一门新的编程语言,或者学习一个新的框架,是一个动手实验和重复的问题。即使是《生活大爆炸》中的谢尔顿博士也不能只靠读一本关于 Angular 的书就学会它。然而,只是跟随随机的代码示例并不是一件有趣的事情,而且到最后,你实际上并没有任何可以使用的东西。因此,我们在学习 Angular 的过程中采取的方法是构建一个完整的网络应用程序,这样做既有趣又实用,因为你可以部署并自己使用它。
注释照片相册
我们将一起构建的应用程序是基于我推出的一个在线服务之一,名为 Vizcaro。Vizcaro 是一个照片分享服务,但与其分享单个照片不同,你分享相册(一组照片)。此外,照片和相册将被注释,因此你可以为它们添加标题和说明。我们的版本不会拥有我在线服务提供的所有功能,但它将有足够的部分,使它成为一个很好的网络应用程序,以便学习本书中的材料。
设计原则
通常有两种设计类型:设计用户界面(GUI)的方式,以及设计软件组件(API 接口、服务、组件等)的方式。在本书中,我们将涵盖许多代码设计原则。Angular 是一个设计非常出色的软件,这对我们来说非常好,因为它为我们提供了一个完美的机会,在学习 Angular 本身的同时讨论软件设计,以及在构建我们的应用程序时。在本书的剩余部分中,我们还将涵盖用户界面设计原则,特别是在使用线框来指导我们构建模板时。
一般来说,当讨论用户界面设计时,会使用 UX 设计这个术语。从维基百科借来 UX 设计的定义:
“UX 设计是通过改善产品的可用性、可访问性和提供的互动乐趣来增强用户对产品的满意度的过程。”
这是一个很好的定义,适用于不仅仅是软件产品。
线框
线框从 80 年代初就开始存在。它们的重点,至少最初,是桌面应用程序中屏幕的功能(请记住,当时还没有网络应用程序),以及其一般布局。它们并不是用来展示最终设计的样子,包括字体选择、颜色和屏幕上控件的其他属性。实质上,它们是“纸上原型”。相反,纸上原型是使用线框的过程。值得注意的是,名词“线框”和“模型”可以互换使用,它们是同一回事。我将在本章末尾简要介绍纸上原型的过程。
线框工具
正如你可能已经猜到的,或者已经知道的,有几种工具可用于创建线框图,用于布局你的应用程序,比如 Balsamiq Mockups,Mockflow 和 Visio。对于我的 Web 应用程序,以及本书中,我更喜欢使用 Balsamiq Mockups。你最终使用哪种工具,或者已经在使用哪种工具,都无关紧要。事实上,即使你的线框图是用笔在你最喜欢的快餐餐厅餐巾纸的背面手绘的,我也觉得很酷。说真的,重要的是在编写一行代码之前养成创建线框图的习惯。为什么?因为这是明智的做法,可以节省大量时间。此外,这给了你一个完美的机会来真正思考你将要构建的东西。而且,这是你可以向用户展示的东西,以便在编写一行代码之前获得他们对可用性的反馈。还有更多好处;它让你对如何为应用程序设计数据模型以及可能使用的服务的 API 有了一些想法。你会在没有商业计划的情况下开始一项业务吗?你会在没有蓝图的情况下建造你的梦想之家吗?构建 Web 应用程序不应该有任何不同,使用线框图规范页面。永远。明白了吗?
我们注释的相册的线框图
我们将使用 10 个线框图来构建我们的应用程序,每个屏幕都有一个。以下是它们的列表,每个屏幕截图前都有简短的描述。
主页
每个 Web 应用程序都需要某种起始页面。它通常有许多名称,通常是这些之一:主页,登陆页面,索引页面或闪屏页面。我们的将是直接的。没有 Flash 动画或彩虹色的背景;它将是一个简单的页面,让用户知道网站的功能,并希望在五到七秒内完成。如果不能,你可能会永远失去访问者。

仪表板
大多数网络应用程序没有仪表板页面,但那些有的通常会提供用户拥有的东西摘要,他们上次登录的时间,以及公司希望引起用户注意的任何通知。如果您使用在线银行业务,很可能您的银行在线银行网络应用程序有一个仪表板页面 - 它可能是账户列表(支票、储蓄、信用卡、汽车贷款等),以及您在这些账户上的余额。
我们将构建一个用户将用来创建相册的应用程序,因此我们的仪表板将包含我们上传的照片数量,相册数量,上次登录时间等:

图片上传
由于我们的应用程序应该让用户创建相册,我们最好让他们有办法上传照片!是的 - 我们将专门为上传一张照片而设立一个整个网页,因为我们将在上传后使用同一个页面来预览它 - 并且撤消上传。您会惊讶地知道,有一个著名的照片分享网站直到您转到照片列表才会显示您刚刚上传的内容!立即确认您打算上传的照片实际上就是已上传的照片:

照片准备
上传照片是我们注释相册应用程序的第一步。我们将为准备照片专门设置另一个网页。这是我们将允许用户调整图像大小并对其进行注释(给它一个名称和标题)的地方。查看相册时,照片的标题将显示出来:

创建相册
在用户可以在他们的相册中添加照片之前,他们必须能够创建相册。以下网页将用于此目的:

照片列表
您总是需要考虑可用性以及如何设计尽可能直观的用户界面。此页面将显示用户上传的所有照片列表。此外,他们可以在同一页面上编辑任何照片的名称和标题。您的用户需要跳转的页面越少,他们就会越开心:

相册列表
这个页面为相册做了前一页为照片所做的事情——提供了用户创建的所有相册的列表,并提供了一种直观的方式来编辑它们的名称和描述(而不是照片的标题),而无需转到另一个网页:

工作台
工作台是用户可以将照片拖放到相册的地方。这将是我们让用户直观地将特定照片与特定相册关联的方式。顺便说一句,我们的拖放功能不仅直观而且功能强大,还会为用户增添一些乐趣。从心理上讲,用户希望在网站上“玩耍”。拖放——虽然不是令人惊叹的体验——比从照片下拉菜单中选择照片,然后从相册下拉菜单中选择相册,最后点击“连接”或“关联”按钮更有趣。前一种方法会让用户满意,而后一种方法会让他们发脾气,然后离开网站,再也不回来了。

相册查看器
归根结底,用户希望以一种引人入胜的方式查看他们的相册。拖放的东西很有趣,但他们不是为了这个而来。他们来这里是为了看到儿子生日派对的照片,女儿高中毕业的照片,或者他们梦想家园的照片。这对他们来说是一个重要的页面;这是他们使用我们网站的工作将为他们付出的地方。让我们不要让他们失望:

这就结束了我们在本书剩余部分将要构建的带注释的相册、线框和本章计划涵盖的材料的介绍。然而,我想快速讨论一下纸面原型,作为本章的结束,并将其与我们的 Angular 应用程序的规划联系起来。
纸面原型
正如本章开头提到的,纸面原型设计是一个过程。我们还提到纸面原型设计的核心重点是可用性测试。我们没有提到的是,纸面原型设计应该成为你的开发团队使用的软件开发方法论的一部分——无论是瀑布模型还是某种敏捷形式。具体来说,纸面原型设计应该在需求文档交付给项目经理后立即进行。这就是纸面原型设计是什么以及它在哪里适用的高层视图。
让我们现在来看一下在较低层面上的流程机制,也就是开发团队与即将开发的应用程序的用户之间的互动。
纸面原型设计的机制或流程是首先创建线框图并打印出来(我知道,会有更多的树被砍伐,全球变暖会变得更加严重,但纸面原型设计很重要)。一旦纸质版本摆在你面前,你的老板、客户或一群预期用户(比如焦点小组),你或其他人会像点击鼠标一样使用纸面原型,就好像它已经完成并投入生产一样。你会要求他们假装它是实际的完成应用程序。起初听起来很傻,但人类有着令人难以置信的想象力,只需很少的努力,他们就会像使用真正的东西一样使用它!这不是催眠,而是一种非常神奇的事情开始发生。除了在一开始解释你要求他们做什么以及为什么要求他们这样做时,他们会开始自发地大声谈论他们正在采取的行动,或者正在考虑采取的行动,比如,“好的,现在我需要填写这个表格并提交”,或者“按钮在哪里可以撤销我刚刚做的事情。我犯了一个错误”。通过这种练习,你从人们那里得到的最好的信息是他们提出了如何改进某些东西的建议,比如“如果我能轻松地导航回到我…”你能想象编写网页然后意识到被要求进行的更改具有深远影响并且需要耗费大量时间吗?这经常发生。
你意识到通过这样做你得到了什么了吗?你有了测试用户,而且你还没有写一行代码!这是一个非常有力的练习!试一试,然后给我发电子邮件分享你的故事。
所以,当我向客户解释这一点时——不是作为用户,而是如何向他们的用户和/或客户展示纸质原型制作过程时,我通常会被问到,*但这是纸张。我们怎么改变屏幕呢?*我会尽我所能地回答——通过向他们展示一个例子。我通常会带着一套线框的样本。不仅是为了演示纸质原型制作过程,也是为了展示一个良好的线框的例子。我把登录屏幕放在我们坐着的桌子上,然后让他们用手指作为鼠标指针登录,并在他们想象中的键盘上输入。在他们轻笑并和我一起假装在线框下输入他们的用户名和密码后,他们点击登录按钮,然后我成为了电脑——我拿起登录线框,放下仪表盘线框。他们通常停止轻笑,看着仪表盘页面,停顿几秒钟,然后看着我点头说,这很酷。我明白了。
总结
这一章涵盖了各种各样的主题,我知道。这是不可避免的,因为没有一个最佳的起点适用于所有读者。例如,一些读者知道线框是什么,并且已经使用了多年,而其他读者可能刚刚听说过这个术语,甚至连那都不知道。这是本书的第三版,但它与前两版有很大不同,即使它基本上是相同的,这并不意味着读者已经阅读了前两版。你可以把这一章看作是一种漏斗——一个足够宽的漏斗,将各种经验水平和不同知识的读者引入学习 Angular 和本书涵盖的其他共生技术的共同轨道。从第二章开始,ECMAScript 和 TypeScript Crash Course,漏斗方法结束了。其余的章节将更加专注于手头的主题。所以,感谢你和我一起坚持下来。不过,我希望在翻阅这前几十页时,有一些事情是值得的,即使你对 Angular 并不完全陌生。
回顾一下,我们涵盖了 Angular 的演变,包括其语义版本和发布计划。虽然 NodeJS、npm 和 CLI 的安装在附录 A 中有介绍,但本章是引导该讨论的基础,并且我们使用 CLI 来构建我们的第一个 Angular 应用程序和一个待办事项列表应用程序。我们将应用程序命名为待办事项列表,因为我们是开发人员,而不是营销人员(眨眼)。我们还介绍了如何使用 StackBlitz 来构建相同的 Angular 应用程序,而不依赖于我们的本地开发环境。然后,我们介绍了 Angular 的第一个非常基本的构建模块,你需要很好地了解它们,因为它们将在你构建任何 Angular 应用程序时一再使用。具体来说,这些是模板化、属性绑定、事件绑定和类绑定。最后,我们介绍了我们将在整本书中一起构建的注释相册应用程序,并在此过程中介绍了 UX 设计原则、线框图和纸质原型。哇!天哪!
在下一章中,我们将首先了解 JavaScript 和 TypeScript 之间的关系。然后,正如其名称所示,我们将对 TypeScript 进行快速入门,并介绍它相对于 JavaScript 的优势。
第二章:ECMAScript 和 TypeScript 速成课程
第一章,快速入门,是一个杂乱的主题混合,可能看起来有点松散,但是以这种方式呈现是为了铺设与前端 Web 应用程序开发相关的一大片材料和主题,显然是为了开始你成为 Angular 大师的冒险。从这一点开始,每一章都将尽可能保持专注,并且专门致力于特定的覆盖领域。随着你逐渐深入各章,你会发现它们变得越来越技术化。这是一个自然的进步,不必害怕,因为你可能还记得,我在第一章,快速入门中对你做出的承诺之一是不要陷入技术细节以至于达到收益递减的地步。换句话说,不会有任何对我们目的没有任何价值的深奥技术的废话。相反,我们会变得尽可能技术化——不多,也不少。此外,材料将以一种引人入胜的方式呈现,你将以最小的努力获得最大的保留可能性。
不过,请不要认为这意味着你不必努力。和生活中的任何其他事情一样,你想要变得更优秀,就需要付出更多的努力。种瓜得瓜,种豆得豆。话虽如此,本章和下一章将是逐渐升级到接下来的技术深入探讨的过渡阶段——有点像在我们开始在整本书中运用 Angular 技术之前的热身。
本章将涵盖以下主题:
-
JavaScript 和 TypeScript 之间的关系。
-
TypeScript 的速成课程
(快速)路线图
本章是关于 TypeScript 的速成课程,旨在为已经熟悉 JavaScript 的开发人员快速过渡到 TypeScript 提供帮助。
正如在第一章中提到的,TypeScript 是我们在本书中处理 Angular 特定事务时将使用的语言,因此本章将作为您准备好使用 Angular 进行 Web 开发的程序化部分。您可以将第三章《Bootstrap-网格布局和组件》视为本章的表兄弟,因为它的目标类似,但是针对的是呈现方面(即网页布局),而不是程序化方面。第二章《ECMAScript 和 TypeScript 速成课程》和第三章《Bootstrap-网格布局和组件》将一起完成构建客户端 Web 应用程序的先决条件-无论客户端 Web 应用程序框架如何,但也特别适用于基于 Angular 的应用程序。从第五章《Flex-layout-Angular 的强大响应式布局引擎》开始,几乎都将是以 Angular 为中心。简而言之,这是本章和下一章的路线图。让我们开始热身我们的技术肌肉!
JavaScript 和 TypeScript 之间的关系
JavaScript 和 TypeScript 是密不可分的。因此,虽然本章涵盖了 ECMAScript 和 TypeScript 两种技术,但它们足够相似,以至于本章可以同时涵盖两者。它们彼此有多相似?嗯,大部分时间,您可以将 TypeScript 视为 JavaScript 的超集。它们之间关系最有用的描述是:TypeScript 是一种带有许多强大功能和可选类型的严格类型语言,通过其转译器,它变成了普通的 JavaScript。这对开发人员来说非常重要,并带来了几个优势;这足够引人注目,以至于谷歌的 Angular 团队决定从 JavaScript 转换到 TypeScript 来开发 Angular 本身。我们将很快介绍转译器是什么,以及使用 TypeScript 的优势是什么。
JavaScript 的一系列幸运事件
在我们深入技术部分和代码之前,值得快速看一下 JavaScript 的演变以及导致需要像 TypeScript 这样的语言的一些驱动因素。此外,就像 Angular 的命名混乱在开发社区中引起了一些困惑,自从 20 多年前诞生以来,JavaScript 的版本过去更加混乱,因此我想尝试澄清一些关于 JavaScript 版本命名的混乱。更重要的是,我想讨论一下我所说的 JavaScript 的一系列幸运事件。这将有助于为我们在本书的其余部分一起涵盖的大部分内容设定节奏。
我必须承认,我喜欢使用 JavaScript。我一直喜欢这种语言,不是因为语言本身,而是因为它让我们能够使网络生动起来,而无需其他插件,如 Flash 或 Shockwave。然而,近年来,我喜欢使用这种语言的原因还有一些额外的原因,而我喜欢 JavaScript 的确切原因正是我即将介绍的一系列幸运事件。话虽如此,我在行业中有一些朋友持相反的观点,他们认为 JavaScript 是一种“玩具语言”,并且更倾向于使用 Java 和 C#等语言,尽量避免使用 JavaScript,直到他们不得不勉强为客户端编写一些代码。这些老手对 JavaScript 的使用通常不会超出使用 jQuery 库将点击事件绑定到函数调用(以提交表单数据到他们的 Java 或 C# API)。当然,大约十年前,由于 JavaScript 只能在客户端(即浏览器)上运行,没有那么多的库,也没有真正高性能的运行时,因此 JavaScript 并不像 Java 或 C#那样强大。所有这些都将因为一系列幸运事件而发生改变,具体来说,有三个。让我们快速回顾一下。
Chromium 项目
第一个是谷歌的 Chromium 项目。2008 年 9 月,谷歌发布了 Chrome V8,这是一个高性能的 JavaScript 引擎。谷歌的 Chrome V8 极大地提高了 JavaScript 代码的执行方式。这是一个非常成功的项目,它使其他技术得以实现,包括真正立即和永远改变了 JavaScript 未来的技术:Node.js(简称 Node)。
JavaScript 框架
JavaScript 的统治地位作为最重要的 Web 应用程序编程语言,甚至可能是移动应用程序和甚至桌面应用程序的事件系列中的第二个事件,是 JavaScript 框架的爆炸式增长。自 2010 年以来,开发世界对创建基于 JavaScript 的框架的渴望已经变得非常疯狂,不仅用于客户端 Web 应用程序开发(如 Ember,Knockout 和 React),还用于服务器端库(再次感谢 Node),用于创建原生移动应用程序的框架(如 Ionic,React Native 和 Native Script),以及用于开发桌面应用程序的框架(如 Meteor 和 Electron)。我在这段话中没有提到 Angular,因为我们已经知道 Angular 可以用于构建跨平台应用程序,涵盖了浏览器、桌面和原生移动应用程序的所有三个领域。
ECMAScript 2015
幸运事件系列中的第三个事件是 ECMAScript 2015 的发布。ECMAScript 是 JavaScript 标准的官方名称。尽管主要版本发布数量增加,但 JavaScript 语言多年来基本上没有发生变化。这是由于影响力玩家之间的分歧(最好不要提),导致语言的发展分裂和停滞不前。
总之,这是对 JavaScript 当前状态及其生态系统的一个快速概述。JavaScript 的生态系统非常庞大,需要写好几本书来覆盖它。例如,我们甚至没有提到可视化 JavaScript 库。JavaScript 有数百甚至数千个可用于项目的库,我们甚至无法开始覆盖。但是,JavaScript 生态系统中有一部分我们一定会涵盖:单元测试。您可能知道单元测试的重要性,并且可能已经使用诸如 JUnit、NUnit、RSpec 等框架为服务器端代码编写了单元测试,具体取决于您使用的编程语言。但是,客户端上的单元测试同样重要,大多数开发人员并没有进行单元测试,即使他们可能已经为服务器端编写了单元测试脚本。在第十三章中,单元测试,您将学习如何为客户端编写单元测试,特别是如何编写它们来测试您的 Angular 应用程序。我们将一起介绍的两个框架是 Jasmine(一种流行的单元测试框架)和 Karma(一个具有用于测试框架的插件的测试运行器,如 Jasmine)。
有了本章的技术前言,让我们戴上潜水装备,潜入 TypeScript 的海洋吧!
TypeScript 速成课程
TypeScript 对开发人员比 JavaScript 具有许多优势,包括以下内容:
-
纯粹的面向对象
-
可选的静态类型
-
类型推断
-
访问 ECMAScript 功能
-
转译
-
IntelliSense 的强大工具支持
-
您可以构建 Angular 应用程序!
转译与编译
开发人员通常可以在编程的上下文中定义编译是什么。定义可能是这样的:编译是将源代码通过另一个称为编译器的程序转换为机器可读代码的过程。这个结果代码通常被称为汇编代码,它是一组本机于机器 CPU 的机器指令,代码是要在其上运行的机器。
另一方面,转译是将用一种语言编写的源代码转换为另一种(或目标)语言的等效代码的过程。虽然这个定义对于讨论来说已经足够好了,但为了完全准确,我们还必须注意到源语言和目标语言实际上可能是同一语言的不同版本(或发布)。对于我们的转译需求,我们将使用 TypeScript 的转译器 tsc,它与 TypeScript 捆绑在一起。我们关心转译的原因是因为我们将在构建 Angular 应用时使用 TypeScript 编写代码。然而,Web 浏览器只有 JavaScript 引擎/解释器,因此我们需要一种将其转译为 JavaScript 的方法。
让
let和const关键字是在 ES6 中引入的。为了讨论它们是什么以及它们如何工作,让我们回顾一下var关键字的作用。在 ES6 之前,初始化变量的方式是使用var关键字。关于var需要记住的两件事是:
-
当您使用
var在函数体外定义变量时,它将成为全局作用域。这意味着 JavaScript 文件中的所有其他函数都可以访问它。虽然这有时可能很方便,但也可能很危险,因为值可能会被意图之外的函数无意中更改。当多个函数引用相同的变量名时,这种情况是可能的。 -
当您使用
var在函数内部定义变量时,它将成为局部作用域。与全局作用域变量相反,局部作用域变量只能在创建它们的函数内部访问。这是真实的,无论块作用域如何,因为使用var关键字声明的 JavaScript 变量作用域限定在最近的父函数内。
当然,您仍然可以使用var来声明和定义变量,因为该关键字尚未被弃用。只是现在您对初始化代码的行为有了更明确的控制,并且使用let和const后代码的可读性得到了改善,因为在查看 JavaScript 代码时意图是清晰的。
let关键字创建了块作用域的局部变量,并且其名称来源于其他具有类似构造的语言,比如 Lisp、Clojure、Scala 和 F#。在这些语言中,使用let声明的变量可以被赋值,但不能被改变。然而,在 ES6 中,使用let赋值的变量可以被改变;即使如此,无论它们是否被改变,该变量都是一个本地块作用域变量。
如果你觉得这有点令人困惑,你并不孤单。要牢固理解变量作用域的微妙之处并不是仅仅通过阅读就能学会的事情。编程就像学数学(或者学大多数事情一样):你做得越多,就会变得越好。话虽如此,将所有这些都归纳到你的脑海中的一种方法是看待var和let之间的这个主要区别:由于在一个函数内可以有多个块,甚至是嵌套块(或子块),使用let关键字定义的变量只能在其定义的块内访问,以及从该块的嵌套块中访问。相比之下,使用var定义的变量的作用域是整个封闭函数。记住这个主要区别,你就掌握了。
让我们看一些代码来理解let关键字的影响,然后我们可以继续讨论const关键字:
let x = 5;
if (x === 5) {
let x = 10;
console.log(x); // this line will output 10 to your console
// note, x was allowed to be changed
}
console.log(x);
// this line will output 5 to your console
// because the change to x was made within a block
常量
const关键字创建一个常量。你会很高兴知道,因为你已经经历了理解let关键字的痛苦,理解const关键字的作用将会非常简单。准备好了吗?在这里……const和let在它们的作用域工作方式上是相同的。let和const之间唯一的区别是你不能重新声明一个常量,它的值也不能被改变。就是这样。让我们继续讨论一些数据类型。
数据类型
每种编程语言都有数据类型。它们只在可用类型的数量以及类型变量可以保存的值(以及数字类型的值范围)方面有所不同。我不会在本书中探讨强类型与静态类型与弱类型语言之间的哲学辩论(通常称为静态与动态类型)—但由于本章专门讨论 JavaScript 和 TypeScript,我需要简要谈一下它们的类型。
JavaScript 是一种弱类型语言——也就是说它是一种动态语言,而不是静态语言。这意味着 JavaScript 中的变量不绑定到任何特定类型,而是它们的值与类型相关联。变量可以被分配和重新分配给所有可用类型的值。虽然方便,但由于没有编译器检查值是否符合类型引用,因此很难找到错误。这是因为当你使用var、let或const声明变量时,你没有指定关联的类型。
相比之下,TypeScript 是可选的静态类型。这里的关键词是可选。TypeScript 是一种静态类型语言,但它不强制你明确注释你的变量所需的类型。这是因为 TypeScript 具有所谓的类型推断,也就是说 TypeScript 运行时将在运行时推断变量的数据类型。这是 TypeScript 的默认行为。现在,这就是可选部分的地方……如果你想严格地给变量加上类型,从而将数据类型绑定到变量而不是与变量的值相关,你必须在变量声明中明确添加类型注释。
这是代码中的情况:
var num: number = 12345; // this is a statically typed variable
var num = 12345; // this is a dynamically typed variable
前两行都是有效的 TypeScript 语法,但它们之间有所不同:
-
第一行是静态类型变量,使用
num关键字进行注释,由 TypeScript 转译器检查,任何问题都将由它报告 -
第二行,变量声明以 JavaScript 的方式进行(也就是说,没有静态类型的注释),不受检查,任何问题只能在运行时发现。
ES6 有七种数据类型,其中六种被称为原始数据类型,一种被称为引用数据类型(只是称为Object)。JavaScript 标准库中还有几种内置数据类型,但由于这不是 JavaScript 的全面覆盖,我们只会在这里涵盖其中一些:你可能会在你的 Angular 开发中使用的那些。
以下是提供的原始数据类型列表:
-
空
-
未定义
-
布尔值
-
数字
-
字符串
-
符号
以下是提供的内置数据类型:
-
日期
-
数组
-
地图
-
集合
对象
仅具有原始数据类型和内置复杂数据类型并不足以编写表达性软件,试图模拟现实世界或虚构世界(在游戏中)。解决方案是拥有具有创建自定义对象构造的编程语言。幸运的是,JavaScript,因此 TypeScript,是一种允许创建自定义对象的编程语言。JavaScript 中的对象是一组映射键和值的集合,其中键可以是字符串或符号。这类似于许多其他编程语言的情况,例如 Python 的字典和 Ruby 的哈希。不要仅仅为了技术而变得太技术化(这是我讨厌的事情,也可能是你讨厌的事情),但 JavaScript 不是经典面向对象的语言。相反,JavaScript 使用原型继承来创建其他对象,而不是从类定义创建对象的实例。换句话说,JavaScript 没有类的概念。JavaScript 有原型。JavaScript 中的对象直接从其他对象继承。实际上,当您在 JavaScript 中使用大括号创建一个空对象时,这实际上是使用内置对象的create方法的语法糖。在 JavaScript 中有几种可用的方法可以创建一个空对象。我们不会在这里涵盖它们所有,但我们将涵盖两种多年来在 JavaScript 中可用的方法,并在 ES6 中提供给我们的方法:
-
使用
Object构造函数:var myObject = new Object(); -
使用大括号语法:
var myObject = {}; -
使用 ES6 类语法(我们将在接下来的类部分介绍语法)
前两种方法创建一个空对象。如果您想在 JavaScript 中轻松创建一个空对象,第二种方法显然是最简单的。然而,第三种方法,ES6 类语法,是我们将在本书中使用的方法。
JSON
JSON 是 JavaScript 对象表示法的缩写,不是一种数据类型,而是结构化数据。JSON 被用作轻量级数据交换格式,并被许多编程语言使用。我们不仅将在稍后更详细地介绍这一点,而且我们将广泛使用这种格式在我们的 Angular 应用程序和我们为其构建的后端 Web 服务之间传递数据。就像编程语言有数据类型一样,数据交换格式通常也有。以下是 JSON 允许表示的数据类型:
-
字符串
-
数字
-
对象
-
数组
-
布尔
-
空
您可能已经注意到 JavaScript 和 JSON 数据类型之间有很大的重叠。这并非偶然,因为 JSON 是 JavaScript 对象表示法,因此是模仿 JavaScript 的数据类型而建模的。以下是一个包含三个人的姓名和年龄的 JSON 数据的示例(每个都是 JavaScript 对象):
{
“people”: [
{“name”:”Peter”, “age”:40},
{“name”:”Paul”, “age”:42},
{“name”:”Mary”, “age”:38}
]
}
在前面的 JSON 示例中,我有people作为键,其值是一个包含三个people对象的数组。并没有硬性规定说你必须给嵌套结构命名,但这样做会更易读。在这个简单的例子中,你可以省略键而不会丢失数据,就像下一个 JSON 示例所示的那样:
[
{“name”:”Peter”, “age”:40},
{“name”:”Paul”, “age”:42},
{“name”:”Mary”, “age”:38}
]
然而,在第一个例子中,我们有people键,不仅更容易阅读,而且在代码中更容易处理。当我们在第十二章中为我们的应用编写 RESTful web 服务 API 时,我们将采取第一种方法,为我们的数据集提供键。
关于数据交换格式,这里有一个有趣的注释。虽然有一些可供选择的格式,比如 XML 和 SOAP(简单对象访问协议),但在开发 Web 服务时,JSON 是最受欢迎的格式,它受到 JavaScript 的启发。
如果没有 JavaScript,我们会在哪里呢?我不敢想象。
JavaScript 运行环境
本章的其余部分有许多代码片段,所以如果您想在阅读本章的过程中尝试材料,最好启动 JavaScript 运行环境。除非您使用 JavaScript IDE,比如 JetBrains 的 WebStorm,否则您有几种选择可以用来测试 JavaScript 代码。以下是其中的三种选择:
-
你可以使用在线的 JavaScript 控制台,比如
es6console.com/。 -
你可以在终端中使用 Node(附录 A 向你展示了如何安装 Node)。
-
你可以在浏览器的开发者工具中使用控制台。例如,我主要使用 Chrome 进行开发,而 Google 有出色的开发者工具。
任何这些选择都可以很好地工作。我更喜欢在 Node 终端中快速地做一些小事情,这也是我用来测试我为本章编写的代码的工具。
数组
数组是对象集合的一部分,被称为索引集合。如果你写过一定量的 JavaScript,你一定使用过数组。数组对象可以保存任何有效的 JavaScript 数据类型,并且可以通过调用它们的内置方法(如push和splice)来增长和缩小。你可以使用indexOf方法来搜索数组中值的存在,使用length属性来获取数组的长度,等等。创建一个空数组的 JavaScript 语法如下:
var myDreamCars = [];
然后你可以使用数组的内置push方法来向数组中添加一个项,就像这样:
myDreamCars.push("Porsche");
或者,你可以一次性地创建数组,就像这样:
var myDreamCars = ["Porsche", "Mercedes", "Ferrari", "Lamborghini"];
indexOf方法非常方便,我们肯定会在后面用到它。在继续讲解TypedArrays之前,让我们快速介绍一下这个方法。当你需要找到数组中特定项的位置,或者判断它是否存在于数组中时,你可以使用indexOf方法。假设我们想要查找 Mercedes 在数组中的位置。我们可以这样搜索:
var indexOfMercedes = myDreamCars.indexOf("Mercedes");
给定我们的myDreamCars数组,indexOf函数会返回 1。这是因为 JavaScript 中的数组从 0 开始索引,而 Mercedes 在我们的数组中是第二个位置。如果我们要查找的东西不在数组中会怎样呢?让我们看看当我们查找 Corvette 时会发生什么:
var indexOfMercedes = myDreamCars.indexOf("Corvette");
当执行上述行时,indexOf函数会返回-1,这表示我们搜索的项没有找到。
TypedArray
TypedArray在 ES6 中使用,尽管它具有与普通 JavaScript 数组对象相同的一些方法,但它与您可能期望的有很大不同。事实上,TypedArray根本不是数组。如果您尝试将TypedArray传递给Array.isArray(),您会发现返回的值是false。那么它们是什么呢?TypedArray为我们提供了对底层二进制数据缓冲区的视图,并使我们能够访问和操作数据。我们不会在本书中涵盖TypedArray,因为我们不会使用它们,它是一种高级数据类型和机制,但我提到它的原因是让您知道它的存在。在我们继续之前,让我至少解释一下它的创建动机和您可能希望考虑可能使用它的用例。TypedArray随着 ES6 的出现而出现,因为 Web 应用程序变得越来越先进,客户端机器现在有如此多的可用性能,编写一个处理和操作音频和视频的客户端应用程序是一个好主意。为了做到这一点,您需要一种机制来使您的 JavaScript 代码能够读取和写入这些原始二进制流的数据。
您可能希望构建的两个立即可以使用TypedArray的示例是:
-
视频编辑(您希望删除不需要的镜头段)
-
对音频进行采样(更改声音字节的频率,也许创建原始样本的 11 个版本以创建一个音阶,以便能够从原始单个样本中演奏旋律)
再次,这是 JavaScript 发展的一个例子。
地图
地图是一种数据结构,它在 ES6 中引入了 JavaScript。地图用于将值映射到值。此外,它们允许使用任意值作为键,这意味着您可以使用整数作为键,或字符串,甚至对象;但是,不允许使用符号作为键。还有一些方便的方法可以在地图上执行操作,并且您还可以对地图进行迭代。让我们来看一些创建地图的代码,并探索一些常见的内置函数。首先让我们创建我们的地图。我们将创建一个用于将学习曲线映射到编程语言的地图,使用new关键字:
var mapLangCurve = new Map();
现在让我们使用地图的设置功能向其添加一些条目:
mapLangCurve.set('Python', 'low');
mapLangCurve.set('Java', 'medium');
mapLangCurve.set('C++', 'high');
虽然你可以一次在Map中添加键值对,就像我们刚刚做的那样,但set方法是可链接的,所以我们可以使用这种语法来完成完全相同的事情,这样可以节省一些输入:
var mapLangCurve = new Map().
set('Python', 'low').
set('Java', 'medium').
set('C++', 'high');
或者,作为声明和初始化我们的语言学习曲线Map的第三种方法,我们可以将一个包含两个元素数组的数组传递给 Map 的构造函数。假设我们的两个元素数组设置如下:
var arrLangCurve = [['Python', 'low'],
['Java', 'medium'],
['C++', 'high']];
然后我们可以像这样将其传递给构造函数:
var mapLangCurve = new Map(arrLangCurve);
这三种创建Map的方法都会产生完全相同的结果。好了,让我们继续快速看一下可以在Map上执行的一些常见操作。我们可以使用size属性获取 Map 的大小:
var langCurveSize = mapLangCurve.size;
我们可以使用get函数检索键的值:
var javaLearningCurve = mapLangCurve.get('Java');
我们可以使用has函数检查Map中键的存在:
var blnCurveExists = mapLangCurve.has('C++');
我们可以使用delete函数删除一个键及其值:
mapLangCurve.delete('Python');
我们可以使用clear函数一次性清除一个集合中的所有项。
如果你在 JavaScript 环境中跟着做,现在不要尝试这样做,因为我们需要一些数据来迭代:
mapLangCurve.clear();
在 JavaScript 中,你可以很容易地使用for构造迭代Map,但我们需要知道我们想要迭代什么。我们想要获取我们地图的键吗?还是它的值?或者我们想要获取两者。这是如何迭代我们地图的键:
for (let key of mapLangCurve.keys()) {
console.log(key);
}
这是如何迭代我们地图的值:
for (let value of mapLangCurve.values()) {
console.log(value);
}
这是如何迭代我们地图的键和值:
for (let item of mapLangCurve.entries()) {
console.log(item[0], item[1]);
}
大多数情况下,你可能希望访问你的地图的键和值,所以你应该使用地图的entries函数。
在本章的稍后部分,我们将看一下 ES6 给我们的一个构造,叫做解构,它使我们能够直接访问键和值。
WeakMap
WeakMap是一个有趣的生物,它不是从Map继承而来,尽管它们都是键值对的集合,并且共享一些相同的功能。
Map和WeakMap之间最重要的区别是可以用作它们的键的数据类型。通过Map,我们已经看到可以使用各种数据类型作为其键,包括对象。但是,WeakMap只能将对象作为其键。这是有意设计的,并且使WeakMap特别有用,如果只关心在键尚未被垃圾回收时访问键的值。让这一点沉淀片刻。我知道这听起来像一个奇怪的用例,但是如果考虑到WeakMap可以帮助减轻 JavaScript 程序中的内存泄漏,可能足够考虑如何在代码中使用WeakMap。
数据结构名称中的Weak部分来自于WeakMap弱引用其键对象。这意味着它们有可能被垃圾回收。这一事实导致了我们的下一个观点。由于我们的键可能在任何时候被垃圾回收而无需我们的参与,因此将它们列举出来是没有意义的,因此它们不是,这意味着我们无法遍历集合。
如果需要遍历集合中键或值的列表,则应使用Map。相反,如果不需要遍历Map,只打算将其用作查找表,则可以考虑使用WeakMap。
我们将在下一节学习Set。
Set
Set是一个唯一值的集合,并且可以按照其元素添加到其中的顺序进行迭代。Set可以包含同类数据,但是每个数据(即元素)都需要是唯一的。如果尝试将现有元素添加到集合中,对集合不会产生影响。
集合具有许多与映射相同的功能。让我们创建一个Set对象,并快速浏览一些常用的函数。
创建一个集合,我们使用new关键字调用它的构造函数:
var setCelestialObjects = new Set();
让我们向我们的Set添加一些元素(即天体):
setCelestialObjects.add('Earth');
setCelestialObjects.add('Moon');
setCelestialObjects.add('Solar System');setCelestialObjects.add('Milky Way');
setCelestialObjects.add('Andromeda');
setCelestialObjects.add(['Aries', 'Cassiopeia', 'Orion']);
setCelestialObjects.add(7);
好吧,数字7并不完全是天体,但我想向您展示您可以将不同类型的元素添加到同一个Set中。与我们的星座数组一样:我们可以向我们的Set添加数组和任何类型的对象。
我们可以使用size属性获取我们的Set的大小:
var sizeCelestialObjects = setCelestialObjects.size;
现在不要这样做,但是您可以使用其clear函数清除Set:
setCelestialObjects.clear();
我们可以通过将其值传递到集合的delete函数中来从我们的Set中删除一个元素:
setCelestialObjects.delete('Andromeda');
您可以使用for循环来迭代Set,就像我们用于Map一样:
for (let element of setCelestialObjects) {
console.log(element);
}
如果您想对Set中的每个元素执行操作,可以使用 set 的forEach函数,该函数以回调作为其参数。例如,如果您的Set中有一组整数,并且想要对它们进行平方运算,可以这样实现:
var setIntegers = new Set();
setIntegers.add(1);
setIntegers.add(7);
setIntegers.add(11);
setIntegers.forEach(function(value) {
console.log(Math.pow(value, 2));
});
上述代码不会更改我们Set中的元素,而是将平方值打印到控制台。我们无法轻松地就地更改我们的元素,但是我们可以创建一个新的Set并将我们的平方元素存储在其中,就像这样:
var setSquaredIntegers = new Set();
setIntegers.forEach(function(value) {
setSquaredIntegers.add(Math.pow(value, 2));
});
我们可以使用has函数来检查我们的Set中是否存在元素:
var blnElementExists = setCelestialObjects.has('Moon');
如果您还记得我们讨论过的Map,Map对象有以下三个函数:keys,values 和 entries。Set也有这三个函数,但它们的返回值是非常不同的。当您在Set上调用这些内置函数时,将会得到一个SetIterator对象。
在本书中,我们不会使用SetIterator,但是就像我给您介绍TypedArray的用例一样,我想给您介绍SetIterator的用例。Map对象和Set对象是不同的数据结构,您可以以不同的方式迭代这些结构。如果使用迭代器,可以构建一个可以以相同方式迭代这两种类型数据结构的函数。换句话说,您可以将对象传递到迭代集合的函数中,而不必担心它们的类型。
WeakSet
WeakSet是一组弱引用对象,每个对象必须是唯一的;不允许添加重复的对象。回想一下我们在WeakMap上的讨论,由于它们的键只能是对象,因此它们的键可能会在我们脚下被垃圾回收。因此,就像WeakMap一样,对于迭代,WeakSet也是如此:我们无法对集合进行迭代。
WeakSet有非常少量的内置函数,即 add,delete 和 has。WeakSet还有一个length属性,类似于数组,而不是Map的size属性。
让我们快速看一下创建WeakSet对象的语法,以及它的属性和函数。
我们可以使用其构造函数创建一个空的WeakSet对象:
var myWeakSet = new WeakSet();
让我们创建三个空对象添加到我们的WeakSet中,然后使用WeakSet对象的add函数将它们添加到其中,然后使用其length属性获取它包含的对象数量:
var objA = {};
var objB = {};
var objC = {};
myWeakSet.add(objA);
myWeakSet.add(objB);
myWeakSet.add(objC);
var lengthMyWeakSet = myWeakSet.length; // lengthMyWeakSet will be set to 3
您可能会问,等一下。您说对象必须都是唯一的,任何重复的对象都不会被插入到 WeakSet对象中。*所有对象都是空的;它们不是相同的吗?*是的,当尝试插入操作时,重复的对象将被拒绝。然而,虽然我们的三个对象都具有相同的值(即它们都是空的),但它们实际上是三个独立且不同的对象。
在 JavaScript 中,与大多数其他面向对象的语言一样,决定对象是否与另一个对象相同的是对象引用(即底层内存地址),而不是其内容。
以下是您可以比较两个引用同一对象的对象变量的方法:
var blnSameObject = Object.is(objA, objB);
objA和objB各自引用空对象,但这是两个不同的对象;因此,blnSameObject将被设置为false。
如果我们做了以下操作,由于objB和objC变量指向内存中的同一个对象,试图将objC添加到myWeakSet的行将不会对myWeakSet产生影响,因为底层对象已经包含在WeakSet对象中:
var objA = {};
var objB = {};
var objC = objB; // objB and objC now both point to the same object in memory
myWeakSet.add(objA);
myWeakSet.add(objB);
myWeakSet.add(objC);
var lengthMyWeakSet = myWeakSet.length; // lengthMyWeakSet will be set to 2
类
几页前,我们介绍了在 JavaScript 中创建对象的三种不同方法。我还提到我们将在后面介绍如何使用 ES6 类语法创建对象。此外,我还提到 JavaScript 没有类的概念,但我们在本节中涵盖了类。让我们澄清这一切,看看如何在 JavaScript 中创建类,以及如何从这些类创建对象。
在 ES6 之前的 JavaScript 版本中,不存在类的概念。相反,每当创建对象时,JavaScript 运行时会直接从其他对象继承,而不是从类继承(请记住,JavaScript 不是经典的面向对象语言;它使用原型继承)。这并不是说 JavaScript不好,但它确实与众不同。为了将经典面向对象的风格和语义带入,ES6 给我们带来了类的概念。类是对象的蓝图,当我们从这个蓝图或模板创建一个对象时,它被称为实例化。我们使用类来实例化(使之存在)一个或多个对象。让我们来看一下语法。
让我们创建一个Car类,并为其提供一个构造函数、三个属性和三个方法:
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
this.speed = 0;
}
get speed() {
return this._speed;
}
set speed(value) {
this._speed = value;
}
speedUp() {
this.speed += 10;
}
slowDown() {
this.speed -= 10;
}
}
我故意在这里使用了术语方法,而以前我总是把它们称为函数。因为我们现在讨论的是类和对象,在经典的面向对象术语中,方法比函数更好。
你需要记住任何面向对象语言的两种关系,如下:
-
对象是它们类的实例
-
对象封装了操作数据的数据和方法
数据代表对象在任何时刻的状态,方法代表对象具有的行为。就是这样。
好了,回到我们的类示例。我们的Car类有一个构造函数,它接受两个参数:汽车的制造商和型号。它还有三个实例变量:make,model和speed。此外,它有两个方法,speedUp和slowDown。最后,speed实例变量实际上是一个属性;这是因为它有一个关联的 getter 和 setter。
需要注意的是,我们类中的 setter 和 getter 在属性名称前面有一个下划线,而关联的实例变量没有。这很重要,因为如果没有下划线,JavaScript 运行时在实例化您的Car对象时会抛出异常(即RangeError: Maximum call stack size exceeded)。
太好了!那么,我们如何创建它的实例(即Car对象),以及如何调用它的方法和读取它的属性?以下是代码来帮助回答这些问题。
我们创建我们的Car对象就像创建任何其他对象一样,通过调用它的构造函数:
var myG6 = new Car('Pontiac', 'G6');
让我们读一下我们车的当前速度:
myG6.speed; // this returns 0, which is the value it was initialized to
哦,天啊!零英里每小时?这是不可接受的。让我们踩油门!看这个:
myG6.speedUp(); // this increases our speed by 10 mph
myG6.speedUp(); // this increases our speed by another 10 mph
myG6.speedUp(); // this increases our speed yet again, by 10 mph
myG6.speedUp(); // this increases our speed, one last time, by ... you guessed it, 10 mph
我们的速度有多快?我们最好检查一下:
myG6.speed; // this now returns 40
该死!我们刚刚进入了一个学校区,必须将最高速度降到 20 英里/小时:
myG6.slowDown(); // this decreases our speed by 10 mph
myG6.slowDown(); // this decreases our speed by another 10 mph
让我们再次检查我们的速度:
myG6.speed; // this now returns 20
呼!好了,目前我们的速度还可以。
总结一下这一部分,在 JavaScript 中有几件事情要记住:
-
与 Java 或 Python 不同,JavaScript 中的类只能有一个构造函数。不支持构造函数重载。
-
你可以在你的类中使用 super 调用(用于调用层次结构中更高级别的类的构造函数),但必须在使用
this引用之前调用它,就像我们将make和model参数分配给它们各自的实例变量时一样。
接口
到目前为止,我们一直在看一些 JavaScript 的新添加,这些对我们来说是可用的。对于我们来说,接口部分是 TypeScript 特有的东西,因为 JavaScript 没有接口的概念。
接口就像是类的合同,并提供了类必须遵循的一组规则。让我们从构建Car类转换到构建Animal类,并且在此过程中,让我们的类实现一个我们称之为Species的接口:
class Animal implements Species {
}
interface Species {
name: string;
isExtinct: boolean;
}
我们的Animal类是空的。它甚至没有构造函数或任何实例变量,但对我们来说这并不是问题,因为它仍然可以满足我们演示如何使用接口的目的。
稍微看一下Species接口。你会注意到一些事情:
-
它有两个公共属性。TypeScript 有访问修饰符,就像 Java 和 C#一样,我们将在后面的章节中使用它们时介绍。现在,你只需要知道属性上缺少访问修饰符意味着属性是公共的。这很重要,因为由于接口描述了实现它的类的公共接口,其属性必须是公共的。
-
第二件你会注意到的事情是我们在输入属性。我们声明
name属性的类型为字符串,isExtinct属性的类型为布尔值。这是 TypeScript 的一个主要优势,正如我们之前学到的那样,也是 TypeScript 得名的原因(即,一个有类型的 JavaScript)。
我们将在本书的后面看到访问修饰符的作用。有三种:
-
公共的: 这是默认的修饰符,意味着属性或函数对所有其他代码可见
-
私有: 类的属性和函数标记为私有的可供其声明的类的成员函数使用
-
受保护的: 这与私有相同,但类成员也对从其声明的类继承的任何类可见
我们将类与接口结合的方式是在类定义中使用implements关键字,就像我们在这个例子中所做的那样。一旦我们这样做,类必须遵守接口的合同。
那么现在呢?如果Animal类没有实现Species接口规定的两个属性,那么 TypeScript 在转译过程中会抛出错误。
我们还可以通过在属性或函数的末尾添加问号来描述一个可选的接口。我们的接口中没有列出函数,但我们也绝对可以有函数。
如果我们的接口是一个可选的合同,它会是这样的:
interface Species {
name?: string;
isExtinct?: boolean;
}
继承
我们提到,标有受保护访问修饰符的类成员也对任何从它们声明的类继承的类可见,因此我们最好快速讨论一下继承。
继承并不意味着我们创建的类会变得独立富裕;那是一种完全不同的继承。我们谈论的继承类型可能不那么令人兴奋,但对于我们的 JavaScript 代码来说,它更有用。
一个类可以从另一个类继承。为了做到这一点,我们在类定义中使用extends关键字。让我们再次转换一下,这次是从Animal到Employee(尽管我在一些客户地点的一些浴室和厨房看到过一些动物,我可以告诉你一些员工也可以是动物)。让我们来看代码:
class Employee {
constructor(name) {
this.name = name;
this.title = "an employee";
}
annouceThyself() {
return `Hi. My name is ${this.name} and I am ${this.title}.`;
}
}
class Manager extends Employee {
constructor(name) {
super(name);
this.title = "a manager";
}
}
让我们创建一个Employee对象,并让员工宣布自己:
var emp = new Employee("Goofy");
console.log(emp.annouceThyself());
// Hi. My name is Goofy and I am an employee.
让我们创建一个Manager对象,并让经理宣布自己:
var mgr = new Manager("Mickey");
console.log(mgr.annouceThyself());
// Hi. My name is Mickey and I am a manager.
这里发生了什么:
-
我们创建了一个
Employee类。 -
我们创建了一个从
Employee继承的Manager类。 -
Manager类除了构造函数之外,没有任何属性或函数。但是,它从Employee类继承了属性(name和title)和方法(annouceThyself)。 -
Manager类中的构造函数调用Employee类中的构造函数,传递经理的名字。 -
经理的构造函数重新分配了
title属性的值。
这是相当简单的,但这里有两个要点要记住:
-
继承类从它继承的类中获取所有类成员。
-
一个构造函数可以调用它的父类的构造函数,并且如果父类有父类,等等,这种情况可以一直延续下去。
解构
解构是一个非常酷和非常有用的构造,我们将在本书中多次使用它,而且在你完成本书后,你在你的 Angular 项目中也无法离开它。简而言之,解构是一个 JavaScript 表达式,它使我们能够轻松地从对象和集合中提取数据。
当我们看Map对象时,我提到我们会看一个解构的例子。在这里。
假设我们有以下对象:
const author = {
firstName: 'Aki',
lastName: 'Iskandar',
topics: ['Angular', 'JavaScript', 'TypeScript', 'Bootstrap', 'Node'],
cities: ['Calgary', 'Cleveland'],
publisher: 'Packt'
}
如果我们想提取firstName,lastName和publisher,我们知道如何以传统的方式做到这一点(即在 ES6 之前):
const firstName = author.firstName;
const lastName = author.lastName;
const publisher = author.publisher;
好吧,解构(尽管它的语法看起来有点奇怪)通过以下语法节省了我们大量的按键次数,给我们提供了相同的结果(提取数据的新变量):
const {firstName, lastName, publisher} = author;
我们可以很容易地看到,它通过将一个变量写入控制台来完成了它的工作:
console.log(publisher); // Packt
这非常方便,当我们一起编写应用程序时,我们将充分利用它。
模板字符串
模板字符串是用反引号括起来的字符串(即`)。
注意:反引号字符通常与波浪号(即~)在键盘上的同一个键上,并且紧挨着数字 1 键的左边。
JavaScript 总是允许我们使用双引号和单引号来创建字符串,那么为什么要使用第三种类型的字符串创建字符呢?嗯,事实证明,鉴于前端框架的大量使用,有一个共同的需求需要做三件事:
-
字符串插值
-
多行字符串
-
标记模板
for-of 循环
JavaScript 为我们带来了forEach构造,用于循环遍历集合。这是一个很好的内置方法,但你无法跳出这个循环。我们还有for-in循环,对于具有字符串键的对象来说非常好,但在迭代数组时有一些缺点。
进入新的for-of循环。它适用于对象、数组和映射,并且你可以跳出它。以下是语法,与for-in循环的语法相同,只是将in更改为of:
let myArray = 5, 10, 15, 20, 25;
for (var item of myArray) {
console.log(item);
}
装饰器
装饰器也是 TypeScript 的一部分。TypeScript 中的装饰器装饰函数和类,就像它们在其他一些语言中一样,比如 Python 和 Java。
我们不会花太多时间在这里,因为我们不会为我们要一起构建的应用程序编写自己的装饰器,但由于 Angular 大量使用装饰器,我想至少让你了解它们的用途。我们还将看一个快速的例子,如何创建一个装饰器以及如何使用它,但我们会快速地飞过它。
装饰器是一种通过用装饰器注释类来为函数或类(通常是类)添加功能的方法。装饰器只是一个函数,尽管乍一看它的语法看起来有些奇怪。让我们看一些代码:
function iq(target) {
Object.defineProperty(target.prototype, 'iq', {value: () => "Genius"})
}
@iq
class Person {
}
let readerOfMyBook = new Person();
console.log(readerOfMyBook.iq()); // prints out "Genius" to the console
这是中级到高级水平的 TypeScript,整个章节都可以写关于装饰器的内容。我们没有时间在这里详细介绍它们,但要记住的是,它们只是简单地为函数或类添加功能的函数,你只需要用装饰器的名称来注释函数或类(即@NameOfDecorator)。
Promises
当我们在[第十二章 集成后端数据服务中使用它们时,我们将更详细地介绍 promises,所以我会推迟代码。原因是展示一个真正好的实际例子需要相当多的代码,因为需要调用异步代码。所以,我承诺在书中稍后会有真实世界的 promises。但是,我们至少可以看一下定义,这样你就知道它们是什么。
当你调用一个可能需要很长时间才能返回结果或完成任务的函数时,而且你不想延迟程序的执行,你可以异步调用该函数。这意味着你的代码在异步调用前一行后会继续执行到下一行。如果你不异步调用它,你的程序的执行将停止并等待你最后调用的函数从它正在做的事情中返回,比如从数据库中读取一堆记录。
有几种不同的方法可以异步调用函数。异步调用函数最常见的方式是使用回调函数。回调函数是你传递给异步调用的函数的函数,这样它就可以在完成工作后调用该函数。这就是回调函数得名的原因;你调用的函数在完成时会回调你。
Promises 是我们可以用来异步编程的另一种机制。虽然 Promises 使事情变得更加可控,但在 JavaScript 中编写良好的异步代码通常仍然非常困难。因为这个事实,人们开始编写 JavaScript 库来尝试使异步代码更容易编写。有几种库可供选择。一个拯救了我的理智的库叫做 Async。
话虽如此,我仍然没有给你一个 Promise 的定义,所以在这里:Promise 是一个代理尚未知的值;它就像一个值的占位符,最终将从异步调用的函数中返回。这种构造允许异步函数立即返回一个值,就像它是一个同步方法一样。返回的初始值是一个 Promise,一旦调用的函数完成了它的工作,Promise 最终将被返回的值替换。
我知道这可能需要花费一些时间来理解,但当我们在第十二章中编写我们的代码,集成后端数据服务,你将理解 Promise。这是一个承诺,双关语。
模块
在 ES6 之前,JavaScript 没有模块的概念。模块是简单的代码文件,可以加载到其他代码中,以便加载的模块中的函数对导入模块的代码可用。模块可以加载模块。模块导致模块化的代码,这是一件好事。与其在一个文件中编写庞大的代码块,不如将其分割成逻辑单元,并使该代码存在于多个文件中。这导致了代码重用,命名空间和可维护性。
虽然 JavaScript 没有模块,但我们仍然能够在一定程度上实现相同的功能。我们可以在网页中调用函数之前使用脚本标签加载脚本文件。但是,对于在服务器端或网页之外的其他环境中运行的 JavaScript,没有模块,编写非单片应用程序变得困难。
让我们继续进行代码。
假设我们有一个名为alphafunctions.js的文件,其中包含以下代码:
function alpha1() {
console.log("Alpha 1 was called");
}
function alpha2() {
console.log("Alpha 2 was called");
}
export {alpha1, alpha2};
export关键字用于标记哪些函数可以被导出,因此可以被其他模块导入。
现在假设我们有这个文件main.js,其中包含以下代码:
import {alpha1, alpha2} from ./alphafunctions;
alpha1(); // "Alpha 1 was called" is written to the console
默认导出
假设我们总是希望将我们的alpha1函数导入到其他模块中,或者至少更频繁地这样做。我们可以在keyword函数之前添加关键字export default。因此,当我们导入它时,我们不再需要在函数名称周围使用大括号。让我们在代码中看到这一点。
查看alphafunctions.js:
export default function alpha1() {
console.log("Alpha 1 was called");
}
function alpha2() {
console.log("Alpha 2 was called");
}
export {alpha1, alpha2};
查看main.js:
import alpha1, {alpha2} from ./alphafunctions;
虽然这并不是一个惊人的差异,但“默认导出”这个术语在对话和博客文章的代码片段中经常出现,因此我想确保我们至少快速看一下,这样你就明白为什么有时会有大括号,而有时没有。当你使用 JavaScript 库时,你也会在文档和代码示例中看到这一点。所以,现在你知道了。
总结
在本章中,我们涵盖了 JavaScript 的一些历史,特别是围绕 JavaScript 生态系统的一系列幸运事件,这些事件巩固了该语言作为近代最重要的编程语言。现在我们不仅可以编写在浏览器中执行的客户端代码,还可以编写在服务器上运行的 JavaScript 代码。如果这还不足以成为使用更多 JavaScript 的有力理由,你还可以将 JavaScript 用于原生移动开发,以及创建桌面应用程序。这真是一个令人兴奋的故事!
然后,我们简要地查看了 ES6 发布时添加到 JavaScript 中的一些内容。这些新增内容相当重要,特别是因为 JavaScript 在十多年来基本保持不变,因此这些新增内容真正加强了语言。我们还列举了 TypeScript 带来的一些好处。请记住,你可以将 TypeScript 视为 JavaScript 的超集,并且可以将其定义为 ES6 加上可选类型。
微软将 TypeScript 贡献给 JavaScript 开发人员是该公司长期以来对开源世界做出的最重要的贡献之一。Angular 本身就是用 TypeScript 编写的,因为 TypeScript 相对于纯 JavaScript 具有优势,所以在构建 Angular 应用程序时,最好使用 TypeScript 进行编写。我们记得 JavaScript 是唯一可以在浏览器中执行的语言,但幸运的是,TypeScript 附带了一个转译器,可以将我们的 TypeScript 代码转换为纯 JavaScript 代码。
如路线图中所述,第三章《Bootstrap - 响应式网格布局和组件》,我们有一个类似的目标。在本章中,我们将对 SASS 进行快速介绍,这是我们将用来为我们的 Angular 组件设计样式的工具,而不是使用 CSS。我们还将涵盖足够的 Bootstrap 知识,让您能够舒适地使用这个古老的 CSS 框架来布局我们将一起构建的网络应用程序 ListingCarousel 的网页。您将获得足够的知识,立即将这些技能应用于您目前可能拥有或将来可能开始的几乎任何网络应用项目中。
第三章:Bootstrap - 网格布局和组件
嘿——您已经到达第三章,Bootstrap – 网格布局和组件。太棒了!接下来的议程是倒一杯您最喜欢的饮料,因为这是一个重要的章节,您会希望保持清醒。然而,这并不全是工作,因为这一章是我们一起开始构建示例应用程序的地方,我们应该能够在其中玩得开心。在这个过程中,我们还将涵盖各个领域的相当多的材料。以下是我们将要涵盖的内容列表:
-
我们将正式介绍我们的示例应用程序,名为 Listing Carousel,并提出一些建议,说明您可以如何将此应用程序转化为您可能更喜欢的其他事情——可以是在本书旁边(而不是构建 Listing Carousel),或者在完成本书后,如果您首先与我一起构建 Listing Carousel。
-
我们将介绍我们在整本书中如何逐步构建我们的应用程序的计划,并且您还将看到我们有一些备选技术可供选择,用于构建 Listing Carousel,或者您可能会在本书结束时受到启发,选择构建自己喜欢的应用程序。
-
我们还将研究 Sass,这是一种使编写项目中的 CSS 变得更容易、更有组织的技术。
-
我们肯定会研究 Bootstrap——它的两个主要部分:响应式网格和一些组件。
-
在第一章,快速入门中,我们偷偷看了一些将构成我们示例应用程序的线框图。好吧,这一章是我们将编写 HTML 代码,利用 Bootstrap,将线框图变为现实的地方。
-
作为额外材料,我们还将研究软件项目从构思到实现的过程,使用一个真实的案例研究,即 Listing Carousel。这包括项目阶段,如分析、需求收集、用例图、线框图和实施。
在本章结束时,我们的网页将是硬编码的,不会有任何 Angular 代码。随着我们在书中的进展,我们将逐渐将它们转变为一个完全成熟的 Angular 应用程序,通过添加路由、Angular 组件、模板、数据等。
关于本章不包括的内容
本章涵盖了很多内容,包括 Sass 和 Bootstrap 的响应式网格以及其中的一些组件。然而,本章并不全面涵盖您需要了解的这些内容。原因很简单——不仅有专门讲解 Bootstrap 的书籍,而且 Bootstrap 的官方网站是查找 Bootstrap 文档的理想地方。重复他们的文档不是这本书页面的好用处,也不是您的时间和辛苦赚来的钱的好用处。相反,更明智的做法是以实际的方式介绍 Bootstrap 的网格和组件——比如在本书中一起构建一个应用程序,并在需要时参考官方文档。
顺便说一句,同样的情况也适用于[第五章],Flex-Layout – Angular’s Responsive Layout Engine,[第八章],使用 NG Bootstrap,和[第九章],使用 Angular Material,因为每种技术都有它们自己的官方文档。
我的工作是做以下事情:
-
向您介绍这些技术(并指向它们的官方文档)
-
演示它们如何以实际、有趣和引人入胜的方式应用
-
鼓励您通读整本书,这样您就可以成为一个 Angular 网页开发大师
顺便说一句,Angular 当然也有自己的官方文档,但其中包含的内容太多,甚至开始都可能让人望而却步。根据我的经验,学习新技术的更有趣的方法是通过教程,而这本书正是这样一本全面的教程,通过构建一个应用程序来学习,每章节中都有额外的解释和一些额外的材料。如果我做得好的话,您应该能够使用 Angular 构建几乎任何您可能需要(或想要)构建的应用程序。这就是目标。
现在让我们来看看“列表轮播”,这是我们将一起构建的示例应用。
我们的示例应用
“列表轮播”是我们这本书的示例应用,它是一个真实的在线应用,为房地产经纪人(即专业的房地产销售人员)提供了一个机会,以引人入胜和信息丰富的方式与他们在社交媒体上的联系人分享他们的房源。我的一家公司拥有并运营它。
我选择这个应用的原因并不是让你偷我的代码,然后试图和我竞争(这样做完全不酷,也不推荐),而是因为通过一些调整,你可以把这个应用变成你自己的在线服务。例如,你可以很容易地把这个应用变成一个分类广告应用(比如 Craigslist 或 Kijiji),只需添加搜索功能,或者通过添加搜索功能,甚至可以变成一个约会/婚介网站,再加一点代码。或者,如果你喜欢美食,可以把它变成一个餐厅网站。让餐厅注册并在轮播中列出他们的菜单——每张幻灯片上放一道菜或开胃菜,然后餐厅老板可以与他们的社交媒体圈分享他们的菜单。或者,如果你喜欢有新的方式分享相册,你可以把这个应用变成类似的东西。我以前想过一个主意,就是创建一个人们可以展示他们的作品集的网站(比如艺术家、建筑师和摄影师)——随意去建立类似的东西并且发挥它。选择真的是无限的。重点是,我想为这本书想出一个有趣的应用——一个能给你一些动力来完成整本书的应用。为什么?很简单——因为我知道如果你只是读它,这本书对你来说就不会像它本来可以给你的那样有价值。所以,承诺和我一起深入代码并构建一些你会喜欢的东西。谁知道,也许你会想出一个有利可图的在线业务的好主意!我的目标是让你在阅读这本书的时间投资变得有意义,如果我成功了,你就可以给这本书一个五星好评(眨眼)。这听起来不错吗?
游戏计划
我们有一个逐步的游戏计划,以 Listing Carousel 作为我们一起讨论这本书材料的焦点。虽然这本书并没有明确地分成部分(也就是说,章节的分组),但我们现在可以通过将我们需要为构建应用程序做的工作分成三个主要阶段来松散地将它们分组。跟着我一起做,这一切都会有意义——给我们一个将材料(也就是书的章节)与我们将一起构建的应用程序相结合的方法,同时给我们一个我们的目标。
在开始驾驶之前知道你要去哪里,随时能够认清自己的位置是很好的。像这样拥有一份路线图/游戏计划会让整个过程更加愉快,从而最大程度地增加你会完整地阅读这本书的机会,而不仅仅是偶尔查找一些内容。这本书的设计并不像一本食谱书,而是旨在教你如何烹饪。你将通过实践学会烹饪(这是一种双关语),通过准备一道适当复杂的菜肴来要求一定水平的知识和技能来正确烹饪。这本书有四个主要的好处:
-
它为你提供了所有的食材,甚至是你准备这道菜所需的替代食材(也就是选择)。
-
它为你提供了厨师所需的知识、教授了烹饪餐点所需的过程和技能。
-
它以一种系统的方式做这些事情,让你尽可能有效和高效地学习。
-
餐点的选择是代表你可能需要烹饪的大多数菜肴复杂性的一道菜。换句话说,如果你学会了如何烹饪这道菜(也就是我们的示例应用程序),你应该有信心能够准备任何你被要求准备的菜肴。
撇开烹饪类比不谈,这本书的承诺是教会你如何通过一种方法论的过程来使用 Angular 构建一个实际的应用程序。毕竟,如果你想一想,这就是你买这本书的原因,不是吗?否则,你可以试图在这里那里搜索,希望最终能把一切拼凑在一起。那既不有趣,也不是学习 Angular 的聪明方式。当你想学习新东西,或者把基本技能提升到更高水平——无论是什么,不仅仅是 Angular——你都需要有一个明确的目标,并制定一个到达目标的路线图/计划。好吧,我们知道我们的目标,那就是构建列表轮播、学习 Angular,以及一大堆其他好东西。所以现在让我们来看看我们的计划。
在构建我们的应用程序的第一阶段,我们需要决定要构建什么,它将具有什么功能,以及它将是什么样子。一旦我们确定了所有范围和线框图,下一步就是为我们的应用程序建立骨架。到这个阶段结束时,我们的应用程序很可能只是硬编码的——只是一些 HTML 和 CSS。我们唯一的组件将是我们从 Bootstrap 组件库中选择使用的组件。你可以把这看作是我们的应用程序有了皮肤和骨骼,但还没有内在或跳动的心脏。
在构建我们的应用程序的第二阶段,嗯,你猜对了,我们将开始给我们的皮肤和骨骼应用程序添加一些内在!具体来说,它将是 Angular 的内在!请记住,在第一阶段,我们根本没有使用 Angular——一点也没有——这是有意的。虽然我们的应用程序在第二阶段结束时肯定会比第一阶段更有活力,但它的行为会更像一个机器人——非常机械化。如果你记得电影《绿野仙踪》,它会像铁皮人一样——非常活跃,但没有心脏。这个第二阶段(也就是给我们的应用程序一些内在)将包括第四章,“路由”,第七章,“模板,指令和管道”,以及第六章,“构建 Angular 组件”。
最后,在构建应用的第 3 阶段,我们将最终给我们的铁皮人一个心脏!是的!好了,是什么让我们的应用有了心脏?答案是数据和 API!数据就像是我们应用的血液,而 API 就像是心脏——接收数据并推送数据。我打赌你从来没有以这种方式思考过 Web 应用,从现在开始,你将无法以其他方式思考它们(微笑)。这第三阶段将包括第十章,使用表单,第十二章,集成后端数据服务,第十一章,依赖注入和服务,以及第十四章,高级 Angular 主题。
第十三章,单元测试,和第十五章,部署 Angular 应用程序,实际上并不是任何阶段的一部分,但它们发挥着非常重要的支持作用。在这两章中,我们将学习如何测试我们为应用程序编写的代码,以及如何以几种不同的方式部署它。
这就是我们对我们的整体规划的大致了解。让我们再仔细看一下我们构建应用程序第一阶段的五步游戏计划,然后我们将进入我们的第一个技术主题,SASS。
-
步骤 1:在本章中,我们将研究 Bootstrap 的响应式网格,以及几个 Bootstrap 的组件:
-
我将解释 Bootstrap 的网格如何工作,并如何帮助我们布置网页。
-
在构建页面时,我将介绍我们将在页面上使用的 Bootstrap 组件,并且我们将使用我们的线框图来指导我们,结合 Bootstrap 的网格。
-
步骤 2:在第五章中,Flex-Layout – Angular’s Powerful Responsive Layout Engine,我们将用 Flex-layout 替换 Bootstrap 的网格系统。我们只会在一些网页上这样做,其他网页仍然会使用 Bootstrap 的网格。我们这样做有两个原因:
-
向您展示总是有备选方案可用,并且您通常可以混合和匹配这些备选方案。
-
能够混合和匹配为我们提供了一条路径,可以在不需要一次完成所有工作的情况下用另一种技术替换另一种技术。我们不想完全重建所有东西,我们只想重新做一些最初构建的东西的一部分,以学习如何应用特定的替代技术。
-
步骤 3:在第六章中,构建 Angular 组件,我们将看看如何构建自己的组件以在网页中使用。由于在创建我们自己的组件时,我们控制 HTML 和 CSS,我们可以利用 Bootstrap 的组件来创建我们自己的 Angular 组件的外观和感觉。注意:第七章,模板、指令和管道,也是其中的一部分,因为这两章是相关的。
-
步骤 4:在第八章中,使用 NG Bootstrap,我们将发现有现成的Angular-ready Bootstrap 组件。就像我们将为我们的一些页面用 Flex-layout 替换 Bootstrap 的网格一样,我们也将用 NG Bootstrap 项目的组件做同样的事情——即用 NG Bootstrap 项目的组件替换一些 Bootstrap 组件。我们这样做的动机是意识到有许多不同的第三方组件可以方便地用于我们的 Angular 应用程序,包括基于 Bootstrap 组件的组件。
-
步骤 5:在第九章中,使用 Angular Material,我们将再次替换一些 Bootstrap 组件,但这一次,它们与 Bootstrap 组件没有任何关系。Angular Material 项目有精美设计的组件,专门为在 Angular 应用程序中使用而设计,我们将学习如何将其中一些组件整合到我们的应用程序中。
同样,这里需要注意的重要事情是,我们在布置网页和选择使用的组件方面有技术选择,包括创建我们自己的自定义组件,当构建我们的 Angular 应用程序时。此外,正如你将在第十二章中看到的,集成后端数据服务,你在服务器端和数据库技术栈方面几乎有无限选择。而且,在第十四章中,高级 Angular 主题,我们将探索一些第三方身份验证 API,我们可能希望利用它们来代替从头开始编写我们自己的应用程序。
是的!我们有很多有趣的东西要一起在这本书中讨论。也就是说,让我们先专注于首要的事情,开始利用本章提供的好东西:Sass,Bootstrap,软件项目的典型演变(即,从构思到实现),并使用 Bootstrap 构建我们的列表轮播页面(即,构建应用程序的第一阶段)。我将在第七章的开头为构建应用程序的第二阶段提供类似的计划,模板,指令和管道,并在第十二章的开头为构建应用程序的第三阶段提供最终计划,集成后端数据服务。
我知道要涵盖的内容很多,但回顾我们的计划是我们要做的重要事情——知道我们在哪里,我们要去哪里总是有帮助的。现在,让我们加快速度,在我们讨论 Bootstrap 之前迅速过一遍 Sass 速成课程。
Sass 速成课程
对于大多数技术来说,包括本书中提到的所有技术,如 ES6,Bootstrap,Node 和 MongoDB,都可以写成整本书。Sass 也不例外。这个速成课程的目标不是让你成为 Sass 专家,也不是为了重复 Sass 的官方文档。由于空间限制,速成课程的目标只是向你介绍 Sass,并激励你自己进一步探索它,无论是在你完成这本书之后,还是与之并行进行,因为 Sass 是一个非常酷的技术。
Bootstrap 团队已经采用了 Sass 来进行 Bootstrap 项目的开发,而其他技术(比如 Compass)也是基于 Sass 构建的。严格来说,你不必知道如何使用 Sass 来编写 Angular 应用程序,甚至不需要通过本书来学习,但这是一项值得学习的技术,所以我鼓励你自己更仔细地了解一下。现在让我们一起来学习一些 Sass 的基础知识。
什么是 Sass?
-
Sass 是 Syntactically Awesome StyleSheets 的缩写,但当然,Sass 比缩写更有趣味!Sass 是 CSS 的扩展,为我们编写 Web 应用程序的 CSS 提供了额外的功能和灵活性。编译后,Sass 会为我们生成格式良好的 CSS。具体来说,Sass 为 CSS 带来的附加功能包括嵌套规则、变量、条件逻辑、数学运算、混合等。此外,Sass 还使得在 Web 项目中更容易维护和组织样式表。在这个速成课程中,我们将学习这些内容。
-
Sass 与所有版本的 CSS 兼容,不仅仅是 CSS3 和 CSS4。
-
由于 Angular CLI 的存在,Sass 可以很好地融入我们的 Angular 应用程序中,因为 CLI 默认为我们编译组件中的 Sass。
-
Sass 的官方网站可以在这里找到:
sass-lang.com/。
Compass 框架
Compass 是一个构建在 Sass 之上的 CSS 编写框架,提供了一些不错的附加功能,并且还会将你的 Sass 编译成 CSS。如果你在非 Angular 项目上工作,Compass 是一个选择(记住,Angular CLI 会为我们的 Angular 项目编译 Sass 为 CSS)。我们不会在本书中涵盖 Compass,但我至少想让你注意到这项技术,因为我知道作为 Web 开发人员,你不仅会使用 Angular 这项技术。然而,作为 Web 开发人员,我们无法避免使用 CSS!
这里的要点是,你可以简单地在 Angular 项目中使用 Angular CLI 来使用 Sass,但是对于非 Angular 项目,尤其是如果你的项目倾向于使用大量 CSS,可以考虑使用 Compass。
大公司使用 Compass。我知道其中两家,并且我每天都在使用它们的在线服务,它们分别是 LinkedIn(www.linkedin.com/),这是世界上最大的面向就业的社交网络服务,以及 Heroku(www.heroku.com),这是一个非常受欢迎的云应用平台。
您可以在官方网站compass-style.org/上了解有关 Compass 的所有信息。另一个提供有关 Sass 和 Compass 教程的不错的在线参考资料名为The Sass Way,网址为www.thesassway.com/。
两种 SASS 样式
Sass 有两种语法风格:旧语法依赖缩进,而新语法使用大括号而不是缩进。两种语法风格之间的另一个区别是,旧风格不需要在行末加上分号,而新风格需要。这两种风格的文件扩展名也不同——旧风格的文件扩展名是.sass,而当前风格的文件扩展名是.scss。
现在让我们快速看一下每种 CSS 语法的一个示例。第一个代码块是旧风格(.sass),第二个代码块以新的语法风格(.scss)产生相同的效果。我们将在整本书中都使用新风格。
这里给出的示例代码是用于编写.sass语法的:
$blue: #0000ff
$margin: 20px
.content-navigation
border-color: $blue
color: lighten($blue, 10%)
.border padding: $margin / 2 margin: $margin / 2 border-color: $blue
这里给出的示例代码是用于编写.scss语法的:
$blue: #0000ff;
$margin: 16px;
.content-navigation {
border-color: $blue;
color: lighten($blue, 10%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
两种语法风格之间的主要区别在于,旧风格旨在简洁,而新风格旨在更符合传统 CSS 语法的开发人员的习惯。
在前面的代码块中,您可能已经注意到了$blue和$margin。这些不是 CSS 项目,而是变量的示例。您可能还注意到了除法运算符。变量和数学计算只是您的 Sass 代码中可能包含的一些内容。我们将在接下来的部分中看到这些以及更多 Sass 功能。
无论您使用哪种语法——旧的还是新的——编译结果都是相同的。如果您将前面的任何一个代码块运行到在线编译器,比如Sass Meister*(我马上也会提到这个工具),生成的 CSS 将是以下内容:
.content-navigation {
border-color: #0000ff;
color: #3333ff;
}
.border {
padding: 10px;
margin: 10px;
border-color: #0000ff;
}
安装 Ruby
Sass 是用 Ruby 编写的,因此我们需要在计算机上安装 Ruby。要下载并安装最新版本,请转到 Ruby 的官方网站:www.ruby-lang.org/en/downloads/。要查看您的计算机上是否已安装 Ruby,请在命令行或终端上运行此命令:$ ruby -v。
如果 Ruby 已安装,输出将显示版本号。例如,当我执行$ ruby -v时,我的终端输出是ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]。从 2.3 版本开始的任何版本都足够满足我们的需求。
安装 Sass
安装了 Ruby 后,安装 Sass 就非常简单。请前往sass-lang.com/install并按照说明操作。
就像您可以通过在终端或命令行中运行$ ruby -v来获取 Ruby 的版本一样,您也可以使用 Sass 执行相同的操作。在终端或命令行中执行以下命令$ sass -v,以查看您系统上的 Sass 版本。我的系统输出如下:
Sass 3.5.5 (Bleeding Edge).
Sass 的在线工具
有几个在线工具可以用来将 Sass 文件编译成 CSS 文件。我喜欢的一个工具叫做Sass Meister,您可以在这里访问:www.sassmeister.com。
我喜欢它,因为它非常易于使用,并且在 Sass 语法出现问题时提供了不错的帮助。它还支持旧样式和新样式的 Sass 语法,并允许您从几种不同的 Sass 编译器中进行选择。您可以在窗格顶部的 Options 菜单选项下找到这些设置。
要使用该工具,只需在左窗格中编写您的 Sass 代码,编译后的 CSS 将显示在右窗格中。注意所选的选项,确保激活的选项是您想要的。
Sass 的离线工具
就像在线工具一样,我们有几个选项可以选择使用离线工具将 Sass 文件编译成 CSS 文件。我使用 Koala,因为它易于使用,跨平台,并且免费。您可以从项目网站免费下载 Koala:koala-app.com/。
Koala 让您不仅可以处理 Sass,还可以用它来编译和/或压缩 Less、JavaScript、CoffeeScript,甚至可以与 Compass 框架一起使用。
学习如何使用 Koala 的最佳方法是阅读官方文档,可以在github.com/oklai/koala/wiki#docs找到。但是,如果您只是暂时使用 Koala 来编译您的 Sass 文件,让我在这里快速概述一下步骤,以免您需要在书本和在线文档之间来回跳转。
您需要做的就是使用您选择的任何文本编辑器(如 Sublime Text 或 Visual Studio Code)创建一个 Web 项目,并在项目的根文件夹中创建一个 CSS 文件夹和一个 Sass 文件夹。当然,您不需要一个完成的项目,您只需要非常基本的文件夹结构。创建项目结构后,您可以打开 Koala 开始使用它为您编译 Sass 文件。以下是基本步骤:
- 创建一个空的项目结构,至少包括以下内容:
-
根文件夹中有一个空的
index.html页面 -
在根文件夹中有一个 CSS 文件夹,其中有一个空的
styles.css文件 -
在根文件夹中有一个 Sass 文件夹,其中有一个空的 style
.scss文件
-
打开 Koala 应用程序。
-
单击左上角的大加号(+),导航到您项目的根文件夹,并选择它。此时,Koala 将找到您的
styles.scss和styles.css文件。 -
在 Koala 的右侧窗格中右键单击
styles.scss文件,选择设置输出路径,然后在文件资源管理器中导航到并选择您的styles.css文件
遵循上述步骤就是您需要做的一切,以便设置 Koala 来为您编译 Sass 文件。编译的输出将被插入到您的styles.css文件中。
Sass 功能和语法
现在让我们来看看一些 Sass 的功能,这些功能在您的应用程序中最有可能使用。我们不会在我们的示例应用程序中使用所有这些功能,但我想向您展示一些 Sass 提供的很酷的东西。
嵌套
使用 Sass,您可以将 CSS 规则嵌套在彼此之内。Sass 不仅更易于阅读,而且有助于避免大量重复的 CSS 选择器。这对于高度嵌套的 CSS 规则尤其有效。让我们看一个简单的例子:
/* Here is some basic Sass that uses nesting */
#outer-frame p {
color: #ccc;
width: 90%;
.danger-box {
background-color: #ff0000;
color: #fff;
}
}
前面的 Sass 代码将被编译,生成相应的 CSS 代码:
/* This is the CSS that the above Sass is compiled to */
#outer-frame p {
color: #ccc;
width: 90%;
}
#outer-frame p .danger-box {
background-color: #ff0000;
color: #fff;
}
变量
Sass 变量就像你期望的那样:它们存储您想要在整个样式表中重用的信息。这样可以节省时间和烦人的错误。就像其他语言中的全局变量一样,您只需要在一个地方定义它们,所以如果它们需要更改,您只需要在一个地方更改变量,而不是更改 CSS 样式中的所有出现。
您几乎可以存储任何东西。这是一个示例,我们在其中存储了字体信息和字体颜色:
/* Here is the Sass code defining the variables */
$font-stack: Helvetica, sans-serif;
$primary-color: #333;
body {
font: 100% $font-stack;
color: $primary-color;
}
前面的 Sass 代码将被编译,生成相应的 CSS 代码:
/* Here is the CSS that the above Sass is compiled to */
body {
font: Helvetica, sans-serif;
color: #ccc;
}
数学运算
由于 Sass 编译为 CSS,您可以让它为您执行数学计算,而不是自己执行。您还可以让数学运行在变量上,而不是像下面的例子中的硬编码数字一样。当然,这样做非常方便:
/* Here is Sass that has some math in it */
.main-container { width: 100%; }
article {
float: right;
width: 700px / 960px * 100%;
}
前面的 Sass 代码将被编译,生成相应的 CSS 代码:
/* Here is the CSS that the above Sass is compiled to */
.main-container {
width: 100%;
}
article {
float: right;
width: 72.91667%;
}
导入
Sass 使您能够使用@import指令将一个样式表导入到另一个样式表中。这就像它听起来的那样,非常简单。让我们来看一个例子。在以下三个代码列表中,第一个是基本样式(base.scss)表,适用于整个站点,第二个是用于报告页面(reports.scss)的样式表。第三个是在 Sass 编译期间报告样式表导入基本样式表时得到的结果 CSS 样式表。请注意,在 Sass 中使用@import指令时不需要文件扩展名:
/* base.scss */
body {
margin: 10px;
padding: 10px;
font: Helvetica, sans-serif;
color: #333;
background-color: #eee;
}
/* reports.scss */
@import 'base';
p {
margin: 5px;
padding: 5px;
color: #0000CD;
background-color: #EEE8AA;
}
前面的 Sass 代码将被编译,生成相应的 CSS 代码:
body {
margin: 10px;
padding: 10px;
font: Helvetica, sans-serif;
color: #333;
background-color: #eee;
}
p {
margin: 5px;
padding: 5px;
color: #0000CD;
background-color: #EEE8AA;
}
扩展
使用@extend让您可以从一个选择器向另一个选择器共享一组 CSS 属性。一个选择器可以使用@extend Sass 指令从另一个选择器继承。以下示例显示了一组三个相关样式(活动的,非活动的和终止的)的常见样式属性。
%common-status-styles {
width: 200px;
height: 75px;
padding: 10px;
color: #333;
}
.active {
@extend %common-status-styles;
background-color: green;
border-color: #001a00;
}
.inactive {
@extend %common-status-styles;
background-color: yellow;
border-color: #999900;
}
.terminated {
@extend %common-status-styles;
background-color: pink;
border-color: #ff5a77;
}
当前面的 Sass 代码被编译时,它会变成以下 CSS:
.active, .inactive, .terminated {
width: 200px;
height: 75px;
padding: 10px;
color: #333;
}
.active {
background-color: green;
border-color: #001a00;
}
.inactive {
background-color: yellow;
border-color: #999900;
}
.terminated {
background-color: pink;
border-color: #ff5a77;
}
混合
混合器就像命名模板。它们是 Sass 允许您将 CSS 或 Sass 声明(即 CSS 样式)分组并为其命名的方式。这样,您可以根据需要在其他 CSS 类中包含这些声明,而无需复制和粘贴-这样做会在以后需要更改时造成一些混乱。在某种意义上,它们也像变量,因为您只需要在一个地方更改某些内容(即在混合器本身),但它们比变量更强大,这就是为什么我提到它们像模板。实际上,混合器甚至可以使用变量进行参数化。让我们看一个例子,前面的描述应该清楚地说明了我所说的混合器就像模板的意思。
这是我喜欢在我的网站中使用的下拉菜单的样式示例。我们将参数化宽度,以便我们可以创建不同大小的下拉菜单。请注意使用@mixin指令:
@mixin custom-dropdown($dropdown-width) {
-webkit-appearance: button;
-webkit-border-radius: 2px;
-webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
-webkit-padding-end: 20px;
-webkit-padding-start: 2px;
-webkit-user-select: none;
background-image: url(https://www.maxfusioncloud.com/static/img/15xvbd5.png),
-webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
background-position: 97% center;
background-repeat: no-repeat;
border: 1px solid #AAA;
color: #555;
font-size: 10pt;
margin: 0px;
overflow: hidden;
padding: 5px 12px 6px 6px;
text-overflow: ellipsis;
white-space: nowrap;
width: $dropdown-width;
}
以下是我们如何使用混合器(请注意使用@include指令):
.small-dropdown { @include custom-dropdown(75px); }
.medium-dropdown { @include custom-dropdown(115px); }
.large-dropdown { @include custom-dropdown(155px); }
这将编译为以下 CSS:
.small-dropdown {
-webkit-appearance: button;
-webkit-border-radius: 2px;
-webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
-webkit-padding-end: 20px;
-webkit-padding-start: 2px;
-webkit-user-select: none;
background-image: url(https://www.maxfusioncloud.com/static/img/15xvbd5.png),
-webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
background-position: 97% center;
background-repeat: no-repeat;
border: 1px solid #AAA;
color: #555;
font-size: 10pt;
margin: 0px;
overflow: hidden;
padding: 5px 12px 6px 6px;
text-overflow: ellipsis;
white-space: nowrap;
width: 75px;
}
.medium-dropdown {
-webkit-appearance: button;
-webkit-border-radius: 2px;
-webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
-webkit-padding-end: 20px;
-webkit-padding-start: 2px;
-webkit-user-select: none;
background-image:
url(https://www.maxfusioncloud.com/static/img/15xvbd5.png),
-webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
background-position: 97% center;
background-repeat: no-repeat;
border: 1px solid #AAA;
color: #555;
font-size: 10pt;
margin: 0px;
overflow: hidden;
padding: 5px 12px 6px 6px;
text-overflow: ellipsis;
white-space: nowrap;
width: 115px;
}
.large-dropdown {
-webkit-appearance: button;
-webkit-border-radius: 2px;
-webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
-webkit-padding-end: 20px;
-webkit-padding-start: 2px;
-webkit-user-select: none;
background-image:
url(https://www.maxfusioncloud.com/static/img/15xvbd5.png),
-webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
background-position: 97% center;
background-repeat: no-repeat;
border: 1px solid #AAA;
color: #555;
font-size: 10pt;
margin: 0px;
overflow: hidden;
padding: 5px 12px 6px 6px;
text-overflow: ellipsis;
white-space: nowrap;
width: 155px;
}
到目前为止,您可以看到 Sass 有多酷,它可以为您节省多少时间,以及您如何使用它来避免代码重复和避免犯愚蠢的剪切和粘贴错误。
如果所有这些还不够酷的话,Sass 还通过其内置函数为您提供了很多强大的功能。有很多内置函数,这就是为什么 Sass 如此强大和实用。您可以在这里查看它们:sass-lang.com/documentation/Sass/Script/Functions.html。我们只会涵盖一个,只是为了向您展示如何在接下来的内置函数部分中使用函数的示例。
占位符
占位符旨在与@extend指令一起使用。使用占位符的规则集,但不使用@extend指令,将不会呈现为 CSS。使用占位符的一个有效用例是,如果您正在编写一个用于代码重用的 Sass 库。您可以编写一个包含占位符的 Sass 文件,该文件旨在包含在您或其他人编写的另一个 Sass 文件中。如果 Sass 文件中的规则集导入了作为库的其他 Sass 文件,并且规则集扩展了您的库中的占位符,则在编译 Sass 文件时,它将呈现为 CSS 文件。如果没有规则集扩展占位符,则占位符将不会呈现/打印到 CSS 文件中。
让我们来看一个例子。注意,占位符前缀为百分号(%):
%warning-placeholder {
color: red;
font-weight: bold;
font-size: 1.5em;
}
.warning {
@extend %warning-placeholder;
}
Sass 代码编译为以下 CSS:
.warning {
color: red;
font-weight: bold;
font-size: 1.5em;
}
内置函数
当我们介绍 Sass 的扩展功能时,每个类的边框颜色比其对应的背景颜色暗 20%。为了找到比另一个颜色暗 20%的颜色,你需要进行一些繁琐的数学运算,如果以后决定更改百分比,就需要进行更多繁琐的数学运算。幸运的是,Sass 为我们提供了内置函数,可以做各种事情,包括变暗和变亮颜色等等。
现在,让我们重新审视之前在扩展部分看到的 Sass 代码,并且这次使用变量和内置的变暗函数来更灵活地编写它,以便让 Sass 为我们做数学运算。这样,如果以后选择更改百分比,就会变得很容易。以下 Sass 代码的编译输出将与之前扩展部分的编译输出完全相同,因此我们不会在这里重复。
/* Example of using variables and a built-in function */
$active-color: green;
$active-border-color: darken($active-color,20%);
$inactive-color: yellow;
$inactive-border-color: darken($inactive-color,20%);
$terminated-color: pink;
$terminated-border-color: darken($terminated-color,20%);
%common-status-styles {
width: 200px;
height: 75px;
padding: 10px;
color: #333;
}
.active {
@extend %common-status-styles;
background-color: $active-color;
border-color: $active-border-color;
}
.inactive {
@extend %common-status-styles;
background-color: $inactive-color;
border-color: $inactive-border-color;
}
.terminated {
@extend %common-status-styles;
background-color: $terminated-color;
border-color: $terminated-border-color;
}
自定义函数
Sass 通过使用其现成的内置函数为我们提供了很大的能力,但有时,没有什么能替代自定义函数——为手头的项目做出你想要的功能。Sass 团队的成员知道这一点,因此为我们提供了一种在 Sass 文件中添加自定义函数的方法。
为了结束这个 Sass 速成课程,让我们快速看一下如何创建自定义函数。我们的函数将根据两个参数计算宽度百分比,目标宽度的列数和我们拥有的总列数。
在这个简短的例子中,你会注意到我们做了以下几点:
-
使用变量
-
进行一些简单的数学运算
-
使用内置的 Sass 函数(即百分比)
-
引入两个新的 Sass 命令:
@function和@return:
@function column-width-percentage($cols, $total-cols) {
@return percentage($cols/$total-cols);
}
.col-1 {
width: column-width-percentage(4, 12);
}
.col-5 {
width: column-width-percentage(5, 12);
}
这将编译为以下 CSS:
.col-1 {
width: 33.33333%;
}
.col-5 {
width: 41.66667%;
}
我希望你能在网页开发中找到 Sass 的用武之地。现在看起来可能有点多余,但当你花些时间去尝试时,我相信你会发现聪明的方法来利用 Sass 帮助你更好地组织你的 CSS,并帮助减少代码重复的问题。
现在让我们转变一下方向,快速看一下 Bootstrap。
Bootstrap 速成课程
在本节中,我们将重点介绍 Bootstrap,特别是其响应式网格和组件。我们将只介绍足够多的 Bootstrap 网格,让您对如何使用它有一个坚实的开始。我们还将只介绍 Bootstrap 的五个组件,让您开始。Bootstrap 拥有远远超过五个组件,而且您可以以许多方式自定义每一个组件。然而,这是 Bootstrap 的速成课程,而不是详尽的手册——要试图详尽地介绍 Bootstrap,需要的是一本全面的手册。Bootstrap 是一个庞大的库,有大量的选项供您使用,因此重要的是向您展示基础知识,并告诉您在哪里获取有关 Bootstrap 的更多信息,而不是试图详尽地介绍它。好消息是,这个 Bootstrap 的速成课程是让您快速上手的最快方法。
采取这种方法的原因如下:
-
我们不会在示例应用程序中使用所有的 Bootstrap 组件
-
我们的示例应用程序也将使用 ng-bootstrap 组件和 Angular Material 组件进行制作(我们将在后面的章节中介绍:第八章,使用 NG Bootstrap,和第九章,使用 Angular Material)
-
对我们来说,Bootstrap 最重要的部分将是 Bootstrap 的网格——我们将对网格进行比我们将要查看的五个组件更详细的介绍
然而,与 Sass 速成课程不同,我们将看到如何实际使用 Bootstrap,因为我们将直接在本章中的主页布局中使用它。
什么是 Bootstrap?
Bootstrap 是一个用于构建响应式网站的 CSS 框架,重点是移动优先。虽然还有其他前端呈现框架,但 Bootstrap 仍然是这个领域的霸主,不仅因为它拥有最多的关注度,而且它可能拥有最多的运行次数。我所说的运行次数是指它在网站中被使用的次数,因此它比其他 CSS 框架更加经受考验。Bootstrap 的领先关注度(即流行程度)主要是由于以下三个原因:
-
它是其类别中最早的框架之一(因此竞争几乎不存在)
-
它得到了世界顶级社交网站(即 Twitter)的支持
-
该项目自 2011 年 8 月开始存在,因此非常成熟。
另外,正如我们将在第八章中看到的,使用 NG Bootstrap,ng-bootstrap 项目的目标是使用 Bootstrap 4 创建 Angular 小部件,这充分说明了 Angular 社区对 Bootstrap 的看法。
第三版保持了 Angular 和 Bootstrap 之间的关系之所以如此真实的原因是因为它们各自在各自的领域都是领导者,并且它们之间是共生兼容和互补的。事实上,这两个框架就足以构建强大的 Web 应用程序的前端部分——只需选择任何你喜欢的后端构建,因为如今所有的后端框架都可以生成和消费 JSON,包括仍在运行 COBOL 程序的大型机。这是因为 JSON 已经成为通过消息集成系统的最流行方式。
动机
如果你曾经尝试过在不使用框架的情况下构建一个在不同视口大小(即,形态因素/屏幕大小)上运行良好的网站,那么你很容易就能理解使用 Bootstrap 的动机——从头开始构建这样的东西既乏味又困难。移动计算确实加剧了对类似 Bootstrap 的需求,而它的出现是不可避免的。虽然对于几乎任何框架都可以说同样的话,即你可能不应该花时间重新发明轮子,除非你有极好的理由这样做,但可以说(对于绝大多数网站,甚至 Web 应用程序)前端比后端更加重要。在过去几年里,客户端已经成为了新宠。我并不是在暗示后端不重要——事实恰恰相反,i,集成后端数据服务,完全致力于构建后端。然而,我建议的是,当移动计算出现时,我们已经有了足够多的后端技术和大量的框架可供选择,但缺乏前端框架。
我将在结束这个动机部分时添加的最后一条评论是,在商业世界中一举两得可以为公司带来竞争优势(即,市场速度)和/或财务优势(即,成本节约)—因此在软件开发中也不例外。如果你可以构建一次东西,在这种情况下是一系列网页,并且在移动和桌面上使用相同的客户端代码,而不是构建两套一切(甚至考虑到平板电脑,甚至三套),你应该意识到节约了时间和金钱。这就是承诺—不幸的是,它并不总是兑现。然而,在这些领域获得一些优势肯定比没有获得任何优势要好。
Bootstrap 在我们示例应用程序中的作用
对于我们的示例应用程序,Bootstrap 将仅用于两个目的:
-
使用其响应式网格布局网页
-
利用一些现成的组件快速构建样式良好的 UI
安装 Bootstrap
出于学习目的,安装 Bootstrap 与我们将在 Angular 应用程序中安装 ng-bootstrap 是不同的。本章重点介绍 Bootstrap 的网格系统以及一些组件,因此我们将通过暂时不创建 Angular 应用程序或完全不使用 Angular 来保持简单。在本章结束时,我们将只有我们的皮肤和骨头应用程序(如前所述),然后将其转换为一个完整的 Angular 应用程序。
让我们从将 Bootstrap 集成到我们的 HTML 中的最小且最快的方式开始。要使用 Bootstrap 提供的所有功能,我们只需要添加一个样式表和三个 JavaScript 文件的资源链接。
以下是创建演示如何将 Bootstrap 引入的空 HTML 页面的 HTML 代码:
<!DOCTYPE html>
<html>
<head>
<title>Chapter 3 - Bootstrap: Responsive Grid Layout &
Components</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/
bootstrap/4.0.0/css/bootstrap.min.css" crossorigin="anonymous”>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js”
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/
1.12.9/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/
js/bootstrap.min.js” crossorigin="anonymous"></script>
</head>
<body>
This page is intentionally blank. It's sole purpose is to show the HTML code that needs to be added to integrate Bootstrap.
</body>
</html>
根据先前 HTML 代码中链接文件的顺序,以下是一个 CSS 文件和三个 JavaScript 文件的用途:
-
bootstrap.min.css文件是 Bootstrap 的压缩样式表,其中定义了所有默认样式 -
jquery-3.2.1.slim.min.js文件是包含 jQuery 库的压缩 JavaScript 文件,并且被引用,因为 Bootstrap 本身依赖于 jQuery -
popper.min.js文件是另一个名为 Popper 的第三方库的压缩 JavaScript 文件,并且被引用,因为 Bootstrap 利用其中的功能来实现其工具提示组件 -
最后,
bootstrap.min.js文件是 Bootstrap 本身的压缩 JavaScript 文件,用于各种组件,如模态和下拉组件,这些组件需要 JavaScript 来运行
您还会注意到这些链接是指向 CDN(即内容传送网络)的资源。虽然有其他安装 Bootstrap 在我们网站上的方法,但使用 CDN 的优点有三个:
-
我们不需要在我们的网页项目中下载和包含文件
-
客户加载我们的网页的时间被最小化,因为这些资源可能在他们访问我们之前的其他网站时已经被下载到他们的浏览器中
-
服务器经过优化,用于传送这些资产(使用缓存和其他托管策略)
当我们在本章后面看 Navs 和 Navbar 组件时,我们将考虑在我们的主页上添加导航栏。
Bootstrap 的响应式网格系统
从我的角度来看,特别是作为一个专注于 Angular 的 Web 开发人员,Bootstrap 提供的最重要的东西是响应式网格。原因是有许多来自许多不同库的 Web / HTML 组件可供选择进行 Web 开发(例如 NG Bootstrap 和 Angular Material,我们将在后面的章节中介绍),因此,我们并不仅仅局限于只使用 Bootstrap 的组件。然而,无论您最终使用哪些组件,或者如果您创建自己的组件(正如我们将在第六章中学习的那样,构建 Angular 组件),Bootstrap 网格仍然可以用于构建响应式布局,并极大地简化我们创建设计良好的 Web 应用程序的繁重任务。
Bootstrap 的网格使我们能够轻松地为各种视口(即屏幕)大小布局我们的页面。我们只需使用特殊的 Bootstrap 类来指示在我们的页面上为我们的应用程序可能运行的不同视口大小定位事物。
如果你曾经想知道 Bootstrap 是否是建立在其他东西之上的,答案是,毫不奇怪地,“是”。库和框架经常相互依赖。这就是开源世界中现代软件的构建方式。毕竟,当我们已经有完全可靠的轮子可供使用时,为什么要重新发明轮子呢?我们已经从之前关于安装 Bootstrap 的部分中看到,Bootstrap 依赖于 jQuery 和 Popper。Bootstrap 的响应式网格系统是建立在 CSS3 中引入的 CSS Flexbox 之上的。
在 CSS4 中有一个更新的网格系统,称为 CSS Grid,但 Bootstrap 4 没有使用它。Bootstrap 使用 CSS Flexbox。这并不意味着 Bootstrap 落后于时代,因为更新并不一定意味着更好。使用 CSS Grid 可以使一些事情变得更容易,而使用 CSS Flexbox 可以使其他事情变得更容易。
稍后,当我们讨论 Bootstrap 预定义的用于在网格内垂直和水平对齐事物的类时,如果你熟悉 CSS Flexbox,这些类名可能会让你感到熟悉。这是因为 Bootstrap 在内部使用 CSS Flexbox,并且类名是受其类名启发而来的。
网格本身有三个主要部分(容器、行和列),每个部分都是在 Bootstrap 的 CSS 文件中定义的类,这就是为什么需要在我们的页面中引用它。
这就是俗语变得有点混乱的地方,所以让我快速解释一下。正如你所知,HTML 中没有名为“容器”、“行”或“列”的元素。但是,在 HTML 中我们有 div 元素,在 Bootstrap 中,我们用一个类来装饰它,特别是用容器、行或列的类。但是当我们谈论 Bootstrap 时,假装这些类型的 HTML 元素会更容易。让我澄清一下,因为从现在开始我将把 Bootstrap 行称为行元素,把 Bootstrap 列称为列元素。我的意思是:
-
说“容器元素”比说“具有容器类的 div 元素”更容易(在代码中,这就是“容器元素”的样子:
<div class="container">) -
说“行元素”比说“具有行类的 div 元素”更容易(在代码中,这就是“行元素”的样子:
<div class="row">) -
更容易说“列元素”,而不是不得不说“具有列类的 div 元素”(在代码中,这就是“列元素”的样子:
<div class="col">)
好了,希望这样说得通。
容器
容器是网格中的根元素,或顶层元素。它包含一个或多个行,这些行必须嵌套在容器内,而行又可以包含零个或多个列。要创建一个 Bootstrap 网格,我们首先创建一个容器,为此,我们只需创建一组 HTML div元素,并将容器类分配给第一个div元素。
在代码中看起来是这样的:
<div class="container">
</div>
哈!你明白我为什么提到前面关于容器元素的东西了吗?这是一个混乱的方式来尝试解释它。所以,现在让我们用我们的新术语来重新表达一下。
要创建一个 Bootstrap 网格,首先添加一个容器元素,就像这样:
<div class="container">
</div>
啊,这样说和读起来容易多了!好了,回到我们正常的节目安排…
你可以有两种类型的容器,它们的类名使它们彼此区分开来:
<!-- fixed-width container centered in the middle the viewport
--> <div class="container"></div>
<!-- full-width container that spans the entire viewport width (no margins)
--> <div class="container-fluid"></div>
行
行元素必须嵌套在容器元素中。(哈!我喜欢这个元素的东西。试着在写作中解释它而不做类似的事情!)Bootstrap 网格必须至少包含一行,并且可以包含所需的行数。
在前一个容器代码的基础上,以下是具有两行的网格代码的样子:
<div class="container">
<div class="row">
</div>
<div class="row">
</div>
</div>
一行不一定要包含一列——例如,你可能只是想在网格中的两行之间留出空白,但它最多可以包含 12 列。
然而,重要的是要注意,一行中的列数与嵌套列元素的数量不成比例(我们将在下一节中看一下 Bootstrap 对列的概念)。这是因为一行中的总列数与该行中的列元素数量是独立的。
让我通过在上一行代码中添加三个示例来澄清这个概念。我将解释class="col-4",class="col-6",以及一般来说,class="col-x"(其中 x 是从 1 到 12 的整数)的含义,紧接着是以下三个网格示例。
在第一个示例中,网格有两行,每行都有三列等宽:
<div class="container">
<div class="row">
<div class="col-4">
</div>
<div class="col-4">
</div>
<div class="col-4">
</div>
</div>
<div class="row">
<div class="col-4">
</div>
<div class="col-4">
</div>
<div class="col-4">
</div>
</div>
</div>
在第二个示例中,网格只有一行,有两列等宽:
<div class="container">
<div class="row">
<div class="col-6">
</div>
<div class="col-6">
</div>
</div>
</div>
在第三个例子中,网格也只有一行,有两列,但它们的宽度不相等。事实上,第一列只占总宽度的 25%,第二列占了剩下的 75%。
<div class="container">
<div class="row">
<div class="col-3">
</div>
<div class="col-9">
</div>
</div>
</div>
好了,现在我们已经看过三个网格示例,我们可以讨论一下 "col-x" 类名到底是什么意思。网格允许每行最多有 12 列,你在行中嵌入的每个列元素可以跨越 1 到 12 列——这就是 x 代表的意思。举个例子,如果我们在行中有一个列元素,并且希望它跨越可用的 12 列中的 8 列,我们的类名将是col-8,我们的列元素将如下所示:<div class="col-8">。关键是我们行中的列的总数(也就是我们类名中 x 的总和)不应该超过 12。但是,它可以少于 12。
此外,你的网格中的每一行可以有不同数量的列,每个列的宽度也可以不同。在讨论一些有趣的方法之前,让我们快速看一个例子,你可以通过向行元素添加预定义的 Bootstrap 类来对齐行内的列。
<div class="container">
<div class="row">
<div class="col-10">
</div>
<div class="col-2">
</div>
</div>
<div class="row">
<div class="col-4">
</div>
<div class="col-3">
</div>
<div class="col-5">
</div>
</div>
</div>
在前面的代码中的网格有两行,第一行有两列宽度不等,第二行有三列宽度不等。
每当你有一个网格时,你需要关心的是如何在其中对齐。Bootstrap 有预定义的类,可以用于行元素,以便对其中的列元素进行对齐。
以下是其中一些类:
-
justify-content-center(居中对齐列)
-
justify-content-start(左对齐列)
-
justify-content-end(右对齐列)
-
justify-around-end(均匀间隔列)
-
justify-between-end(将所有可用空间放在两列之间)
这些类的有趣之处在于它们影响封装行中列的水平对齐,只有当所有列元素跨越的列数总和少于 12 时,你才能看到它们的效果。这正是为什么允许少于 12 列的跨度的原因。
这是一个包含少于 12 列跨度的行元素的例子:
<div class="container">
<div class="row justify-around-end">
<div class="col-4">
</div>
<div class="col-4">
</div>
</div>
</div>
在先前的示例中,我们有一个包含两列的一行网格。然而,由于跨度的列少于 12 列,将应用水平对齐(由于justify-around-end类),这将产生可见效果,即将列居中,并在列周围插入可用的未使用空间(在这种情况下为行宽的三分之一)。这将使列两侧出现边距,它们之间的边距加倍。
其他提到的类别具有不同的水平对齐效果,与它们旁边的项目描述不同。我鼓励你尝试使用这些类别来熟悉它们。
列
列元素必须嵌套在行元素中,就像之前的示例所示。我们已经看到了多少列元素可以放入一行中,这取决于它们各自的列宽度。
网格中的列基本上是网格中的单元格,是您的内容(即文本、图像等)要插入的位置。如果您有一个包含六行四列元素的网格,您有 24 个单元格可供放置您的内容。
就像你可以使用行元素上的特殊类别来对齐列元素一样,你也可以使用列元素上的特殊类别来对齐列元素内的内容。
以下是您可以在列元素上使用的一些类别,以便对齐其中的内容:
-
align-self-start将强制特定单元格的内容位于单元格顶部 -
align-self-end将强制特定单元格的内容位于单元格底部 -
align-self-center将强制特定单元格的内容位于单元格的垂直中心
不同的视口大小
关于 Bootstrap 的网格,我想要讨论的最后一件事可能是最重要的。什么使得网格具有响应性?也就是说,网格如何适应不同的视口尺寸?这个问题有两个方面的答案。首先,大多数 HTML 布局(甚至是那些根本没有设计为响应式的普通布局)在不同尺寸的屏幕上查看时都有一定的灵活性。然而,虽然标准网页的布局在平板电脑和普通 19 英寸显示器上的渲染可能仍然可以接受,但对于一个在平板上看起来不错,但目前正在普通手机上查看的网站来说,情况往往会变得混乱,甚至根本无法使用,比如 iPhone 7 或类似尺寸的 Android 设备。这就是我们需要一些设计干预的地方,也是 Bootstrap 网格适应设备视口尺寸的第二种方式,即对类和列的类名进行特殊调整。
你会记得我们一直在为列元素使用的类名具有以下一般形式:
<div class="col-x">
为了使网格具有响应性,Bootstrap 包含了让我们通过在col和x之间的类名中添加一个符号来调整类的能力(即从 1 到 12 的整数)。
例如,以下是带有其中一个这些符号的列元素类的样子(实际上,它不是一个符号,而是一个新的类名,但是为了解释起见,你可以把它看作是一个符号):
<div class="col-sm-4">
我将解释一下col-sm-4中的sm是什么意思,但实际上,在实践中,你会在列元素上看到不止一个类名。例如,以下是列元素上可能的一组类名:
<div class="col-xs-12 col-sm-4 col-md-3" >
好的,让我们解释一下这组类是用来做什么的。为了做到这一点,让我首先列出可用的符号及其含义:
| 视口尺寸 | 超小 | 小 | 中 | 大 | 超大 |
|---|---|---|---|---|---|
| 网格断点 | <576px | >=576px | >=768px | >=992px | >=1200px |
| 最大容器宽度 | 无 | 540px | 720px | 960px | 1140px |
| 符号 | xs | sm | md | lg | xl |
| 典型设备 | iPhone、iPod、Android 手机 | iPad 1、iPad 2、iPad Mini | 旧显示器(低分辨率,800x600)、一些旧 Android 平板 | 普通现代显示器、大多数现代 Android 平板 | 高分辨率现代显示器、iPad 3、iPad 4、Android 平板 |
| 类前缀 | .col-xs- | .col-xs- | .col-md- | .col-lg- | .col-xl- |
在上表中,从底部的第三行开始,我列出了对你可用的五个符号。在倒数第二行,我列出了符号和网格断点适用的典型目标设备。我将在一会儿讨论网格断点,但我只想说我列出的这些目标设备是经验法则——它们并非一成不变。例如,Android 平板电脑在五个视口大小列中出现了三次。这是因为有许多 Android 平板电脑制造商,甚至更多尺寸的显示器(即视口)可供选择。笔记本电脑也是如此。然而,基于苹果产品的视口大小是众所周知的,数量较少——这就是我按名称列出它们的原因。可以说,通过查看典型设备的行,你可以相当清楚地了解你可能想要使用哪个列类。
掌握了视口大小和之前的表格的知识,现在让我们解密这个列元素和类别的含义:
<div class="col-xs-12 col-sm-4 col-md-3" >
这个列元素包含一组三个类,每个类基本上指示浏览器如何根据视口大小呈现列和其内容。从技术上讲,视口大小是显示器的最大尺寸(以像素为单位)。以分辨率设置为 1600 x 900 的 13 英寸笔记本电脑显示器为例,其视口大小为 1600 像素宽,900 像素高。然而,在实际情况下,视口大小是浏览器窗口的尺寸,而不是笔记本电脑显示器本身的尺寸。这在我们谈论响应式网页设计时是一个重要的区别,因为在使用台式机或笔记本电脑时,人们可以调整他们的浏览器大小——这会强制网页重新呈现——因此,这确实是 Bootstrap 的视角和我们的目的所在的视口大小。
回到解密上一列元素的过程,参考上一个视口大小表,并提到浏览器调整大小如何决定我们作为开发者关心的视口大小,我们现在可以解密这三个类别指示浏览器要做什么:
-
col-xs-12:这告诉浏览器,当视口宽度小于 576 像素时,该列应跨越所有 12 列。换句话说,该列应占据整个行的可用宽度。 -
col-sm-4:这告诉浏览器,当视口宽度在 576 到 767 像素之间时,该列应占用 12 个可用列中的四列。换句话说,该列应占据行宽的 1/3。 -
col-md-3:这告诉浏览器,当视口宽度为 768 像素或更多时,该列应占用 12 个可用列中的三列。换句话说,该列应占据行宽的 1/4。
我们本可以通过添加带有类前缀.col-lg-和.col-xl-的类来控制视口宽度为 992 像素或更多时列的呈现,但在我们刚刚看到的例子中,我们似乎并不在乎——也就是说,无论视口有多宽(甚至是 2400 像素!),我们的列宽都会按比例占据行宽的 25%。
各位先生女士,这就是你如何设计一个网页,同时保持网格单元格中内容在成千上万个视口尺寸上的呈现方式。通过利用 Bootstrap 的网格,我们不再需要编写多个版本的网页来让它们在不同尺寸的显示器上显示我们想要的样子。相当酷,不是吗?
Bootstrap 组件
正如本章开头提到的,我不想在讲解组件时简单地重复 Bootstrap 的文档。相反,我将简要讨论我们将使用的 Bootstrap 的五个组件,展示它们的一些基本代码,并指向 Bootstrap 官方文档,让你可以了解更多关于这些组件的选项,其中有很多——远远超出了这本书的覆盖范围。
按钮组件
按钮无处不在——不,我指的不是你最喜欢的衬衫上的按钮。如果你曾经乘坐电梯(嘿,有些人绝对拒绝进入电梯),你肯定会看到按钮,并按下其中一个按钮会把你带到你想去的地方。电视遥控器也是一样的——但是它不是把你带到另一个地方(至少目前还没有,但也许在未来,你们永远不知道),它是把你的思绪从舒适的客厅带到另一个地方。这些按钮执行功能性、有意义的任务。网页上的按钮呢?可以说,它们也传输东西——比如,当你点击表单上的提交按钮时,它传输信息。但也许按钮同样重要的功能是帮助使你的网页更具吸引力和直观。幸运的是,Bootstrap 让我们可以轻松地向我们的网页添加漂亮的样式按钮——比浏览器在添加按钮元素时呈现的默认灰色按钮精致 100 倍。
让我们看看一些这些类,同时探索一些 Bootstrap 预定义的按钮类(即样式)。
无需任何调整,我们可以通过给按钮元素分配两个类来轻松插入一个漂亮的样式按钮,就像这样:
<button type="button" class="btn btn-primary">Click me</button>
那个按钮是蓝色的,但是我们可以通过其他类来访问其他默认颜色:
-
btn-secondary: 浅炭灰色,白色字体 -
btn-success: 浅绿色,白色字体 -
btn-danger: 红色,白色字体 -
btn-warning: 金黄色,黑色字体 -
btn-info: 蓝绿色,白色字体 -
btn-light: 浅灰色,黑色字体 -
btn-dark: 几乎是黑色,白色字体
还有一个将按钮变成链接的类:btn-link
如果你更喜欢白色一些,或者颜色更轻一些,Bootstrap 有一组与前面的类匹配的类,称为轮廓按钮。颜色和类名是相同的,唯一的区别是在btn和secondary, success, danger等之间加上outline这个词。按钮是透明的,除了轮廓或边框,当然,按钮上的文本的字体颜色也是不同的。
这些类名看起来是这样的:
-
btn-outline-secondary: 浅炭灰色轮廓,字体颜色相同 -
btn-outline-success: 浅绿色轮廓,字体颜色相同 -
btn-outline-danger: 红色轮廓,字体颜色相同 -
btn-outline-warning:金菊色轮廓,字体颜色相同 -
btn-outline-info:青色轮廓,字体颜色相同 -
btn-outline-light:浅灰色轮廓,字体颜色相同 -
btn-outline-dark:几乎黑色轮廓,字体颜色相同
所有这些按钮都有默认的高度和字体大小。但是,正如您可能已经猜到的那样,Bootstrap 有一种方法可以通过分别添加.btn-lg或.btn-sm类来使默认按钮变大或变小。以下是它的样子:
-
<button type="button" class="btn btn-primary btn-lg">我很大</button> -
<button type="button" class="btn btn-primary btn-sm">我很小</button>
您可以在这里阅读有关 Bootstrap 按钮的所有信息:getbootstrap.com/docs/4.0/components/buttons/
警报组件
当用户在网页上执行操作,例如在其用户资料中更新其电话号码时,让他们知道更新是否成功或不成功总是很好。有时这些用户反馈消息被称为“闪现消息”(因为它们通常只出现片刻,然后消失,以免使屏幕混乱)。Bootstrap 称它们为“警报”,通过向div元素添加预定义的警报类和 role 属性来创建它们。
在大多数情况下,它们的着色和命名方案与按钮组件相当一致。以下是可用的警报:
-
<div class="alert alert-primary" role="alert">这是一个主要警报</div> -
<div class="alert alert-secondary" role="alert">这是一个次要警报</div> -
<div class="alert alert-success" role="alert">这是一个成功警报</div> -
<div class="alert alert-danger" role="alert">这是一个危险警报</div> -
<div class="alert alert-warning" role="alert">这是一个警告警报</div> -
<div class="alert alert-info" role="alert">这是一个信息警报</div> -
<div class="alert alert-light" role="alert">这是一个浅色警报</div> -
<div class="alert alert-dark" role="alert">这是一个黑暗警报</div>
Bootstrap 的警报不仅外观漂亮,而且非常整洁。您可以在其中嵌入链接(毕竟它只是 HTML),甚至插入一个可选的关闭按钮。警报组件是 Bootstrap 依赖 jQuery 库的一个很好的例子,因为它需要用于关闭警报组件。
警报值得学习,这样您就可以在应用程序中利用它们。这是 Bootstrap 关于其警报组件的文档链接:getbootstrap.com/docs/4.0/components/alerts/。
导航栏组件
导航栏组件非常丰富 - 您可以做很多事情 - 但本质上,它是 Bootstrap 为您提供一个漂亮样式的网页顶部导航栏的方式。丰富性来自于可以使用的一些子组件。这些包括以下内容:
-
.navbar-brand用于您公司、产品或项目名称 -
.navbar-nav用于全高度和轻量级导航(包括对下拉菜单的支持) -
.navbar-toggler用于与我们的折叠插件和其他导航切换行为一起使用 -
.form-inline用于任何表单控件和操作 -
.navbar-text用于添加垂直居中的文本字符串 -
.collapse.navbar-collapse用于通过父断点对navbar内容进行分组和隐藏
在这里展示所有这些项目的示例将成本过高,而受益甚微。与其在这里这样做,不如在本章后面向您展示如何使用 Bootstrap 来构建我们示例应用程序的导航菜单。代码可以在本章末尾的代码列表中找到。接下来的页面中的第一个线框显示了一个标志占位符、一个菜单以及登录和立即尝试按钮。线框代表我们打算构建的页面的草稿。我们的导航栏看起来可能会有所不同,但将包含线框上显示的所有部分。
有关 Bootstrap 的 Navs 和 Navbar 组件的更多文档可以在这里找到:getbootstrap.com/docs/4.0/components/navs/ 和 getbootstrap.com/docs/4.0/components/navbar/。
模态组件
模态组件是吸引用户注意力的好方法,可以用它们来创建灯箱、用户通知等。我喜欢用它们来弹出表单,让用户直接从列出这些项目的页面上添加和编辑项目。这样,所有项目列表的功能(即查看、添加、编辑和删除)都在一个页面上完成。以这种方式使用模态组件会导致直观的清晰设计。
与导航栏组件一样,这里展示示例并不是展示模态框的最佳方式。我将通过代码(在适当的时候引用代码清单)来向你展示我们将如何创建下面线框中显示的模态表单。当你看到线框时,你会发现我在页面中非常慷慨地使用了模态框。我甚至用它们来实现网站的登录和注册功能。
这里有几个关于 Bootstrap 模态组件的演示,你可以在这里查看:getbootstrap.com/docs/4.0/components/modal/
我们只涵盖了 Bootstrap 提供的四个常用组件,但这已经足够让我们一窥预定义组件的功能。还有许多其他可以使用的组件,你可以在官方 Bootstrap 网站上找到它们:getbootstrap.com/docs/4.0/components/
同样,我们没有涵盖所有 Bootstrap 的组件,因为官方文档已经完成了这项工作,并且做得很好。此外,我们将在后面的章节中使用 NG Bootstrap 组件、Angular Material 组件和我们将一起创建的自定义组件。
清单轮播 - 正式介绍
软件项目的演变是一件非常有趣的事情,它遵循一系列非常合乎逻辑的阶段。以下是我们将涵盖的阶段 - 这对于任何软件项目都是真实的:
-
创意生成/概念。
-
分析/可行性研究:对产品概念进行可行性研究的目的是审查项目的投资回报率(即投资回报率)。换句话说,这个项目是否值得公司投入资源(时间、金钱、人才等)?
-
需求收集。
-
使用案例。
-
线框。
-
实施。
有了这些软件项目阶段的概述,让我们来看一个使用清单轮播的真实例子。
创意生成/概念
软件项目的想法可以来自任何地方,任何时间,但在绝大多数情况下,这些想法都是受到解决组织在其生命周期中不可避免地遇到的问题的启发。主要的问题类别有解决低效和通过创建一个比竞争对手更好(即与众不同)的竞争产品在市场上创造机会。换句话说,软件项目通常可以被视为是一个高效的举措或竞争优势的举措。解决这两种类型的问题是每个不断发展的组织在其存在的某个时刻,或者在其整个存在期间都会遇到的需求。
那么,Listing Carousel 是如何构想出来的呢?作为前房地产销售员,转行成为 IT 专业人员,我很容易想到一种更好的方式,让房地产经纪人能够更好地向他们的社交媒体圈子传播他们的新房源,并以比目前其他主要选项更具信息性的方式展示他们的房源。虽然房地产经纪人可以通过多种方式推广他们的房源,但我发现他们缺乏两个基本的东西:
-
他们的房源可以轻松地在他们的社交媒体圈子(即 Instagram 和 Facebook)中传播
-
以一种稍微更具吸引力的方式呈现房产,同时更好地描述房产
所以,我的问题是我必须创建一个与其他软件服务明显不同的软件产品。解决办法是考虑之前列出的两个产品差异化因素,并假设我可以获得所需的技术来实现它。因此,对于 Listing Carousel 来说,可以说这个软件项目是作为竞争优势的举措构想出来的。
太好了!我有一个潜在的软件项目要做!接下来呢?嗯,正如本节开头提到的,下一个阶段是进行可行性研究。回想一下,对产品概念进行可行性研究的目的是审查项目的投资回报率,并进行研究,看所需技术是否已经可获得,或者是否可以创建?让我们简要地看一下接下来的内容。
分析 - 可行性研究
项目分析的这个阶段将决定是否继续进行。进行可行性研究的正确方法是准备一个商业计划,并向投资者展示。为什么?公司经理编写商业计划并向投资者(或公司的副总裁、总裁或首席执行官——对于内部软件项目)展示的原因是因为他们需要一份可以与投资者分享以衡量项目兴趣的文件。如果投资者有兴趣进行投资,那么这意味着该项目具有价值。
这个完美的文件是正式的商业计划,因为它包含了投资者想要看到的所有重要信息的摘要,即:
-
市场分析:市场是否有空间容纳另一个类似产品?市场潜力是多少?
-
竞争分析:我们的产品/服务将如何不同?我们将在成本、质量还是功能上竞争?
-
所需资源:项目需要什么人员?需要多少人时来构建并推向市场?
-
预算:项目总共需要多少资金预算(IT、销售、运营成本等)?
-
财务预测:在接下来的 12 个月、两年、三年和五年内可以预期的收入是多少?何时达到盈亏平衡点?
-
退出策略:我们要经营公司多久?如何收回我们的投资?
你可能会问我是否真的为一个规模相当小的软件项目准备了详细的商业计划。答案是——当然!为什么?简而言之,我需要看看实施该项目是否值得我的时间和金钱。具体来说,我花了必要的时间准备商业计划,原因如下:
-
市场分析:无论你觉得一个想法有多好,你都需要尽职调查,以合理确定市场对你即将进入的产品或服务是否有需求。如果有市场空间,那么你就有潜在的机会。在我的情况下,我相信 Listing Carousel 有市场空间,并且它在竞争激烈的市场中具有足够的差异化,可以给我带来竞争优势。
-
开发成本和时间:时间和金钱都是宝贵的资源——开发软件产品或服务都需要。你在一个项目上投资的每一美元意味着你不能在另一个项目上投资。你的时间也是如此。你在做某事上花费的每个小时意味着你放弃了做其他事情的时间。所以,明智地选择你投入资源的地方!在我的情况下,我有一些钱用于一个有趣的项目——所以金钱部分已经得到解决。时间呢?这对我来说是一个困难的决定。虽然我实际上没有时间,但我喜欢这个项目,我有一些朋友是房地产经纪人——所以我决定,去他妈的,让我们试试吧。所以,我知道我需要投入多少钱,大致需要投入多少时间。
-
预期收入:仅仅因为我要投入的必要资源(即时间和金钱)对我来说是可以接受的,并不意味着一切已成定局。下一步是进行一些计算,看看我是否会随着时间获利,以及能赚多少。如果投资回报率足够高,那就可以继续进行。在我的情况下,投资回报率实际上并不如我希望的那么好,事实上几乎为零!换句话说,我可能只能勉强保本。然而,你也必须听从直觉,而我的直觉告诉我,也许我最终能够出售软件服务,这将使项目变得有价值。在撰写本文时,我尚未出售 Listing Carousel,但它开始有了一点点利润。
-
退出策略:在着手建立任何业务之前——我把 Listing Carousel 视为一个独立的业务——你必须考虑一个退出策略。什么是退出策略?它基本上定义了你如何摆脱经营和/或服务公司的义务。公司不会自己运行,所以除非你想永远与公司结婚,你需要从一开始就有一个退出策略。我无法在这本书中再多花时间详细阐述这一点,但可以说的是,我构建了公司的结构,使我的退出策略早已考虑在内。
需求收集
软件项目的这个阶段构成了项目计划的基础,项目经理使用该计划来确保项目按计划和预算进行。需求通常是从最终客户(内部或外部)那里收集的,但如果您正在构建市场上尚不存在的新产品,需求也可以来自想法板。
例如,对于列表走马灯,我向一些房地产经纪朋友请教,告诉他们我想要构建什么,以及我想要使它与他们已经在使用的东西不同。以下是我们提出的部分需求清单:
-
能够创建走马灯式照片查看器(每个列表/属性一个,其中可以包含任意数量的照片)
-
用户有能力上传照片
-
能够为每张照片做注释(即,在照片底部添加说明)
-
能够翻转照片,显示照片上所示内容的详细描述
-
用户有能力将照片链接到走马灯
-
用户有能力在走马灯中订购/重新订购照片
-
用户有能力在 Facebook 上发布列表的走马灯
-
用户有能力在 Instagram 上发布列表的走马灯
-
用户有能力在他们可以访问的任何网站上手动放置一个魔术链接,以在模态灯塔中打开列表的走马灯
-
在列表的走马灯自动滚动显示照片时播放背景音乐的能力
-
每个走马灯都将被分配一个唯一的短链接,以便用户可以通过电子邮件或短信发送给任何他们喜欢的人
我们不会将所有这些功能都构建到我们的示例应用程序中,因为书中没有足够的空间来这样做,但我们将构建重要的功能。我们将省略的两个功能是魔术链接和播放音乐背景音轨。我不会做任何承诺,但我可能会在不久的将来在我的博客AngularMotion.io上发布有关如何构建魔术链接的博文。
线框图
这个阶段是规划和布局应用程序的外观和感觉的阶段。
以下是我们将要构建的页面的 12 个线框图(注意:有几个线框图太长,无法作为一个屏幕截图,比如欢迎页面,因此它们有多个屏幕截图)。
我们将在接下来的章节中实施其中一些线框图,并学习如何实施一些布局和组件。
实施
这就是关键所在。我们将使用刚刚审查过的线框图编写一些网页代码,以帮助我们进行指导。我们还需要一个网络服务器,这样我们就可以在构建页面时在浏览器中提供我们的新页面。
安装我们的临时网络服务器
我们将在第十二章中使用 Node 的内置网络服务器,集成后端数据服务。但是,由于在达到那一点之前我们还有一段时间,我们需要一个简单的临时解决方案。
我们以前没有讨论过浏览器,因为没有必要这样做,但现在有必要了。虽然使用哪种浏览器查看 Angular 应用程序并不重要,但在我们一起阅读本书时,使用相同的浏览器会更容易,尽管不是必需的。我在开发 Web 应用程序时首选的浏览器是 Chrome。与大多数浏览器一样,Chrome 有许多其他开发人员创建的扩展,可以提供从订阅通知到调试工具等各种功能。您可以从这里下载适合您选择操作系统的 Chrome:www.google.com/chrome/。您可以在 Chrome Web Store 中搜索并安装 Chrome 的扩展:chrome.google.com/webstore/category/extensions。在本书中,我们将使用 Chrome,特别是它的一些扩展,来完成一些任务。
首要任务是安装一个 Chrome 扩展,它将帮助我们为我们的应用程序构建页面。它被称为Web Server for Chrome,您可以在 Chrome Web Store 中搜索并安装它。我没有直接包含链接,因为 URL 非常长。
此扩展允许您选择文件所在的文件夹以及要监听的端口。您还可以选择其他选项。默认情况下启用的一个常见选项是自动显示index.html文件。例如,假设您为端口号输入8887,您将指向http://127.0.0.1:8887,并且您指定的文件夹中的index.html页面将自动在浏览器中提供。一旦您配置了这两个设置,您就可以查看我们创建的页面。
欢迎页面
我们将使用 Bootstrap 组件和网格布局来实现的第一个线框是欢迎首页。
看一下下面的线框截图。我们有一个包含应用程序 logo 占位符、导航菜单以及右侧的登录和立即尝试按钮的页眉部分。然后是一个展示应用程序标题的巨幕页眉。接下来,我们的内容部分被划分,以便我们可以将内容添加到页面上:

我们现在将继续实现上述线框截图的代码实现。我们首先将实现页眉部分,并使用<nav>标签来对所有页眉部分的代码进行分类,包括 logo、菜单和操作按钮:
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span> </button>
<a class="navbar-brand" href="#myPage">Logo</a>
</div>
<div class="collapse navbar-collapse" id="myNavbar">
<ul class="nav navbar-nav mr-auto">
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#about">About</a></li>
</ul>
</div>
<div class="collapse navbar-collapse ">
<ul class="nav navbar-nav navbar-right">
<li><a href="#features">Login</a></li>
<li><a href="#pricing">Try Now</a></li>
</ul>
</div>
</div> </nav>
在上面的代码中,我们正在实现一个nav标签元素,并使用 Bootstrap 导航栏类,navbar-default,navbar-fixed-top,navbar-brand,navbar-collapse等。这些类具有默认功能,几乎涵盖了导航部分的所有方面。在上面的代码中值得注意的一点是navbar-collapse类,它有助于自动呈现各种设备屏幕分辨率。我们还添加了一些菜单链接,如功能、定价和关于。我们还添加了我们的操作项目,登录和立即尝试。
在浏览器中启动页面,我们应该看到如下截图所示的输出:

接下来,我们需要为内容部分制作布局。巨幕和内容部分。我们将使用jumbotron类与div部分,对于内容部分,我们将使用 Bootstrap 网格列类,row,col-sm-8和col-sm-4:
<div class="jumbotron text-center">
<h1>The Smart way to showcase your listings</h1> <p>Simple, beautiful and wonderful app</p> </div> <!-- Container (About Section) -->
<div id="about" class="container-fluid">
<div class="row">
<div class="col-sm-8">
<h2>Annotate your prices</h2><br>
<h4>Some pictures aren't 1000 words and sometimes pictures
don't do something justice</h4><br>
</div>
<div class="col-sm-4">
<span class="glyphicon glyphicon-signal logo"></span>
</div>
</div> </div> <div class="container-fluid bg-grey">
<div class="row">
<div class="col-sm-4">
<span class="glyphicon glyphicon-globe logo slideanim"></span>
</div>
<div class="col-sm-8">
<h2>Our Values</h2><br>
<h4><strong>MISSION:</strong> Our mission lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</h4> <br> <p><strong>VISION:</strong> Our vision Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p> </div> </div> </div>
现在,让我们分析上面的代码,以了解一些重要的要点。我们正在利用强大的 Bootstrap 网格工具来创建我们的应用程序布局,使用列类来创建在各种屏幕分辨率上呈现的布局。在浏览器中运行应用程序,我们应该能看到如下截图所示的输出:

到目前为止,干得不错,伙计们。我们刚刚使用 Bootstrap 布局组件创建了我们的第一个欢迎页面布局。我们将继续使用相同的组件,并构建更多的线框,以使您感到舒适。在下一节中,我们将学习如何使用 Bootstrap 模态组件创建注册和登录界面。
注册
接下来,我们将使用 Bootstrap 的模态组件来实现我们的注册和登录页面。
让我们来看一下下面的线框。这是一个简单的模态窗口,带有一些表单字段输入元素:

让我们继续实现代码。以下是创建模态窗口的示例代码:
<div class="modal fade" id="signup-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display:
none;">
<div class="modal-dialog">
<div class="loginmodal-container">
<h1>Signup Account</h1><br>
<form>
<input type="text" name="firstname" placeholder="Firstname">
<input type="text" name="lastname" placeholder="Last Name">
<input type="text" name="brokrage" placeholder="Brokrage">
<input type="text" name="user" placeholder="Username">
<input type="password" name="pass" placeholder="Password">
<input type="submit" name="login" class="login
loginmodal-submit" value="Sign Up">
</form> <div class="login-help">
<a href="#">Register</a> - <a href="#">Forgot Password</a>
</div>
</div>
</div>
</div>
在上面的代码中,我们使用了 Bootstrap 的模态组件和模态类 modal 和 modal-dialog。在模态对话框内容中,我们使用输入表单元素——名字、姓氏、经纪人、用户和密码创建了注册表单。在浏览器中启动页面,我们应该能看到如下截图所示的输出:

这是构建我们应用程序的一个很好的开端。在下一节中,我们将使用相同的 Bootstrap 模态组件来构建我们的登录页面。
登录
在前一节中,我们已经学习了如何在模态窗口内创建注册表单。在本节中,我们将学习如何在模态窗口内创建登录界面。方法和原则与我们创建注册页面的方式完全相同。
让我们来看一下下面的登录线框,我们马上就要实现它:

是时候行动起来了。我们将首先创建一个模态窗口,然后可以绑定一个点击事件来打开对话框窗口:
<div class="modal fade" id="login-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="display:
none;">
<div class="modal-dialog">
<div class="loginmodal-container">
<h1>Login to Your Account</h1><br>
<form>
<input type="text" name="user" placeholder="Username">
<input type="password" name="pass" placeholder="Password">
<input type="submit" name="login" class="login
loginmodal-submit" value="Login">
</form>
<div class="login-help">
<a href="#">Register</a> - <a href="#">Forgot Password</a>
</div>
</div>
</div> </div>
在上面的代码中,我们实现了一个带有另一个表单的模态窗口,这次是用于登录功能,包括一些表单元素——用户名和密码以及一个提交按钮。在浏览器中启动页面,我们应该能看到以下输出:

我们的应用程序现在几乎成形了。我相信你和我一样兴奋。让我们继续实现列表页面。
列表
在之前的章节中,我们使用 Bootstrap 组件创建了我们的主页、注册和登录页面。在本节中,我们将创建我们的列表页面。看一下以下线框图。我们需要循环遍历我们的列表,并显示一个网格部分,在那里我们将显示到目前为止所有的列表。简单吗?当然:

我们需要使用 Bootstrap 的高级布局和网格组件来创建上述布局。看一下以下示例代码。我们可以以多种方式实现上述布局。我们可以使用 Bootstrap 网格列来设计布局,或者我们可以使用表格元素来设计结构。在这个例子中,我将向您展示如何使用表格元素来做到这一点,网格结构留给您作业:
<div class="container-fluid">
<table class="table table-hover shopping-cart-wrap">
<thead class="text-muted">
<tr>
<th scope="col" width="150">Thumbnail</th>
<th scope="col">Caption</th>
<th scope="col">Property</th>
<th scope="col" width="200" class="text-right">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<figure class="media">
<div class="img-wrap"><img src=
"https://via.placeholder.com/150" class="img-thumbnail img-sm"></div>
</figure> </td>
<td> Master Bedroom </td>
<td> 789 Charelston Rd </td>
<td class="text-right"> <a title="" href="" class="btn btn-outline-success" ata-toggle="tooltip" data-original-title="Save to Wishlist"> <i class="fa fa-heart"></i></a> <a href="" class="btn btn-outline-danger">Remove</a> </td>
</tr>
<tr>
<td> <figure class="media">
<div class="img-wrap"><img src=
"https://via.placeholder.com/150" class="img-thumbnail img-sm"></div>
</figure> </td>
<td> Kitchen </td>
<td> 789 Charelston Rd </td>
<td class="text-right"> <a title="" href="" class="btn btn-outline-success" data-toggle="tooltip" data-original-title="Save to Wishlist"> <i class="fa fa-heart"></i></a> <a href="" class="btn btn-outline-danger btn-round">Remove</a> </td> </tr>
<tr>
<td>
<figure class="media">
<div class="img-wrap"><img src=
"https://via.placeholder.com/150" class="img-thumbnail img-sm"></div>
</figure> </td>
<td> Den </td>
<td> 789 Charelston Rd </td>
<td class="text-right"> <a title="" href="" class="btn btn-outline-success" data-toggle="tooltip" data-original-title="Save to Wishlist"> <i class="fa fa-heart"></i></a> <a href="" class="btn btn-outline-danger btn-round">Remove</a>
</td> </tr> </tbody> </table> </div> <!-- card.// -->
在上面的代码中,我们使用container-fluid类创建了一个容器,在容器内部,我们创建了一个表格和行结构来显示我们的列表。在更实际的情况下,数据将始终来自后端 API 或服务。对于我们的示例和学习目的,我们在这里存根化了数据。在浏览器中启动页面,我们应该看到如下截图所示的输出:

如果您看到前面截图中显示的输出,请给自己一个鼓励。我们在学习中取得了很大的进步。到目前为止,我们已经使用各种不同的 Bootstrap 组件和网格布局创建了四个页面。
在下一节中,我们将探索应用程序的一些其他线框图,我会留给你练习。大多数线框图将使用相同的组件、布局和网格布局。
创建列表
在本节中,我将与您分享创建列表页面的线框图。可以使用 Bootstrap 组件和布局轻松创建创建列表页面。相反,我们将在下一章中学习如何使用 Flex-layout 来实现这一点。以下是您参考的线框图:

在下一节中,我们将看到编辑列表页面的设计和线框图细节。
编辑列表
在本节中,我们将学习编辑列表屏幕的设计和线框图。如果您仔细观察,编辑列表页面与创建列表页面类似,只是数据在加载时被填充。
与创建列表屏幕一样,我们将在下一章中使用 Flex-layout 设计编辑列表页面。

线框图集合
在本节中,我们将看到其他页面的设计线框图,这些页面将在接下来的章节中创建。
以下是列表预览页面的设计线框图:

以下是物业详情的设计线框图。如果您注意到,我们将使用相同的 Bootstrap 模态窗口组件。当我们打开模态窗口时,我们应该看到物业详情:

现在,我们将学习如何为照片页面设计线框图。如果您仔细观察,布局结构看起来与列表页面相似。我们将不得不使用常见库创建可重用的设计,这些设计可以在各种页面和模板中重复使用:

接下来是上传照片页面。我们将再次创建一个模态窗口组件,并通过它提供文件上传选项,以便我们可以轻松上传照片:

现在,让我们继续编辑照片线框图。我们再次利用 Bootstrap 的模态窗口组件来设计我们的编辑照片页面。我们将使用 Angular 的数据绑定来绑定模态窗口中的数据:

最后,我们将探索照片预览页面。我们可以使用模态窗口 Bootstrap 组件显示照片。我们将关闭常见的操作按钮以关闭或编辑模态窗口:

在本章中,我们做了相当多的工作,学习了 Bootstrap 网格和布局组件。作为实际学习示例的一部分,我们创建了一些页面并设计了我们将在应用程序中使用的线框图。
总结
这一章充满了各种好东西。你现在应该了解我们将要构建的示例应用程序,我们构建阶段的高层游戏计划,以及我们将采取的第一阶段构建的五个步骤过程。
然后,我们讨论了 Sass 是什么,以及它的一些功能如何帮助我们创建应用程序的 CSS。我们将研究一些工具,帮助你在编写应用程序的 Sass 时。接下来,我们了解了 Bootstrap 是什么,以及如何将其集成到你的应用程序中。我们学习了 Bootstrap 的网格是什么,以及如何使用它的基础知识,以及一些 Bootstrap 的组件以及如何使用它们。
最后,我们研究了软件项目的演变,从构思到实施。在这里,我们涵盖了不同类型的分析,需求的收集以及一些用例。我们还涵盖了线框图,详细介绍了每个线框图的目标,以及基本的设计原则(在描述线框图时提到)。
到目前为止,在本书中,除了我们在第一章中构建的快速待办事项应用程序外,我们甚至还没有接触过 Angular。这将会改变——从下一章开始,第四章,路由。在这一章中,我们将使用 CLI 创建应用程序的 Angular 外壳(就像我们在书的开头所做的那样)。但是,我们将为其添加路由。我将在我们逐步进行的过程中解释什么是路由,以及如何为我们的应用程序配置路由。
所以,在你翻页之前,给自己一个鼓励,伸展一下,也许倒一杯你最喜欢的饮料。干得好,我的 Angular 初学者同伴们。通过了这前三章,你现在已经准备好迎接 Angular 了!
67

被折叠的 条评论
为什么被折叠?



