JavaScript 入门指南(二)
八、JavaScript 和调试
到目前为止,我已经讨论了如何安装软件以及使用 JavaScript 时需要知道的一些重要事情。在很大程度上,JavaScript 是您在浏览器中使用的东西。
既然如此,如果你写的代码有效,结果就会显示在浏览器中。更重要的是,如果结果不工作,你需要一种方法来看看哪里出错了。
问题就变成了,“我如何在浏览器中看到我的代码发生了什么?”令人高兴的是,浏览器内置了调试工具。本章将介绍一些在浏览器中调试代码时可以使用的工具。
火狐有专门针对开发者的浏览器,火狐开发者版( www.mozilla.org/en-US/firefox/developer/ )有工具,是那个浏览器特有的。
在撰写本文时,谷歌的 Chrome 浏览器仍然是最受欢迎的。因此,我将使用该浏览器中的开发工具作为参考。
开发人员工具由不同的面板组成。虽然我不会详细讨论每个面板,但是有些面板是针对特定任务的。这些面板包括
-
设备模式
-
元素面板
-
控制盘
-
来源面板
-
网络面板
-
性能面板
-
内存面板
-
应用面板
-
安全面板
控制台面板
在前面的章节中,您使用了控制台面板。这允许您直接在浏览器中执行 JavaScript 并返回结果。您还可以调用加载到浏览器中的 JavaScript 文件中定义的函数。
因为您过去曾经使用过控制台,所以这是一个很好的起点。要打开浏览器控制台,右键单击页面并选择检查选项。这将打开开发人员工具。如果要使用键盘命令,请选择 Command + Option + J(在 Mac 上)或 Control + Shift + J(在 Windows 上)。见图 8-1 。
图 8-1
显示控制台面板
控制台有自己的 API(应用编程接口)。控制台的目的是让您了解正在执行的代码。
到目前为止,您只是使用了console.log方法让浏览器为您打印出结果。这很有帮助,但不是使用控制台帮助您了解浏览器中发生的事情的唯一方式。
其中一些函数是不言自明的。比如console.clear()会清除控制台里的一切。
使用clear方法的唯一例外是保存日志设置被取消。这将在页面刷新之间保留您在控制台中所做工作的历史记录。你可以通过点击开发者工具窗口右边的图标来找到它。见图 8-2 。
图 8-2
显示控制台设置
console 对象有几个方法,这些方法有助于理解代码在执行时发生了什么。例如,count方法跟踪同一个源调用一个函数的次数。在这种情况下,单击按钮是一样的。见清单 8-1 。
8-1.html
<button id="myButton">Click Me</ button >
8-1.js
function myCount(evt){
console.count(evt);
}
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#myButton").addEventListner("click", myCount);
});
//result [object MouseEvent]: 1..2..3
Listing 8-1Using the count Method as Part of the console API Class
这个例子等待DOMContentLoaded事件发生。然后,它查看文档,通过 ID 找到按钮,并为其分配一个事件侦听器。
当按钮被点击时,count方法跟踪与按钮点击相关的函数被调用的次数。所以,不用在代码中创建变量,你可以看到这个函数被调用了一定的次数。
dir方法获取一个对象并在控制台中打印出来。在这里,您可以单击查看为对象显示的所有属性和方法,包括任何子属性和方法。
清单 8-2 从清单 8-1 中提取代码,并使用dir方法来实现不同的结果。
8-2.html
<button id="myButton">Click Me</ button >
8-2.js
function myCount(evt){
console.dir(evt);
}
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#myButton").addEventListner("click", myCount);
});
Listing 8-2Using the dir Method as Part of the console API Class
这段代码让您可以查看MouseEvent附带的所有属性和方法。这类似于使用console.log,在这里您可以看到您正在处理的对象的所有属性和方法。见图 8-3 。
图 8-3
显示 MouseEvent 对象的方法和属性
一个类似于调用console.log的方法是dirxml。该方法返回一个对象,但它不是将所有属性和方法显示为一个 JavaScript 对象,而是将其显示为一个基于标记的 XML 文档。
清单 8-3 显示了dir和dirxml方法。在本例中,函数myCount首先显示dirdxml的结果,然后显示dir的结果。参见图 8-4 。
图 8-4
从控制台 API 显示 dir 和 dirxml 方法的结果
8-3.html
<button id="myButton">Click Me</ button >
8-3.js
function myCount(evt){
console.dirxml(document);
console.dir(document);
}
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#myButton").addEventListner("click", myCount);
});
Listing 8-3Using Both the dir and dirxml Methods
图 8-4 显示第一级结果以 HTML/XML 格式显示文档对象。你可以看到所有标签的层次结构,并点击它们。
第二层显示了文档对象的所有方法和属性。在这种情况下,它更像是在查看一个 JavaScript 对象,这比第一个例子更难理解层次结构。
有时您可能有一些事情想要记录,但是仅仅使用log方法意味着您需要在控制台中滚动结果来找到它们。这就是你可以结合使用group和groupEnd方法的地方。您可以将日志的结果组合在一起。需要记住的是,要结束这个组,需要调用groupEnd方法。
清单 8-4 展示了如何获取一个对象的属性并将它们组合在一起。这为您在控制台中查看结果提供了更多的上下文。
8-4.js
let insocAlbums = {'first': 'Information Socieity', 'second'': 'Hack', 'thrid': 'Peace and Love Inc.'};
function groupBand(albums){
console.group("Album List");
console.log('first:' , albums.first);
console.log('second:' , albums.second);
console.log('thrid:' , albums.third);
console.groupEnd();
}
document.addEventListener("DOMContentLoaded", () => {
groupBand(insocAlbums);
});
Listing 8-4Using the group and groupEnd Methods to Group the Results of the log Method
在本例中,您有一个名为insocAlbums的对象。您希望看到该对象的属性组合在一起。下一个函数groupBand查看传递过来的对象的单个属性,并在浏览器控制台中打印出来。
这里重要的区别在于,您用一个标题显式地将这些项目分组,并指定该组的结尾。图 8-5 显示了结果。
图 8-5
使用控制台 API 的 group 和 groupEnd 方法的结果
现在,您可以从分组在一起的对象中看到您感兴趣的所有项目。您可以单击组标题并展开列表。
我将在本节中介绍的最后一种方法是table方法。顾名思义,它在控制台上一个漂亮的表格中显示结果。让我们以最后一个例子为例,将其显示为表格。参见清单 8-5 。
8-5.js
let insocAlbums = {'first': 'Information Socieity', 'second'': 'Hack', 'thrid': 'Peace and Love Inc.'};
function groupBand(albums){
console.table(albums);
}
document.addEventListener("DOMContentLoaded", () => {
groupBand(insocAlbums);
});
Listing 8-5The table Method
方法在一个格式良好的表格中显示一个对象的所有属性和方法。图 8-6 显示了一个例子。
图 8-6
使用控制台 API 的表方法的结果
您可以看到结果与使用group方法得到的结果非常相似。其中一个好处是,你不需要做任何工作来实现这一点。
这是您可以通过控制台面板使用的一些方法的概述。在很大程度上,这些方法帮助您查看正在使用的对象的值。
有时,您可能希望更好地了解代码在浏览器中是如何执行的,以及代码执行时变量的值是如何变化的。控制台面板很有帮助,但是还有一个面板可以提供更多的细节。
源代码面板可以帮助你做一些事情,比如暂停程序,让你在代码运行时看到代码发生了什么。让我们看看源代码面板。
来源面板
“源代码”面板有三个部分。首先是*文件导航器。*它可以让你检查属于你的网站的任何文件。
一旦从文件导航器中选择了一个文件,您就可以使用代码编辑器来查看该文件的内容,并做出一些将实时反映在浏览器中的更改。
第三个面板是 JavaScript 调试面板。这个面板允许你做一些事情,比如观察一段时间内变量的值。当程序执行时,您还可以看到当前范围内所有变量的值。
在下一个示例中,您将使用与之前类似的东西。基本格式是一样的。会有一个按钮可以点击,但不同的是这个按钮会调用一个 web 服务并返回一些数据。在这里,您可以使用“源代码”面板来了解浏览器是如何执行代码的。
首先,你需要理解代码。看一下清单 8-6 。
8-6.js
let jsonResults;
function getData(){
fetch('https://jsonplaceholder.typicoce.com/todos/)
.then(response => response.json())
.then(json => saveData(json)));
}
functdion saveData(json){
jsonResults = json;
console.log(jsonResults);
}
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#myButton").addEventListner("click", getData);
});
Listing 8-6Using the fetch method to return data from a webservice
在本例中,您正在以不同的方式做一些小事情。点击按钮后,调用getData功能。这个函数调用fetch方法。
fetch方法调用一个远程服务,在您的例子中,这个服务返回一些 JSON 数据。
当结果被返回到浏览器时,你做一些叫做方法链接的事情。这是从一个方法直接调用另一个方法的能力。如果您使用过 JQuery,这是一种常见的做法。
方法返回一个叫做承诺的东西。承诺给了你等待价值的能力。
在这种情况下,因为您正在调用远程服务,所以结果可能需要一些时间来返回值。一个承诺会让你知道数据什么时候被返回给浏览器。
当结果返回时,它可以链接到一个then方法,该方法将告诉浏览器运行一个函数,该函数将结果解析为应用可以使用的 JSON 数据。然后链接另一个函数,将数据传递给另一个名为saveData的函数。
面试问题
什么是承诺,它是如何运作的?什么是方法链?
saveData函数获取传递给它的 JSON 并保存到变量jsonResults 。
您可以使用“源代码”面板中的一些选项来检查这是如何处理的。一种方法是设置所谓的断点。断点停止代码的执行;这使得调试环境能够及时地向您提供关于代码当时正在发生什么的信息。
如果浏览器是展开的,左侧应该有 JavaScript 调试工具。在称为事件侦听器断点的部分,您可以查看鼠标事件。
通过单击箭头,可以展开列表,详细显示可以分配给断点的鼠标事件的类型。参见图 8-7 。
图 8-7
JavaScript 调试窗格
通过选择点击断点,点击按钮将在getData函数的第一行暂停程序的执行。此时,浏览器正在等待您使用调试工具来告诉它下一步应该做什么。
调试工具可以通知您到这个断点为止发生的所有事情。查看 Scope 面板,您可以看到变量this的值。
设置断点的另一种方法是直接在代码中设置。使用当前的例子,如果您想在函数执行的中途知道一个变量的值,您可以点击saveData函数。通过这样做,您可以看到类似于jsonResults变量的值。
如果不希望有两个断点,请确保关闭 MouseEvent 断点。
在函数内部设置断点后,请确保刷新浏览器,然后单击按钮。程序将再次停止,这一次正好是断点被设置的地方。查看范围面板,您现在可以看到属性this的当前值现在是window 。
作用域面板上方是调用堆栈面板。该面板显示所有执行功能的顺序。从下往上,您会看到首先调用的是getData函数,然后移动到当前的saveData函数。
面板的最顶端是手表面板。它允许您添加任何有效的 JavaScript 表达式,包括变量,并观察其值随时间的变化。
与设置断点不同,在设置断点的情况下,您只能在特定时刻获取值,而 Watch 允许您查看值,即使它们发生了变化。打开这个面板,点击加号按钮,输入"jsonResults" 。
在应用的第 11 行的console.log方法处添加一个断点。您的应用目前停在第 10 行,这是您的原始断点的位置。您需要一种方法来移动到下一个断点,这样您就可以看到jsonResults的值发生了变化。这是介绍使用调试器来遍历代码的能力的好时机。见图 8-8 。
图 8-8
deveoper 工具使您能够浏览代码,让您选择是浏览一个函数还是转到下一个函数
这些工具从左到右展示了在设置断点时如何浏览代码。这些工具位于您一直使用的所有面板的顶部。
第一个图标允许您继续执行脚本。它只是继续执行需要执行的下一部分代码。例如,当使用断点时,第一个按钮允许您继续或暂停代码的执行。
在当前示例中,如果您想要移动到下一个断点,您可以单击此按钮,它会将您带到下一行代码。浏览器将在下一个断点处停止,并显示jsonResults的更新值。
如果您的函数更复杂,您可能希望浏览该函数,以便更好地理解该函数的工作内容。下一个示例将使用调试面板中的一些其他工具来帮助您逐步完成更复杂的函数。
你需要做的第一件事是使你的函数更加复杂。下一个例子将从jsonResults变量中获取值,并使用内置的map函数遍历它。
map函数将使您能够按顺序遍历数组中的每一项,并评估每一个值。它还会告诉你该项目的当前索引。它还会在每次迭代中返回整个数组。
让我们来看看这个。如果您下载了这本书的代码,请打开清单 8-7。否则,遵循图 8-9 。
图 8-9
单击调试面板中的“单步执行”按钮,可以将您从映射函数移动到 console.log 函数
在遍历函数体时,使用console.log方法输出已经返回的值。您还可以使用这个方法来输出一个名为checkIndex的函数的结果。
checkIndex函数查看当前索引,看它是否能被 2 整除,并返回 true 或 false 值。您将使用这个函数来完成调试工具。
如前所述,第一个按钮恢复脚本,让它不间断地运行。“下一步”按钮允许您单步执行下一个函数调用。
如果打开了源代码面板,请在第 10 行设置一个断点。这将停止应用,您可以开始看到这到底意味着什么。
当应用正在执行时,如果你点击按钮,所有的东西都应该停在第 10 行。如果您单击 Step Over 按钮,它会将您带到函数中的下一行,在这里您开始使用map方法遍历数组中的所有项。如果你再次点击它,它将跳过map功能,进入列表中的下一个功能,即console.log功能。
如果您再次单击“跳过”按钮,指针将回到第 10 行。在下一个示例中,您将使用“单步执行”按钮来检查函数内部发生了什么。
如果通过刷新页面然后单击按钮来重新开始,调试器将再次停止在第 10 行。
现在,如果您单击“单步执行”按钮,您仍然会移动到下一个函数,在这里您将开始遍历数组中的所有项。
如果您第二次点击该按钮,而不是移动到下一个函数,您现在将进入当前函数的详细信息,并开始查看在map函数中正在处理的值。参见图 8-10
图 8-10
单击“调试”面板中的“单步执行”按钮可以进入映射功能
继续使用该按钮时,向下移动功能中的项目列表,直到到达下一个console.log功能。此时使用“步入”按钮会将您带到checkIndex功能。
到目前为止,您已经逐步完成了saveData功能中的每一项。然而,最后一行执行了一个新的函数,让您可以逐步执行。
您现在开始使用相同的工具逐行遍历checkIndex函数,检查这个函数是如何执行的。
列表中的下一个按钮与“步入”按钮的作用相反。它跳出当前函数。
在这个例子中,如果你正在单步执行checkIndex函数并选择退出,你将被带回到最初调用checkIndex函数的saveData函数。
列表中的下一个按钮是“步进”按钮。这个按钮可以让你一行一行地执行你的函数,并执行所有编写的东西。
最后两个按钮允许您停用或重新激活应用中的所有断点,并在出现异常时暂停。JavaScript 中的异常发生在错误发生的时候。
摘要
本章探讨了一些可以用来调试网站的工具。这里的重点是 JavaScript,但是您也可以检查和编辑 CSS 和 HTML,并且您可以模拟不同的设备来测试您的站点的响应能力。
其他工具包括网络监视器,用于查看代码从服务器发出需要多长时间,或者发出请求和获得响应需要多长时间。“性能”面板让您看到代码执行的速度,而“内存”面板确保您的代码以有效的方式使用浏览器的内存。
所有这些工具都在客户端。您一直在使用浏览器来帮助您理解 JavaScript 如何工作以及如何调试它。
随着项目变得越来越复杂,您可能会开始添加库或框架来帮助组织应用并添加您需要的功能。下一章将介绍如何作为客户端开发人员利用 NodeJS。
九、JavaScript 和客户端开发
在前面的章节中,您安装了 NodeJS 并学习了如何运行本地服务器。之后,您花了大量时间使用浏览器并探索 JavaScript 在其中的工作方式。在这一章中,我将介绍由 NodeJS 支持的工具,它们将增强客户端开发。
即使您决定不在服务器端工作,NodeJS 也是您应该知道的。它已经成为任何使用 JavaScript 的开发者的生态系统的一部分。像节点包管理器(NPM)这样的工具已经成为前端开发人员生活中重要的一部分。
本章将详细介绍 NodeJS(通常称为 Node)是什么,以及作为前端开发人员如何利用它。
node.js 到底是什么
为了理解当前的 JavaScript 框架如何能够执行像创建文件和文件夹这样的操作,重要的是后退一步,讨论 NodeJS 到底是什么,以及为什么它是 JavaScript 开发人员工具箱的重要组成部分。
关于节点的讨论将分为两部分。第一部分,我将在本章中介绍,将展示节点对于客户端开发的重要性。
下一章将介绍如何使用 JavaScript 和服务器端框架(如 Express)来组装 web 服务器。
客户端的节点
安装 Node 后,您就可以使用 Angular、React 或 Vue 等当前框架了。包是可以在任何项目中使用的 JavaScript 代码库。它们是通过一个叫做 NPM 的系统分发的。
NPM 有一个可供开发者使用的 JavaScript 库数据库。如果您想在项目中添加某种类型的功能,可以使用命令行并将其添加到项目中。
要使用 NPM,首先需要安装节点。如果您尚未安装 Node,请转到 NodeJS.org 并安装最新的稳定版本。
我将涉及的一些内容与前几章相似,但作为一个提醒,还是值得一读的。
当您安装了 Node 后,使用 Node 的一种常见方式是使用命令行工具。如果您运行的是 Windows,有一个名为 Cmder 的控制台模拟器。这个工具可以在 cmder.net 下载。安装了它和 Git(可以在 https://git-scm.com/ 找到)你就可以使用所有的命令行选项,界面就像使用 Unix 或 MacOS 一样。
如果您运行的是 Windows 并且已经安装了 Git,那么您也可以使用 Git bash 作为替代。
在 Mac 或 Linux 端,可以使用内置的终端窗口。如果你用的是 MacOS,有一个叫 iTerm 的应用,如果你不想用内置的终端可以用。
安装了 Node 后,您可以使用 JavaScript 来完成在浏览器中无法完成的任务。例如,您可以访问文件系统;您还可以执行网络和数据功能。
对于前端开发人员,您可以使用 NPM 作为一种快速组装应用的方式。使用 NPM 时,需要创建一个名为package.json的文件;该文件跟踪您在项目中使用的所有库和相应的版本号。
可以发布您的包,以便其他人可以使用它。在你的情况下,你不会这样做,但知道你有什么选择是很好的。
在项目中使用 package.json
要在命令行创建这个文件,请键入npm init。这将启动该过程,并询问您有关您将要创建的项目的问题。您不打算发布此包;但是,命令行界面会像您一样向您提问。这就是为什么理解它试图做什么很重要。
您必须回答的第一个问题是项目的名称。当你给你的包起一个名字的时候,这里有一些事情需要记住(你可以在 https://docs.npmjs.com/files/package.json 找到所有的细节):
-
包名不能超过 214 个字符。
-
名字不能以点(。)或下划线(_)。
-
新包不能有大写字母。
如果您不打算发布包,这并不重要。如果你打算发布,为了更好地了解如何命名你的包,看看 www.npmjs.org 的注册表。
下一个问题是版本号。发布时需要名称和版本号。如果您对软件包进行了更改,那么您应该对版本号进行更改。
所有版本号都应该能够被 node-semver 解析。Semver,或语义版本化,是一种帮助跟踪项目版本的标准。以下是一些规则:
-
当更改与以前的更改不兼容时,使用主版本号。
-
当添加向后兼容的功能时,使用次要编号。
-
添加向后兼容的错误修复时使用补丁号。
使用这种格式,所有版本号看起来都类似于 1.0.0(主版本号、次版本号和补丁号)。
若要继续,请按回车键;这将为您留下默认版本。
接下来,应用将询问项目的描述。这有助于人们在搜索时发现该包。您可以按下 Return 键继续。
入口点是当您使用import或require关键字时将会用到的文件。使用这两个关键字中的任何一个都会将该库的功能添加到您的项目中。默认是index.js。
在 test command 选项中,您可以添加命令来对应用运行任何类型的测试。这可能包括单元测试和集成测试。例如,您可以在持续集成环境中键入npm run tests,并确保在将代码发送到服务器上运行之前通过应用测试。
面试问题
单元测试和集成测试的区别是什么?回答:单元测试检查一小段代码,以确保它做了它应该做的事情。集成测试检查这些单元是否能一起工作。
添加 Git 存储库有助于人们了解项目的位置。如果您对项目进行开源并希望人们做出贡献,这也很有帮助。
如果您计划公开项目,关键字会很有帮助。当有人使用“npm 搜索”时,它们会有所帮助关键字是字符串数组。在您的案例中,这是一个本地项目。您可以按下 Return 键并移动到下一个项目。
这种情况下的作者是你。如果您想要添加贡献者,请使用包含其他人的数组。这个数组可以包含名称、电子邮件和 URL。输入您的姓名,然后按回车键,或者直接按回车键。
如果这是一个公开的项目,你可以把它放在像麻省理工学院或知识共享这样的开源许可下。许可证的完整列表可在 https://spdx.org/licenses/ 找到;在你的情况下,它不是开放的,所以你可以继续前进。
有了这些问题的答案,您就可以预览一下package.json文件将会是什么样子。你需要做的最后一件事就是批准它。
NPM 已经创建了一个文件,你可以用它来跟踪将成为你的应用的一部分的库。它的核心只是一个 JSON 文件,但是在你要使用它的上下文中,它不仅知道你正在使用什么库,而且还跟踪你未来的应用工作所需的版本和任何依赖库。
下一节将向您展示如何将包添加到项目中。
向 package.json 添加库
现在您有了一个package.json文件来跟踪您想要使用的库以及这些库在您的项目中的版本。使用这个文件,您还可以运行本地服务器来查看应用的工作情况。您还可以运行脚本来帮助您进行站点的开发、测试和生产。
向项目中添加库时,您可以返回命令行并直接请求它。NPM 将找到 Git 库并将其添加到您的项目中。对于这个例子,让我们将 jQuery 添加到您的项目中,并探索如何使用它。
回到命令行,你应该和package.json文件在同一个文件夹中;输入npm install jquery。
这段代码找到装有最新版本 jQuery 的 Git 存储库,并将其下载到您的机器上。它还在该文件夹中创建一个名为node_modules的文件夹,这是一个包含您刚才请求的库的jquery文件夹。
该过程创建的最后一个文件是一个package-lock.json文件。该文件有助于确保每次都安装了您正在使用的所有库的准确版本。
面试问题
你应该投入到你的项目中吗?回答:是的。这将确保团队中的每个人都安装了项目中使用的所有库的相同版本。
到目前为止,您已经创建了一个package.json文件,并向其中添加了一个库。此时,你还没有一个完整的工作网站。接下来的几个例子将展示如何使用 NPM 来组织一个项目。
在前面的一章中,您添加了一个名为http-server的包;这为您创建了一个将当前文件夹用作 web 服务器的零配置方式。
让我们将这个包添加到您的项目中。在命令行中,确保您位于项目的根目录,并通过键入"npm install --save http-server"来安装http-server。
安装了这个库之后,您可以更新您的脚本,这样您就可以让package.json告诉服务器它应该何时运行。因为这只是一个 JSON 文件,所以您可以在任何编辑器中打开它,并对其进行一些修改。
在任何编辑器中打开文件。要更新脚本部分,您需要添加一个脚本来启动服务器。在 scripts 部分下,添加一个名为start的脚本。清单 9-1 有代码。
"scripts":{
"test":"echo\ "Error: no test specified\" && exit 1",
"start": "http-server"
}
Listing 9-1Updating the package.json File so the Start Script Will Run a Local Server
现在,当在命令行时,你只需要键入npm start,这将启动本地 web 服务器。要停止服务器运行,可以键入 Control + C。
您已经使用 NPM 下载了两个库,但是您还没有任何 HTML 文件可以显示。
要解决这个问题,您需要返回命令行并创建一个文件。键入touch index.html,这将创建一个名为index.html的空白文件。
新创建的 HTML 文件中没有任何内容。你可以通过使用一个名为 htmlshell.com 的网站生成的代码来解决这个问题。在默认设置下,它会给你一个 HTML 站点的基本结构,现在这就是你所需要的。
将 HTML 添加到索引文件后,您可以再次启动项目,并查看本地 web 服务器加载的新创建的 HTML 页面。
要做到这一点,在命令行输入npm start,这将再次启动服务器。
打开浏览器,输入127.0.0.1:8080。这将告诉浏览器查看本地机器(8080 是默认端口号;其他项目可能使用不同的号码在本地机器上提供网页)。
当前页面为空白,如图 9-1 所示。如果您愿意,您可以在文档正文中键入内容,以确认该页面已被提供。
图 9-1
显示来自本地服务器的新 HTML 页面
现在有了启动本地服务器并为您提供 HTML 页面的package.json文件。您需要将 JavaScript 添加到项目中。如果您仍然打开命令行工具,创建一个scripts文件夹来添加一些 JavaScript 到项目中。
在命令行,停止服务器,键入mkdir scripts;这将创建一个文件夹,您可以在其中添加 JavaScript。现在,您可以进入该文件夹并创建新文件。
键入cd scripts更改目录,将您放入scripts文件夹。一旦进入,使用相同的touch命令并创建一个新的 JavaScript 文件。
在scripts文件夹中输入touch app.js。这将创建一个新的空白文件,您可以在其中添加 JavaScript 命令。
在开始编写 JavaScript 之前,您需要确保 HTML 页面知道有 JavaScript 可以使用。
在你的代码编辑器中,打开 HTML 页面(如果它还没有打开的话),添加一个指向scripts文件夹和你新创建的app.js文件的script标签。代码应该如清单 9-2 所示。
/* index.hml*/
<body>
<script src="scripts/app.js"></script>
</body>
//app.js
document.addEventListener('DOMContentLoaded', () => {
console.log('Document Loaded');
});
Listing 9-2Connecting Your HTML Page to the JavaScript File
您在结束的body标签之前添加了script标签,以便在浏览器开始呈现任何 JavaScript 之前呈现页面的其余部分。
准备好之后,添加一些 JavaScript 进行检查,确保您知道您的本地服务器正在工作。您可能需要刷新浏览器或再次启动服务器才能看到此工作。但是,当您打开开发人员工具并查看控制台面板时,您应该会在控制台中看到消息“Document Loaded”。
这段代码查看document对象并监听一个名为DOMContentLoaded的事件监听器。当该事件发生时,会触发一个匿名函数,向您提供一条消息。
到目前为止,您已经有了一个正在运行的本地服务器和 JavaScript。但是,您还不能将 jQuery 库连接到您的项目。
为了让它工作,您需要添加一个模块构建器。下一节将解释这是如何工作的,以及为什么它很重要。
模块捆扎机介绍(网络包)
到目前为止,您已经使用命令行做了很多工作。您创建了一个package.json文件,帮助您为将来的项目加载多个库。您还将添加到该文件的脚本部分,以便以后可以使用命令行来运行本地文件服务器。这种能力让你知道你发布的网站会是什么样子。
到目前为止,您还没有使用之前安装在项目中的 jQuery 库。如果您还记得,在安装 jQuery 时,它创建了一个名为node_modules的文件夹,其中包含您的库。
浏览器理解模块的概念。模块是拥有一些 JavaScript 代码的能力,这些代码将提供一些特定的功能。这个单独的代码可以导入到更大的项目中。
随着您的项目开始变得越来越复杂,您将需要一些不同的模块来使一切正常工作。如果能添加这些模块,而不是为您想要使用的每个模块在文档中添加一长串的script标签,那就太好了。
Webpack 是一个工具,您可以使用它来帮助导入您在项目中使用的任何库,并确保代码与一些旧的浏览器兼容。
要让 Webpack 工作,您需要在命令行上花更多的时间。如果您当前正在运行应用,请停止运行它,并安装 Webpack 应用。
在命令行中,键入
npm install webpack webpack-cli –-save-dev
这将把 Webpack 库添加到您的项目中。额外的标志-–save-dev用于将库添加到您的项目中,但不是在您开发前端代码时使用的。
当查看package.json文件时,您可以看到这个库已经被添加到一个名为 devDependencies 的新部分。这一节只针对那些帮助你使用应用的实用程序,而不是帮助你开发应用。在这一节中,可以结束的一些东西是帮助您运行单元测试或其他开发工具的库。
现在您已经将 Webpack 作为开发工具的一部分,您将使用它来创建您的应用。
返回到您的package.json文件,向脚本部分添加一个新选项。创建一个名为dev的脚本,并赋予它webpack的值。
它应该是这样的:
"dev":"webpack"
编辑该文件后,在命令行中键入
npm run dev
这将抛出一个错误,如图 9-2 所示。
图 9-2
将 Webpack 添加到项目后运行脚本
有时得到一个错误是令人失望的,但在这种情况下,它是好的。你知道 Webpack 已经安装,它只是需要进一步的指导如何工作属性。
没有足够多的人做的事情之一是阅读错误消息。在尝试解决问题时,理解问题非常重要。
这个错误告诉你,你需要添加更多的信息到你的脚本中。现在,它默认为生产模式,这不是你现在需要的。
返回到dev脚本并更新它:
"dev":"webpack --mode development"
Webpack 有一些缺省值,您需要遵守这些缺省值才能让您的示例正常工作。如果再次运行该脚本,仍然会出现错误。
上一个示例中的文件和文件夹的结构不符合 Webpack 需要的默认结构,因此您需要调整您的文件以使用 Webpack。
Webpack 希望index.js文件位于src(源)文件夹中。如果您使用的是前面例子中的代码,将scripts文件夹重命名为src,将app.js文件重命名为index.js。
如果您再次运行该脚本,您应该不会遇到同样的错误。
当脚本执行完毕时,它会创建一个dist文件夹。这是存放所有编译代码的文件夹。
当使用像 Webpack 这样的工具时,所有被处理的代码都在dist文件夹中结束。除了 JavaScript 文件之外,这还包括 HTML 文件和 CSS 文件。
当刷新浏览器时,HTML 文件不识别在dist文件夹中发生的事情,因为script标签没有指向该文件夹中的 JavaScript。
您需要更新 HTML 文件以指向dist文件夹中的 JavaScript 文件。
使用 Webpack 的一个好处是,您不需要在 HTML 文档中为每个要使用的库设置多个脚本标记。
回到 HTML 页面,更新script标签,指向dist文件夹和其中包含的main.js文件。
只是为了确保一切都按您期望的方式运行,通过返回命令行并再次键入npm run dev来重新编译应用。
您在package.json文件中编写的第一个脚本是start脚本。当运行这个脚本时,你可以使用当前文件夹作为 web 服务器和服务器的index.html文件。
为了使本练习生效,您需要打开终端窗口的两个实例,一个用于查看服务器运行,另一个用于使用 Webpack。
在一个窗口中,运行名为npm start的脚本。它启动服务器,并告诉您将哪个本地地址放入您的浏览器,以查看网站的运行情况。
在另一个窗口中,您可以继续更新 Webpack 脚本。
此时,您应该能够看到更新后的脚本在本地 web 服务器上运行。在浏览器的开发工具打开的情况下,您仍然会在控制台面板中看到相同的“Document Loaded”消息。
所有这些设置现在都让您能够利用 NPM。
在本课一开始,您将 jQuery 加载到了您的package.json文件中。现在,您可以将该库添加到项目中,而无需将其添加到 HTML 页面中。
在index.js文件中,在顶部添加这一行:
import $ from 'jquery'
然后在当前代码的上方添加一些代码行,确保 jQuery 正常工作。完成的代码看起来应该如清单 9-3 所示。
import $ from 'jquery';
$(document).ready(()=> {
console.log('hello from jquery');
});
document.addEventListener('DOMContentListener', ()=>{
console.log('Document Loaded');
});
Listing 9-3Importing jQuery into Your JavaScript Project
再次运行npm run dev命令,然后刷新浏览器。查看控制台面板,您应该会看到两条消息。
如果您正在使用 Chrome,并且在重新编译应用后没有看到这两条消息,请单击并按住刷新按钮,然后选择清空缓存和硬重新加载选项。只要开发工具是打开的,这应该会给你想要的结果。
您的应用现在可以利用其他库,而无需添加到 HTML 文件中。您可以将它们直接导入到当前文档中,并使用 Webpack 之类的工具将它们打包到 JavaScript 文件中。
你可以把你的 HTML 文件和dist文件夹的内容上传到一个实时的网络服务器,它应该完全一样。
然而,对于发展来说,你有一些迫在眉睫的问题。首先,应用不知道何时应该重新编译代码。如果您进行了更改,您应该不必手动告诉 Webpack 该做什么。
另一个问题是,如果你想在你的项目中添加 Sass(语法上很棒的样式表), Webpack 应该能够使用它。
第三个问题是,如果你想使用最新版本的 JavaScript。更新的代码库将包括旧浏览器可能不支持的语法更新。
下一节将讨论这些问题的解决方案。
添加 webpack-dev-server
在这里,您将看到上一节中讨论的一些问题。
您知道当您的任何源代码更新时,您需要手动重新编译您的代码。这是不可接受的情况,你需要解决它。您可以通过使用 Webpack webserver 来解决这个问题。这将替换您以前使用的服务器。
如果服务器当前正在运行,请将其停止。返回命令行并安装 Webpack 服务器。类型
npm install webpack-dev-server –save-dev
安装完成后,您可以看到package.json文件已经更新。
现在它已经更新了,您可以更新您原来的start脚本,告诉它使用 Webpack dev 服务器而不是 http-server 服务器。
回到您的开发环境,通过键入以下命令更新start脚本
webpack-dev-server –mode development -–open
在这里,您将指导您的脚本使用 webpack 服务器并添加标志-—open。此标志将打开默认浏览器并加载索引页面。
这样做的额外好处是,当您对文件进行更改时,它会自动更新和刷新页面。这将在您每次编辑时为您提供页面的更新版本。
为了测试这一点,对 JavaScript 页面进行更改并保存文件。您应该看到终端窗口更新,浏览器刷新页面。
现在,您已经有了 Webpack 工作的默认配置。您的开发服务器正在运行,它将识别您的应用中的文件何时被更新。当文件更改时,Webpack 将重新编译代码并刷新浏览器,这样您就可以看到网站的最新版本,就好像它是由服务器提供的一样。
JavaScript 是一种不断变化的语言。伴随这一变化而来的问题是,并非所有的浏览器都跟上了这一变化。根据项目的不同,您可能需要支持没有您想要的功能的旧浏览器。
下一节将介绍 Babel.js 作为解决这个问题的方法。它允许您编写最新的 JavaScript,但仍然具有您在项目需求中工作所需的向后兼容性。
添加 Babel.js
Babel(位于 https://babeljs.io/ )是一个工具,当你想使用 JavaScript 的最高级特性,并且仍然支持可能还不支持这些特性的浏览器时,你可以使用它。
很容易将这个特性添加到您的项目中。既然已经有了 Webpack,您只需要添加一些配置文件,这样 Webpack 就知道它应该通过 Babel 处理所有的 JavaScript。
您需要做的第一件事是返回命令行,将 Babel 添加到您的项目中。在命令行中,键入
npm install @babel/core babel-loader @babel/preset-env –save-dev
这一行将安装巴别塔的三个部分:
-
巴别塔核心
-
巴比伦装载器
-
Babel 预置环境,在这里你可以将 ES6 代码编译成旧版本的 JavaScript
下一步是配置 Babel。配置 Babel 的方法是使用.babelrc文件。如果您以前使用过 Git,您可能对此很熟悉。当文件名前面有一个点时,该文件对操作系统是隐藏的。您可能需要更改操作系统的设置才能看到这些文件。
如果您在命令行,您可以通过键入"touch .babelrc"来创建一个同名的空白文件。该文件通常位于用户目录的根目录下。
您可能需要更新操作系统设置才能看到这些文件。打开.bablerc文件,添加清单 9-4 中的代码。
{
"presets":[
"@babel/preset-env"
]
}
Listing 9-4Configuring the .babelrc File
这在巴别塔内部建立了一个预设。它允许您使用最新版本的 JavaScript,而不需要在每次语言更新时都更新这个文件。它还将代码转换成一种能在大多数浏览器中工作的方式。
现在,您已经设置了 Webpack 和 Babel。你需要一种方法让这两个部分一起工作。
当运行在package.json文件中定义的脚本时,您的目标是让 Webpack 使用 Babel 来编译您的 JavaScript。为此,您现在需要为 Webpack 添加一个配置文件。这将把两个库联系在一起。
到目前为止,您已经使用了默认设置,它们为您提供了大量现成的功能。由于 Babel 不是 Webpack 的一部分,您需要使用一个简单的 JavaScript 文件来指导它。
创建一个名为webpack.config.js的文件。默认情况下,Webpack 会查找该文件。与另一个练习类似,转到命令行并键入"touch webpack.config.js"。结果将是一个空白的 JavaScript 文件,您可以使用它来配置 Webpack。该文件通常位于项目的根目录下。
这个配置文件为您想与 Webpack 一起使用的每个库设置配置设置。在这个实例中,您导出了一个包含 Babel 所有设置的模块。
将清单 9-5 中的代码添加到您的配置文件中,然后您就可以查看细节了。
module.exports = {
module:{
rules:[{
test:/\.js$/,
exclude: /node_modules/,
use:{
loader:"babel-loader"
}
}]
}
}
Listing 9-5Configuring the webpack.config.js File
该文件导出 Babel 的配置选项。此时此刻,巴别塔是你唯一的配置设置。将来,这可能会被更新,以允许 Webpack 处理 CSS 文件、TypeScript 文件和包括图像在内的其他文件类型。
您的配置设置在module对象中。这个对象有一个名为rules的数组。该数组包含规定加载程序如何工作的规则。第一行是test,这个规则使用了一个正则表达式。正则表达式告诉加载器应该处理哪种类型的文件。在这种情况下,它只适用于 JavaScript 文件。
下一项是可选的。它告诉 Webpack 不要查看某些文件夹。这里您排除了node_modules文件夹。忽略这个文件夹是有意义的,因为它不是应用代码的一部分。
您的示例中的最后一个选项称为use。该选项下的一个必选项是loader。加载程序必须始终是字符串。你的加载器是连接 Babel 和 Webpack 的纽带。现在,它将查看项目中的所有 JavaScript 文件,并参考 Babel 配置文件,以便知道如何处理这些文件。
Webpack 处理 JavaScript 时,您会在dist文件夹中找到结果。
目前,您的 HTML 页面还没有被处理。你可以在 Webpack 中添加一个插件,确保你的 HTML 文件得到优化,并保存在你的dist文件夹中。
下一节将详述您在这里所做的工作,并展示如何使用 Webpack 来优化您的 HTML 和 CSS 文件。
添加 HTML 和 CSS 加载器
为了让第一部分工作,您需要添加 HTML Webpack 插件。除了添加这个插件,您还必须添加加载器,它将负责加载 HTML 文件进行处理。
使用命令行,您可以通过键入以下内容来添加 Webpack 插件和加载程序:
npm install html-webpack-plugin html-loader –save-dev
当这一行被执行时,package.json文件被更新。这使您能够更新您的配置文件,以使用插件和加载程序。
您需要做的第一件事是requireHtmlWebpackPlugin 来使用 Webpack。使用require类似于导入。使用它,您可以将特性添加到配置文件中,以便与 Webpack 一起工作。
一旦建立了这种关系,您就可以更新角色,以了解加载程序将如何工作。规则告诉 Webpack 它需要加载所有 HTML 文件并优化它们。
你需要做的最后一件事是添加一个插件部分。插件部分中的数组的第一个元素是 HtmlWebpackPlugin。
传递给该函数的对象包含两个属性:HTML 模板(这是您在src文件夹中的原始文件)和确定 HTML 文件的路径和文件名的属性。现在,保留默认值,继续称它为index.html。这将处理后的 HTML 文件添加到dist文件夹中。
此时,您的文件应该如清单 9-6 所示。
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
module:{
rules:[{
test:/\.js$/,
exclude: /node_modules/,
use:{
loader:"babel-loader"
}
}]
},
{
test:/\.html$/,
use:[
loader:"html-loader",
options:{minimize:true}
]
}
},
plugins:[
new HtmlWebpackPlugin({
template:"./index.html",
filename: "./index.html"
})
]
}};
Listing 9-6Adding the HtmlWebpackPlugin to webpack.config.js
现在,运行构建脚本会将index.html文件添加到dist文件夹中。
现在 Webpack 可以处理 HTML 和 JavaScript 文件了。优化确保了输出可以在尽可能多的浏览器中工作。
你缺少的一件东西是 CSS。CSS 本身并不是一种编程语言,但是通过使用 Sass 之类的东西,你可以使用类似于编程语言的特性。
例如,如果您想在多个地方重用一种颜色,Sass 会帮助您创建一个保存当前颜色值的变量。一旦萨斯或 SCSS 文件被编译,它们只是浏览器理解的 CSS 文件,可以在你的项目中使用。
为了在您的项目中使用 Sass,您需要向 Webpack 添加一些加载器,以便它可以将 Sass 转换为 CSS。
在命令行中,键入以下内容:
npm install –-save-dev style-loader css-loader node-sass mini-css-extract-plugin sass-loader
应该用已经下载的库的引用来更新package.json文件。下一步是配置 Webpack 以利用这些新的更新。
随着这些新功能添加到您的项目中,您现在可以更新 Webpack 来利用它们。您需要更新webpack.config.js文件,以便您的 JavaScript 文件可以导入 SCSS 文件并应用样式。
要完成这项工作,格式与其他示例完全相同。您向对象数组中添加了一个测试和一个使用部分,这将负责您如何处理 SCSS 文件。您的webpack.config.js文件应该如清单 9-7 所示。
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
modules.export = {
module:{
rules:[
test:/\.js$/,
exclude: /node_modules/,
use:{
loader:"babel-loader"
}
]
},
{
test:/\.html$/,
use:[
loader:"html-loader",
options:{minimize:true}
]
},
{
test:/\/scss$/,
use:[
"style-loader",
"css-loader",
"sass-loader"
]
}
},
plugins:[
new HtmlWepPackPlugin({
template:"./index.html",
filename: "./index.html"
}),
new MiniCSSExtractPlugin({
filename: "[name].css",
chunkfile: "[id].css"
})
]
}};
Listing 9-7Adding the Sass Features to webpack.config.js
设置好更新后的配置文件后,现在可以创建一个 Sass 文件,并将其导入到 JavaScript 文件中。
您可以通过创建一个保存颜色值的变量来利用 Sass,然后在声明元素的颜色时使用该变量。您的 Sass 文件应该如下所示:
//_sass/main.scss
$header-color: #b7cbcb;
.header{
background-color: $header-color;
width:100%;
height: 200px;
}
首先用一个值定义变量background-color,在本例中是一个十六进制颜色。下一次使用该变量是在定义名为header的 CSS 类时。
既然已经创建了.scss文件,您需要将它导入到您的 JavaScript 文件中,以便应用可以使用它。
回到最初的index.js文件,您可以在顶部添加这一行:
import './_scss/main.scss';
你有大部分的工作;最后一部分是将它绑定到 HTML 元素。
打开index.html文件,添加一个div标签,在添加class属性时,给它赋值header *。*应该是这样的:
<div id="app" class="header"></div>
还好div里面什么都没有。您只想看看在您的应用中使用 Sass 的效果。
完成所有这些后,您可以返回到命令行。如果您没有运行本地服务器,请键入
npm start
这应该运行本地 Webpack 服务器,编译所有代码,包括.scss文件,并将其转换成 CSS。
浏览器应该如图 9-3 所示。
图 9-3
编译的 Sass 在浏览器中呈现为 CSS
摘要
您的项目现在能够导入外部库,在文件发生变化时自动更新,并输出可以在尽可能多的浏览器上工作的 JavaScript,尽管是使用最新的 JavaScript 开发技术编写的。
您还使用了 Sass,这意味着您的样式可以在尽可能多的浏览器中工作。您获得这些特性是因为您使用 NodeJS 作为基础。Webpack 使用 Node 给你一个本地服务器来使用。您的脚本告诉服务器监视这些文件,这样页面就会随着变化而刷新。
这只是如何在客户端使用 Node 的开始。节点是大量客户端工具的构建块。例如,使用最新版本的 Angular,您可以使用命令行来构建一个基本的应用。
React 有一个类似的工具叫做create-react-app。这两种工具都可以为您完成大量工作,因为 Node 可以访问文件系统,并且可以为您创建文件和文件夹。
这还不是你能用 Node 做的所有事情的结束。你可以探索一个全新的领域。Node 还使您能够使用 JavaScript 运行应用服务器。下一章将探讨 Express 框架,以及如何使用它不仅服务于网页,还能访问数据库中的数据。
十、JavaScript 和服务器端开发
在上一章中,您使用了 NodeJS 作为帮助客户端开发的一种方式。您使用的工具都是由 Node 驱动的。如果没有 Node,创建文件和将 Sass 和高级 JavaScript 等语言转换成浏览器可以理解的内容的能力是不可能的。
这一章将采用这一思想并加以扩展。您将使用 Node 作为 web 服务器。不仅仅是一个本地服务器,这是你在上一章做的,而是一个成熟的生产服务器。
为此,您将使用上一章中的大多数工具。在命令行中,创建一个文件夹,进入该文件夹,使用 NPM 创建一个新的package.json文件。您的命令应该如下所示:
mkdir nodeProject
npm init
在这一点上,您将会被问到一些问题,这些问题将会被用来构建这个文件。有关所有选项的详细分类,请参阅上一章。
对于本练习,您可以通过按 Return 键并接受结果来跳过选项。最后,向项目中添加 Express 框架。这与您对前端项目所做的完全相同。在命令行中,安装 Express 并将其另存为依赖项:
npm install express –save
这将更新package.json文件,并赋予您使用 Express 的能力。
基本快速设置
设置完成后,您现在可以创建一些文件来告诉 Express 启动,监听您的请求,并返回一个字符串。这是一个“Hello World”的例子。在命令行中,创建一个名为app.js的文件。这是你的起点。
在这个文件中,需要 Express 框架,启动应用,监听请求,并返回响应。清单 10-1 显示了代码。
//app.js
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send("Hello World");
});
app.listen(port, () => {
console.,log(`Running on port ${port}!`);
});
Listing 10-1Setting Up a Basic Express Server
您的文件现在已经包含了 web 服务器的所有基本组件。您可以监听来自浏览器的请求。该请求发送一个带有文本“Hello World”的响应。所有这些都在端口 3000 上。要查看这一过程,请返回命令行并键入
node app.js
这告诉 Node 查看文件app.js。然后这个文件执行代码并开始运行 Express 服务器。现在,如果你打开浏览器并输入localhost:3000,它应该看起来像图 10-1 。
图 10-1
用 Node 和 Express framework 启动一个简单的 web 服务器
您添加到应用中的代码允许服务器在端口 3000 上运行。这很重要,因为如果您选择不同的号码,您将不会从服务器得到响应。请注意这一点,因为其他应用使用不同的数字进行开发。web 服务器的默认数量是 8080。
将 nodemon 和路由添加到 Express 应用
在上一章中,您添加了一个特性,每次文件更新时站点都会刷新。这使得开发变得更加容易,因为您不必在每次更改后停止并手动重新编译应用。
为了让您的节点应用做到这一点,您需要添加一个名为nodemon *的库。*它提供了类似的功能。要安装它,请在命令行键入以下内容:
npm install -g nodemon
在安装节点监视器时使用-g标志来确保它是全局可用的。安装后,您可以在硬盘上的任何地方使用该库。
安装了这个库之后,您可以更新您的package.json脚本,以便在您的开发中使用nodemon。start脚本现在应该是这样的:
nodemon app.js
这将监视您的应用,并在您对文件进行更改时重新启动服务器。
现在,您不必在每次进行更改时手动重启服务器,您现在可以扩展您的应用了。
扩展应用的第一种方法是创建一个叫做 route 的东西。路由是服务器上返回 HTML 页面等内容的路径。
例如,在第一个练习中,只需查看服务器的根目录。如果你想要一个名为localhost:3000/users的路由,目前它会抛出一个错误,说它不能“获得”用户。要解决这个问题,您需要创建一个路由。下一节将解释如何做到这一点。
使用节点创建路线
向您的站点添加路由将设置您希望服务器执行的操作。为了全面理解路由是如何工作的,让我们首先研究 HTTP 是如何工作的,然后研究它如何与您的路由相关联。有了充分的理解,你就可以进入代码。
HTTP(超文本传输协议)有一个请求-响应模型。浏览器向服务器发出请求,然后服务器用资源做出响应。该资源通常由 HTML 页面组成,但也可以是其他类型的数据。
HTTP 有一系列描述需要执行的动作类型的方法或动词。
大多数时候浏览器向服务器发出请求,只是想“获取”信息。GET 是当您想要从服务器检索数据时使用的动词之一。以下是动词列表:
-
GET:从资源请求数据
-
POST:请求服务器在数据库中创建资源
-
PUT:请求服务器更新资源。如果该资源尚不存在,则创建它。
-
删除:请求从数据库中删除资源
有了这种理解,你现在可以想出使用这些 HTTP 动词时路由看起来是什么样子的例子。
在下一个示例中,您将从大处着手,深入到更多细节。如果您想要了解美国各州的信息,路线如下所示:
localhost:3000/states
这里,您发出一个 GET 请求,请求所有州的信息。根据数据库的设置方式,如果您想要第 11 个州的信息(纽约),路线如下所示:
localhost:3000/states/11
在这两个例子中,您引用的资源是states。您知道团队中有人在数据库中创建了关于状态的信息。
选择保留“states”而不是“state ”,这为您提供了灵活性。例如,第二条路线缩小了范围,您希望查看第 11 个个州。
延伸一下,如果要删除第 11 个个状态的所有信息呢?路径将是相同的,但是您将发送一个删除请求。
如果您想获得纽约所有的行政区信息,您的路线应该是这样的:
localhost:3000/states/11/boroughs/
如果你只是想了解布鲁克林,路线会是这样的:
localhost:3000/states/11/boroughs/3
现在,您有了一种描述如何访问数据库中的项目的通用语言。像这样格式化路线使您能够创建、读取、更新或删除信息。这通常被称为积垢。
到目前为止,我已经讨论了通常所说的 REST 服务(代表性的状态转移)。记住这一点,您可以请求服务器对数据库执行操作。现在让我们看看如何用 Node 实现它。
使用前面的例子,创建路线并不困难。如果您想发出一个 GET 请求,它将类似于清单 10-2 。
app.get('/states/, (req, res) => {
res.send("This is the States Page");
});
Listing 10-2Creating a GET Route Using Node
这里您可以看到,当请求路由时,Node 将运行一个函数并发送一个响应。
现在您已经对 Node 如何处理 HTTP 动词有了一个基本的了解,其他的函数也就非常熟悉了。参见清单 10-3 。
app.post('/states/, (req, res) => {
res.send("This is the States Page POST request");
});
app.put('/states/, (req, res) => {
res.send("This is the States Page PUT request");
});
app.delete('/states/, (req, res) => {
res.send("This is the States Page DELETE request");
});
Listing 10-3Using the Other HTTP Verbs with Node
现在您已经理解了如何对 Node 使用 HTTP 动词。清单 10-4 展示了如何服务位于states文件夹中的 HTML 页面。
//app.js
const express = require('express');
const app = express();
const path = __dirname + '/views/';
const port = 3000;
app.get('/states, (req, res) => {
res.sendFile( path + 'states/index.html');
});
app.listen(port, () => {
console.log(`Running on port ${port}`);
});
Listing 10-4Setting the Path to Load HTML Pages
这个例子展示了如何从浏览器接收一个请求,然后将一个 HTML 文件发送回浏览器。这个例子之所以有效,是因为您创建了一个views文件夹,并在其中创建了一个名为states的子文件夹。该文件夹包含 HTML 页面。
使用__dirname给 Node 当前运行文件的路径。在这个例子中,您从根目录开始添加views文件夹。从那里,当请求路由时,Node 可以运行一个函数来查看states文件夹,并将states.html发送回浏览器。
现在您已经了解了路由是如何工作的,并且知道如何将响应或 HTML 页面发送回浏览器,让我们来看看如何将数据从数据库发送回浏览器。
设置 MySQL 的本地实例
在上一节中,我介绍了 REST 服务的概念,使用服务器路径和 HTTP 动词的组合来描述需要对数据做什么。
在某些情况下,返回一个 HTML 页面正是我们所需要的。一些信息是静态的,不需要引用数据库内部的信息。
在其他情况下,您可能正在开发类似单页面应用(SPA)的东西。在这些情况下,您不会根据路径加载单个页面。使用像 React、Angular 或 Vue 这样的库,应用将计算出路线是如何工作的,而不使用服务器。
当在这样的环境中工作时,web 服务的路径对于前端应用变得非常重要。路由将决定应用如何发出请求以及如何检索信息。Web 服务通常位于与前端应用不同的服务器上。
在本节中,您将创建一个本地数据库,添加一些数据,并通过 Node 使其作为 web 服务可用。首先,您需要下载并安装一个数据库。
MAMP/WAMP (MacOS/Windows,Apache,MySQL 和 PHP)是一个点击式的开源软件栈。各个部分被放在一起,所以你可以有一个简单的安装和建立一个网站,而不需要配置一切。
栈的每个部分都可以是一本书,但是对于您的目的,您只需要查看 MySQL 数据库。
这个堆栈附带了一个名为 PHPMyAdmin 的 PHP 工具,可以让您编辑本地数据库。您还可以下载一些应用来帮助编辑和更新数据库,而无需了解大量 SQL(结构化查询语言)。其中一些工具包括 Mac 上的 Sequel Pro 和 Windows 上的 SQL Pro。
安装好软件包后,您就可以启动服务器了。这将打开您的默认浏览器窗口,显示欢迎使用 MAMP 页面。打开后,您现在可以访问 MySQL 数据库。
您需要创建一个数据库来使用。我不会涉及数据库如何工作的所有细节。您将创建一个简单的表,并使用 Node 创建一个简单的 API,允许您创建、检索、更新和删除数据。
为了理解什么是表格,可以把它想象成一个电子表格。使用电子表格时,所有信息都被分成行和列。数据库以类似的方式工作。在表格中,每一列用于特定类型的数据,每一行由您正在处理的数据组成。
使用任何可用的工具来创建数据库。Sequel Pro 有一个选项可以让你添加一个数据库。对于您的示例,将其命名为api。一旦创建了数据库,接下来要做的就是创建一个表来保存数据。您的脚本将创建一个名为boroughs的表。该脚本应该类似于清单 10-5 。
CREATE TABLE `boroughs` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT ",
`state` varchar(50) DEFAULT ",
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Listing 10-5A SQL Script to Create a Table Called boroughs
关于这个脚本,需要知道的最重要的事情是,它创建了一个名为boroughs的表,这个表将包含名为id、name和state的列。见图 10-2 。
图 10-2
运行 SQL 脚本来创建表
应该有一个可以添加查询的区域。添加之后,运行查询并创建表。
创建完表后,您需要再运行一个脚本,这样您就可以处理少量数据了。这个脚本将插入Brooklyn作为name和New York作为state。您没有为 ID 添加值,因为该字段设置为自动递增。有了这个设置,每次你添加一个新的条目到数据库中,它将获得一个唯一的编号。
下面是一个insert脚本应该是什么样子:
INSERT INTO `boroughs` (`id`, `name`, `state`) VALUES (NULL, 'Brooklyn', 'New York');
现在您有了一个包含一些数据的表,您可以返回到 Node 并使用您的 API。首先,您需要将 MySQL 连接到您的节点应用。
您应该做的第一件事是将 MySQL 模块安装到您的项目中。这就像您在命令行中完成的所有其他工作一样。通过运行以下命令添加模块:
npm install mysql –save
安装后,它将使引用 MySQL 和访问新创建的数据库中的数据变得容易。
在第节中,您使用 W/MAMP 快速设置了一个数据库,并运行了几个 SQL 脚本来创建一个数据库和数据库中的一个表。现在,您可以使用 Node 从数据库中访问数据了。节点将对数据库运行查询,并将结果返回给浏览器。
弄清楚你将如何组织你的文件是很重要的。您确定了根据您想要返回特定类型数据的路径。
下一节将讨论如何用 Node 创建路由并从数据库中检索数据。
使用 NodeJS 从 MySQL 返回数据
在上一节中,您下载并设置了一个数据库。您还添加了一些数据,以便测试您的路线。路由是您要传递给节点的路径,告诉它您需要某种类型的数据。
您设置项目的方式将赋予您扩展和添加其他路线的能力,但是对于这个实例,您将专注于这一条路线。
你需要创建几个文件夹来存放你的文件。在当前项目中创建一个data文件夹和一个controllers文件夹。
在data文件夹中,创建一个名为config.js的文件来保存数据库的所有配置设置。
在controller文件夹中,添加一个index.js文件和一个boroughs.js文件。有了这些文件,您就可以开始编写配置选项了,这样您就可以连接到 MySQL 数据库了。
在config.js文件中,添加清单 10-6 中的代码。
const mysql = require('mysql');
const config ={
host:'localhost',
user:'root',
password:'root',
database:'api'
port:'8889'
}
const pool = mysql.createPool(config);
module.exports = pool;
Listing 10-6Configuration Settings for Node to Connect to the MySQL Database
本例中的代码类似于您创建节点服务器的方式。首先,mysql库是必需的,并被赋给一个变量。然后创建一个包含所有配置设置的对象。在那之后,你创建一个所谓的pool。这使您能够拥有到数据库的多个连接,并有助于管理这些连接。最后,导出用于代码其他部分的pool对象。
现在,您可以连接到本地数据库了。现在您需要创建一个路由,它将成为您的 API 的一部分来检索数据。
原始的app.js文件是节点应用的起点。在前面的例子中,您学习了如何监听请求并做出响应。
现在,您需要让 Node 捕获请求,并过滤出该请求需要到达的位置。这可以通过使用use方法来实现。打开app.js并添加清单 10-7 中的代码。
app.use(require('./controllers'));
Listing 10-7Having Node Send All Requests That Are Not to the Root of the Application to the Controllers File
这段代码从浏览器获取所有请求,并将其发送到所需的controllers文件夹。
之前,您在controllers文件夹中创建了一个名为index.js的文件。默认情况下,Node 将引用这个文件并使用它来决定下一步做什么。
此示例创建了一条路线;但是,您可以随意添加。您可以在app.js文件中添加所有内容。这本来是一个简单的解决方案;然而,它很难扩展和测试。
在这里,您可以清楚地知道哪些文件将处理每个请求。您的index.js文件应该如下所示:
const router = require('express').Router();
router.use('/boroughs', require('./borough'));
module.exports = router;
第一行应该很熟悉,因为它是您在app.js中用来让 Node 开始工作的。这里您添加了使用Router对象的能力。该对象只执行路由功能,并允许您添加诸如身份验证之类的功能。
现在您已经可以访问router对象,您可以使用相同的use方法来指向 Node。您告诉 Node,对/boroughs的每个请求都必须由borough文件处理。
在这里,您已经能够缩小节点处理路由的方式。从一个非常高的层次来看,您将它定向到controllers文件夹中的索引文件。然后你找出被请求的直接路线。最后,您处理请求的细节以及如何将响应发送回浏览器。
boroughs.js文件检查请求是否就在这个路由的根上。然后,它向数据库查询关于这个主题的所有信息。另一种情况是它根据数据库中的索引号发出请求。如果是这种情况,它将发送一个结果。
在您的例子中,数据库中只有一个条目,但这是一个很好的介绍,便于您在下一章中添加和更新条目。
boroughs.js文件需要更新。这个文件也应该使用 Node 提供的Route对象。您还可以导入您在本节前面创建的pool对象,并使用它来请求数据库。
您的文件应该有两个如下所示的方法:
const router = require('express').Router();
const pool = require('../data/config');
router.get('/', (req, res) => {
pool.query('SELECT * FROM boroughs', (error, result) => {
if(error) throw error;
res.send(result);
});
});
router.get('/:id', (req, res) => {
const id = req.params.id;
pool.query('SELECT name, state FROM boroughs WHERE id = ?', id, (error, result) => {
if(error) throw error;
res.send(result);
});
});
module.exports = router;
从顶部开始,创建Route对象。然后您需要拥有所有数据库信息的pool对象。
现在,您可以开始处理浏览器或应用对 Node 的请求。当应用发出请求localhost:3000/boroughs/时,您的第一个方法将处理该请求并调用数据库。
这里你需要了解一点 SQL 通过使用pool对象,您可以使用query方法并将您的查询作为字符串传递。
写 SQL 时不需要大写;这更多的是一种形式。SQL 命令通常是大写的。
这里您想要选择名为boroughs的表中的所有列。您知道您想要所有的列,因为您使用星号作为选择列的方式,而不是通过名称来要求每个列。
添加查询后,回调函数会查找错误或结果。该函数使用一个if语句来检查错误。如果没有发现错误,它会将查询结果发送回请求它的应用。
第二个功能与第一个非常相似。重要的区别是它在调用中寻找一个参数。第一个方法只是想知道您是否正在请求/boroughs路线,这里您不仅想知道您是否正在请求该路线,还想通过索引号知道路线。
第二个函数是寻找一个叫做id的变量;您可以通过如何将请求设置为/:id来了解这一点。通过在名称前使用冒号,您知道这是一个应用将传递给 Node 的变量。然后,您需要创建一个变量来捕获该值,并将其用作查询的一部分。
一旦有了值,就可以将它添加到查询中。在第一个例子中,您有查询和回调函数。在第二个示例中,在中间添加 id 的值,这样数据库就知道要查找什么 id。
如果服务器和数据库都在运行,您可以进入浏览器并查看结果。在每种情况下,都应该有一个 JSON 对象返回给您。见图 10-3 。
图 10-3
基于从节点请求的路由接收 JSON 对象
现在您有了一个节点服务器,可以用来从数据库中检索数据。您创建的 API 可以基于传递您感兴趣的项目的 id 为您提供单个结果,但是您也可以在单个调用中获得所有信息。
摘要
本章讲述了如何在服务器上使用 JavaScript。您了解了如何使用 Node 和 Express 框架来处理来自浏览器和其他应用的请求。
您安装了 MySQL 作为您的数据库。MySQL 是可以与 Node 一起使用的众多数据库之一。这些示例介绍了一些 SQL,它们创建了一个表并添加了一些数据供您使用。
最后,您利用了Routes对象。您使用它来计算节点请求的路径。你只对/boroughs路径感兴趣。如果是浏览器请求的,Node 会对数据库进行查询,并返回一个 JSON 对象和查询结果。
你也知道如何询问非常具体的信息。通过在 URL 中向服务器传递参数,您的脚本提取了该信息,并将其用作查询的一部分。
现在,您可以使用浏览器或应用直接从数据库中检索数据。如果您开发单页面应用,这将变得更加重要。这种性质的应用向服务器发出请求,并根据结果更新页面。
下一章将介绍 Angular 作为一种创建单页应用和从 API 中更新或检索数据的方法。
十一、JavaScript 和应用框架:Angular
为了使讨论简单,当我提到应用框架时,我指的是任何能帮助你快速开发完整 web 应用的库或框架。这可能包括但不限于 Angular、React、Vue 和 Polymer。
其中一些库被认为是完整的框架,而另一些只是库。本章的目标不是将两者对立起来,也不是描述各自的优缺点。这一章的目的是展示你如何利用前几章学到的知识来开发使用这些框架的应用。
在上一章中,您开发了一个 API,它发送一个描述行政区的 JSON 对象。您使用 MySQL 和 NodeJS 的组合让服务器查询数据库并返回查询结果。
本章将介绍如何使用 Angular 开发一个 web 应用,下一章将介绍 React。这两个应用都能够访问上一章开发的 API。
本章的前半部分将介绍如何使用命令行界面(CLI)快速开发 Angular 应用,以生成从 API 检索数据所需的所有文件。
一旦您能够检索和显示您的信息,您就可以创建一个可以在数据库中添加或更新信息的表单。
安装角形
如果你真的想从整体上对 Angular 框架有一个很好的了解,最好去的地方是项目主页( https://angular.io )。
既然您将使用命令行界面来开发这个应用,那么您应该查看的下一个站点是 CLI 开发的主页( https://cli.angular.io )。
使用 CLI 与直接使用 Node 时的最后几个示例非常相似。
如果您尚未打开终端窗口,请将其打开,并使用节点软件包管理器将 Angular 加载到您的计算机上。
在命令行中,键入
npm install -g @angular/cli
该命令在全局级别安装角度命令行工具。安装完成后,您可以在硬盘上的任何文件夹中创建 Angular 项目。
Angular 将自己描述为一个在 Web 上构建应用的平台。注意,我们说的不是 AngularJS,俗称 Angular 1。我们只打算讨论 Angular 的最新版本,俗称 Angular 2+。在撰写本文时,Angular 的最新版本是 7。
Angular 是一个开源的、基于类型脚本的平台,由 Google 的 Angular 团队领导。TypeScript 是微软维护的开源语言。
什么是 TypeScript?
TypeScript 是 JavaScript 的超集,这意味着除了将 JavaScript 用作强类型语言之外,您还可以使用 JavaScript 内置的更高级的功能。
TypeScript 和 JavaScript 的区别之一是 JavaScript 不强制数据类型,也就是说你不能强制一个变量只能处理字符串或数字。JavaScript 在处理不同类型的数据时非常灵活。
TypeScript 还具有以下功能:
-
接口
-
无商标消费品
-
名称空间
-
装饰者(实验性特征)
其他语言强调,一旦你将一个变量声明为某种类型,改变该类型将会产生错误。
如果您使用过 Java 或 Scala 之类的语言,语法看起来很相似。声明变量时,可以显式指示编译器将变量视为仅使用特定类型的数据:
let x: number = 42;
这段代码强制 TypeScript 编译器确保赋给 x 的任何变量都是数字。试图分配任何其他类型的数据将在编译时被捕获,并将产生错误。这里有一个例子:let x:number = 42;
x = "Fourty Two" //produces an error
TypeScript 语言有一个将 TypeScript 转换成 JavaScript 的编译器。新创建的 JavaScript 可以被多种浏览器理解。作为开发人员,您可以利用 JavaScript 中并非所有浏览器都能理解的高级特性。使用 TypeScript 的另一个好处是能够在编译时发现错误。TypeScript 为您提供了许多来自面向对象语言的人所期望的特性。
在下一节中,您将使用 CLI 创建一个 Angular 项目,并在浏览器中运行它。
开发角度应用
至此,您已经能够安装 Angular CLI 工具。使用这些工具将帮助您使用 Angular 框架开发应用。Angular 将下载构建应用所需的所有文件,并创建基本 Angular 应用所需的所有文件夹。
在命令行中,键入以下命令创建一个名为my-app的应用:
ng new my-app
这一条命令会产生一些关于如何开发应用的问题。您可以随时在以后更新应用,但现在让我们来看一下 CLI 询问的一些问题。
第一个问题是,“您想添加角度路由吗?”用 Angular 路由就像用 Node 路由一样。这个想法是将一个路径传递到浏览器中,该路径将显示站点的某个部分。这里最大的不同是 Angular 将处理路由,而不是服务器。
这种区别非常重要,因为两个应用都会尝试解析路由。服务器将使用该路径查找包含基于该路径的文件的文件夹,然后尝试将结果发送回浏览器。因为服务器上不存在这些文件和文件夹,所以结果将是 404 或文件未找到错误。
对于您的示例,我们同意将其添加到您的应用中。在命令提示符下键入 Y,然后继续回答下一个问题。
下一个问题是,“您希望使用哪种样式表格式?”呈现的不同格式有
-
半铸钢ˌ钢性铸铁(Cast Semi-Steel)
-
半导体色敏传感器
-
厚颜无耻
-
较少的
-
唱针
第一种选择应该直截了当。接下来的四个在处理样式表的方式上都是相似的。
这个列表中的前两个是最相似的,在理解了前两个试图解决的问题之后,其他的就很容易理解了。
Sass 和 SCSS 都是预处理语言,这意味着它们会编译成你的浏览器能理解的 CSS。唯一真正的区别是语法。
Sass 使用缩进来表示选择器的嵌套。以下是 Sass 语法的一个示例:
//sass example
$backgroundColor: #C0C0C0; //silver
.container {
h1{
background-color: $backgroundColor;
; }
}
当编译这段代码时,Sass 知道根据文件顶部的变量将容器类中的h1元素设置为背景色,
Sass 还通过添加嵌套规则扩展了该语言。这些规则被称为混合规则;有了 mixins,除了其他特性之外,您还可以进行数学运算。
扩展名为.scss的文件具有更灵活的语法。这包括直接写 CSS。它还让您能够使用如下功能。清单 11-1 展示了一个 SCSS 的例子。
//scss example
$textColor: 'red';
p{
color: $textColor;
}
Listing 11-1Creating a Variable to Be Used Later in Your SCSS File
LESS 也是一个会转换成 CSS 的预处理程序。这里的区别在于,LESS 是用 JavaScript 编写的,需要 Node。
创建变量时,语法有细微的差别。萨斯和 SCSS 使用美元符号($)来创建变量,而 LESS 使用 at 符号(@)。
列表中的最后一个选项是手写笔。受 Sass 和 LESS 的启发,它与普通 CSS 的不同之处在于花括号和分号是可选的。变量不需要用美元符号($)或 at 符号(@)声明。
所有这些选项对于管理和缩放 CSS 都是有效的;对于你的项目,你可以选择你最喜欢的。在本例中,我们选择 Sass。
做出选择后,CLI 将创建 Angular 项目所需的所有文件和文件夹。您现在应该有一个名为my-app的文件夹。进入该文件夹并启动应用类型:
cd my-app
ng serve
一旦进入应用文件夹,您就可以运行该应用。当应用完成编译时,您可以通过打开浏览器并键入localhost:4200在浏览器中看到它。你的浏览器应该如图 11-1 所示。
图 11-1
在本地机器上运行的 Angular 应用
这是使用 CLI 创建的任何 Angular 应用的默认页面。现在,您可以开始为您认为合适的任何目的定制这个默认应用。
当启动这个应用时,使用命令ng serve;这是特定于角度的。通过使用一系列以ng开头的命令,您可以使用 CLI 来做一些事情,比如添加文件、运行您的测试套件,以及为生产构建最终版本。它甚至可以自己更新库和依赖项。要查看完整列表,请键入ng help 。
现在您的应用已经构建好了,让我们更好地理解它是如何组织的。
Angular 的建筑
像 Angular 和 React 这样的框架都有一个组件的概念。组件仅仅是放置应用不同部分的 HTML 和 CSS 的地方。
这方面的一个例子是标题。页眉可以有公司徽标和登录按钮。一旦一个人被认证,标题应该显示一个注销按钮。所有这些特性都应该存在于 header 组件中,应用逻辑应该决定什么应该可见以及何时可见。
Angular 还有一个概念叫做模块。Angular 中的模块与 JavaScript 模块不同。在这种情况下,Angular 中的模块为您提供了放置某个函数的所有相关代码的地方。你可以用页眉作为例子。头文件模块包括使头文件在应用中正常工作所需的所有文件。
所有角度应用都有一个根模块;这个模块启动应用。app.module.ts文件位于src/app文件夹中。它是启动应用的模块。
在上一章中,您使用 Node 创建了一个 API。您现在可以创建一个模块,它将使您能够从 Node 发出请求并显示结果。
在命令行中,您将创建一个模块,然后可以在其上进行构建。这个模块将很快让你有一个组件来保存你所有的显示信息。它还将利用表单和服务来连接到您的 API。
您的第一个练习是创建一个带有按钮的组件,该组件将向节点服务器发出请求,并在 Angular 应用中显示结果。
要创建一个模块,在命令行*中键入ng g module boroughs。*这段代码创建了一个新的模块,您可以在其上进行构建。
一旦命令被执行,你需要得到更大的角度应用,以意识到这个模块现在是可用的。为此,打开app.module.ts文件。在这里,您将模块导入到主应用模块中。
在app.module.ts文件中是一个imports数组。这个数组允许您将新模块添加到主应用中。如果你正在使用一个类似 Visual Studio 代码的应用,你可以开始直接将你的模块添加到imports数组中。Visual Studio 和其他开发应用一样,会知道你想把这个添加到应用中,并自动在文档顶部添加import语句。
app.module.ts文件的导入部分现在应该看起来像清单 11-2 。
imports: [
BrowserModule,
AppRoutingModule,
BoroughsModule //this is the new module
],
Listing 11-2Adding a New Module to the app.module.ts File
既然主应用知道了您的定制模块,那么您需要向该模块添加一个组件。我提到过组件是应用的视图层。这是您与应用交互并查看来自数据库的结果的方式。
要创建一个将成为行政区模块一部分的组件,您需要确保文件是在您的boroughs文件夹中生成的。在命令行中,键入
ng g component boroughs/list-boroughs
这将在boroughs文件夹中创建创建该组件所需的所有文件。它还更新了boroughs.module.ts文件,让该模块知道它现在可以访问组件了。
使用 CLI 创建组件时,declarations数组会更新。declarations数组跟踪这个模块中使用的所有组件。
为了让您的组件在浏览器中可见,您需要创建一个exports数组并将该组件添加到其中。见清单 11-3 。
@NgModule({
declarations: [ListBoroughsComponent],
imports: [
CommonModule
],
exports: [
ListBoroughsComponent
]
})
Listing 11-3The Updated boroughs Module
现在组件可以导出了,您可以在应用中将它作为 HTML 元素使用。在list-boroughs.component.ts文件中有一个叫做选择器*的部分。*选择器是你定制的 HTML 元素的名字。
您可以将该元素添加到您的 HTML 页面中,就像它是任何其他本机 HTML 元素一样。Angular 编译器将确保该元素在浏览器中正确呈现。
要查看这一过程,请打开app.component.html *。*这里是您在主页上看到的所有信息。您将删除它并添加新的自定义 HTML 元素。最后,app.component.html应该看起来像清单 11-4 。
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<app-list-boroughs></app-list-boroughs>
</div>
<router-outlet></router-outlet>
Listing 11-4Adding the app-list-boroughs Component to the app.component.html File
一旦这个文件被更新,你应该看到应用编译所有的代码,并在浏览器中显示一个新的结果。页面应该如图 11-2 所示。
图 11-2
Chrome 渲染更新后的 app.component.html 文件
现在,您已经拥有了由 Angular 渲染的自定义组件。在下一节中,您将创建一个调用节点应用并在页面中显示结果的服务。
创建角度服务
至此,您已经使用 Angular CLI 创建了一个应用并开发了一个定制组件。所有这些都可以在本地机器的浏览器中看到,而不需要上传到远程服务器。
组件用于显示信息和处理用户交互。组件不应该被用来做像从数据库获取数据这样的事情。
在 Angular 中,您使用服务来执行像从数据库获取数据这样的操作。服务可以注入到任何组件中。这提供了为特定动作编写一个服务并在不同的组件中重用它的能力。您将使用 CLI 来创建服务。您的服务将首先连接到一个远程服务器作为测试。
在您知道它正在工作之后,您将连接到运行您在上一章中创建的节点应用的本地机器。
您将使用 CLI 来创建服务。该服务将位于一个名为service/borough的文件夹中。文件本身将被称为borough.service。
在命令行中,键入
ng generate service service/borough/borough
这将把service文件夹添加到src/app文件夹中。然后它添加了一个boroughs文件夹;在该文件夹中,它创建了一个borough.service.ts文件。
创建好服务后,您现在可以让您的定制组件访问它;之后,您可以配置您的组件,以便它可以调用远程服务。
我讨论了服务必须在多个组件中可用的能力。这是因为服务可以被添加或“注入”到多个组件中。让我们将您的服务添加到list-boroughts.component.ts文件中。
打开文件并导入服务。导入服务后,您可以通过在组件内部使用constructor方法来创建这个类的实例。JavaScript 和 TypeScript 都有类的概念。当创建一个类时,你定义了一个对象类型应该如何工作的“蓝图”。
使用该蓝图,您可以根据自己的需要创建尽可能多的对象。创建类时,首先声明类及其名称。在该声明中,您定义了constructor函数。这个函数用于创建或初始化一个类中的对象。
在清单 11-5 中,您首先导入服务。然后使用constructor函数创建一个对象。
import { Component, OnInit } from '@angular/core';
import { BoroughService } from '../../service/borough/borough.service';
@Component ({
selector:'app-list-boroughs;,
templateUrl:'./list-boroughs.component.html',
styleUrls: ['./list-boroughs.component.sass']
})
export class ListBoroughsComponent implements OnInit{
constructor (private boroughService: BoroughService){}
ngOnInit(){
this.boroughService.getBoroughs().subscribe((data) => {
console.log(data);
}):
}
}
Listing 11-5Importing the BoroughService into the ListBoroughsComponent
这段代码中有很多东西需要解开。我还没有讨论组件是如何工作的。您只是创建了它们,并添加了在屏幕上显示它们的功能。现在您需要添加一个服务,让我们详细了解一下组件是如何工作的,以及这个示例中发生了什么。
组件表示角度应用的可见部分。组件类有一种叫做生命周期挂钩的东西。这些钩子由 Angular 管理,它们让你知道在给定的时间内任何组件发生了什么。
比如你这里用的生命周期钩子是OnInit 。当在一个类中使用时,生命周期挂钩以ng开始,所以您将使用的函数是ngOnInit函数。
第一行导入使角度组件工作所需的两个项目。首先是组件装饰器。你可以在这个例子中看到。@Component decorator 是第一个使它不同于常规 TypeScript 或 ES6 类的东西。它让 Angular 编译器知道这个类是 Angular 框架的一部分。
在这个装饰器中,您创建了一个对象,该对象描述了视觉细节以及它将如何在应用中实现。
从上到下,@Component首先描述选择器。这是您在另一个示例中使用的自定义 HTML 标记,用于使该组件在文档中可见。
这个对象中的第二个元素是templateUrl。它指向这个组件将要使用的 HTML 文件。
最后一节是styleUrls。它指向一个样式表数组,其中一个文件设置为默认值。
使用@Component装饰器,你现在可以添加所有的元数据(你将在文档中看到的一个术语),描述这个组件如何在更大的应用中工作。
回到文件的第一行,导入代码的第二件事是一个名为OnInt的生命周期事件。您可以在类声明中看到这一点。你的类implements OnInit作为类的一部分。
TypeScript 中的这个关键字implements很重要。你告诉编译器这个类将在类内部使用方法OnInit。如果你告诉这个类你将要实现某个东西,然后你没有在类中使用它,你会得到一个错误。
在你声明了你的类之后,你有了一个constructor函数。这些函数在 JavaScript 和 TypeScript 中都存在。Constructor函数在一个类中只能使用一次。在 TypeScript 中,你可以在constructor函数中创建一个对象。在这个实例中,您创建了一个私有对象,它的类型是BoroughService 。
当你声明某个东西private *,*时,意味着其他对象不能从外部访问这个对象的任何方法或属性。有几个关键字可用于控制其他对象对您正在使用的对象的方法和属性的访问级别;现在,你将和private 呆在一起。
使用完你的constructor函数,就可以使用生命周期法ngOnInit *。*当组件初始化时,constructor创建对boroughService对象的访问,并运行getBoroughs方法。
您使用subscribe方法告诉 Angular 您将检索从服务返回的任何结果。您将结果分配给一个名为data的变量,并运行一个函数,将结果显示在浏览器的控制台窗口中。
现在,您引用了作为服务一部分的函数。getBoroughs函数应该返回一个结果。此时,您尚未更新服务。根据您用来编写代码的环境,您可以使用 Control 键或 Command 键(如果您使用的是 MacOS)并单击boroughService。
这将打开该文件,并允许您直接访问该服务。您可以在这里更新您的服务。下一节将展示如何添加调用外部资源并将数据传递回组件的能力。
更新您的角度服务
最后一节展示了如何使用 CLI 创建服务并在组件中使用它。本节将向您展示如何让这个服务访问外部资源,并将结果发送回组件进行显示。
如果您使用 CLI 创建文件,请确保该文件已打开。您只需在这个文件中添加一些项,就可以调用远程 API 了。当您完成时,服务应该看起来如清单 11-6 所示。
import { Injectiable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable ({
providedIn:root
})
export class BoroughService {
constructor (private http: HttpClient){}
getBoroughs() `Observable<any>{
retun this.http.get('https://jsonplaceholder.typicode.com/todos/1')
}
}
Listing 11-6The BoroughService Class
在大多数情况下,您将要使用的格式与您创建组件时的格式相同。您将其他类导入到您的类中。有一个constructor函数用于创建一个对象,您向该类添加一个公共方法,该方法将供组件将来使用。
服务中使用的类似于组件的东西之一是@Injectable装饰器。这个装饰器使你的类能够被添加或被注入多个组件中。利用该功能,您可以编写一个服务并重用它。
装饰器里面是线providedIn: 'root' 。这优化了 Angular 使用所谓的依赖注入的能力。通过将服务注入到应用的根中,Angular 可以使用该服务的一个实例,并将该服务从不使用它的组件中删除。您还可以指定希望服务在哪个模块中可用。一旦加载了该模块,就可以使用该服务了。在每种情况下,您都不需要将服务添加到模块中的提供者阵列。
您需要添加到服务中的第二行是HttpClient。这允许您对任何远程服务进行 HTTP 调用。因为您正在向服务器发出请求,所以您将使用 REST 动词GET *。*这将让服务器知道您只想检索一个响应,而不想添加或更新数据库中的任何信息。
该方法内部是您想要从中获取信息的 URL。在这种情况下,您使用的是一个名为JSONPlaceHolder *的站点。*这个站点将返回一些 JSON,让您知道一个请求检索到了一个结果。
getBoroughs方法中的第一行告诉 TypeScript 它需要将这一行返回给调用该方法的类,在您的例子中是ListBoroughsComponent类。
现在您已经设置了服务,您需要在应用中再添加一个,这样您就可以使用您的HttpClient类了。在app.module.ts文件中,你需要添加一行
import the HttpClientModule from '@angular/common/http'
此外,将HttpClientModule添加到imports数组中。
现在,您有了一个可以注入到组件中的服务。当组件初始化时,它将在服务上运行getBoroughs方法,并在浏览器的控制台中打印结果。当调用boroughService上的方法时,它将向JSONPlaceHolder发出一个GET请求并返回结果。
当您运行 Angular 应用时,浏览器窗口看起来应该是一样的;然而,当您查看控制台日志时,您应该会看到一个包含 REST 调用结果的对象。见图 11-3 。
图 11-3
来自市镇服务的 REST 调用的结果
既然您已经验证了有从 web 服务返回的结果,那么您可以将这些结果显示在屏幕上。
现在,您将对组件进行小的更新。通过这些更新,您可以在页面加载时在页面上显示 REST 调用的结果。
打开组件。在这里,您将为您的类创建一个属性。当在浏览器中显示值时,模板将读取该属性。
您将添加一个名为results *的属性。*然后,您将把从服务返回的数据分配给这个属性。
代码应该如下所示:
private results: any;
constructor (private boroughService: BouroughService) {
}
ngOnInit() {
this.boroughService.getBroroughs().subscribe((data) => {
cosole.log(data);
this.results = data;
});
}
现在,您的函数能够将结果分配给类中的一个属性,您可以在屏幕上显示结果。
打开list-boroughs-components.html *。*在这里,您可以添加组成组件的所有 HTML。默认情况下,有一个包含一些文本的段落。您将在其下创建三个新的div元素来显示 REST 调用的结果。更新后的页面应该如下所示:
<p>
list-boroughs works!
</p>
<div> results.title {{results.title}} </div>
<div> results.Id {{results.userId}} </div>
<div> results.id {{results.id}} </div>
如果您的应用当前正在运行,您的浏览器窗口应该如图 11-4 所示。
图 11-4
在浏览器中显示 REST 调用的结果
你现在可以在屏幕上看到结果。这个例子让你使用一个占位符来获取结果并显示在屏幕上。在下一节中,您将利用您创建的节点 API。因为您的节点应用和角度应用都运行在同一台机器上,所以您需要开发一种方法让它们相互通信。
为本地角度应用创建代理
在上一章中,您创建了一个带有 Node 的 API。您的 API 从 MySQL 服务器发出请求,并将结果返回给浏览器。
本节将介绍如何让您的 Angular 应用向您的节点服务器发出相同的请求,即使它们运行在同一台计算机上。
从 Angular 应用的角度来看,它不知道它正在与一个单独的服务器进行对话。它将每个调用视为与本地服务器对话。
设置代理的好处之一是,在开发应用时,您可以连接到本地服务器(如本例所示)或远程服务器。代理文件将确保您指向正确的位置。
Angular 和 React 都依赖 Webpack 来完成诸如将 TypeScript 转换成 JavaScript 和运行本地开发服务器之类的事情。Webpack 还能够让您添加一个文件,这样您就可以解决这个问题。
在package.json文件的同一层上,创建一个名为proxy.conf.json的新文件。这个文件告诉 Angular 当它试图调用一个 REST 服务时应该去哪里查看。呼叫将被重新路由到您在此文件中指定的服务器。
一旦创建了这个文件,添加一个 JSON 对象,该对象指向您想要使用的端点、服务器的 URL,以及这是否是一个安全调用。您的代码应该类似于清单 11-7 。
{
"/boroughts/*":{
"target": http://localhost:3000/boroughs",
"secure": false,
"loglevel": "debug",
"changeOrigin": true,
"pathRewrite": {"^/boroughs": ""}
}
}
Listing 11-7The Body of the proxy.confg.json File
有了这个文件,您现在需要告诉 Angular 应用它需要在开发中使用这个文件。这样,当您在本地机器上测试您的 web 服务时,您可以在 Angular 应用中获得结果。
为此,您需要打开angular.json来更新应用在开发模式下的运行方式。你可以搜索“服务”这个名字在options对象中,可以在browserTarget属性下添加一个产权。这一行应该是这样的:
"proxyConfig": "proxy.conf.json"
记住保存文件,如果需要,重新启动应用。
在高层次上,您已经使应用知道,当服务调用/boroughs端点时,使用代理并将调用重新路由到具有相同端点的节点服务器。
重要的是要记住,即使你的 Angular 应用和 Node 应用运行在同一台机器上,它们也是两个独立的应用。
Angular 应用使用自己的服务器在开发人员的机器上显示站点。Node 应用也运行在自己的服务器上。这两个应用不知道对方正在运行。通过使用代理,您能够在这两个服务器之间架起一座桥梁,并将结果从一个应用返回到另一个应用。
此示例适用于本地开发,您可以将 Angular 应用指向本地服务器,甚至远程服务器来检索您的数据;当您将应用部署到生产环境中时,情况就不同了。有关如何将您的应用部署到生产环境中的更多信息,请查看位于 https://angular.io/guide/deployment 的 Angular 文档的部署部分。
现在,代理使您能够连接两个服务器,您可以更新组件中的结果,并显示从节点服务器返回的信息。
在服务中,将您的GET方法的 URL 更改为/boroughs。无需做任何更改,现在您可以在浏览器中查看控制台,并看到节点 API 发送回一个对象数组作为结果。在您的例子中,数组中只有一个对象。见图 11-5 。
图 11-5
浏览器控制台中显示的节点 API 的结果
来自节点服务器的结果为您提供了一组要处理的对象。在这种情况下,因为只有一个结果返回,所以很容易将第一个元素(元素 0)的值赋给变量,然后显示该对象的属性。
虽然这可行,但它不可扩展。如果要在数据库中添加更多的项目,该实例中的代码将不会考虑所有需要显示的新项目。您可以做的是分配一个循环来显示所有可用的结果。
在 HTML 文件中为你的组件(list-boroughs-component.html ) ,你需要更新内容。在这里,您将从角度查看您的数组,并遍历结果。
对于这个例子,您将使用*ngFor指令来循环处理结果,并在屏幕上显示它们。当您更新文档时,页面应该类似于清单 11-8 。
<div *ngFor="let result of results">
Id = {{ result.id }}
</div>
<div *ngFor="let result of results">
name = {{ result.name }}
</div>
<div *ngFor="let result of results">
state = {{ result.state }}
</div>
Listing 11-8Updated list-boroughs-component.html Using the *ngFor Directive
这里有三个实例将为这个对象的每个属性生成div标签。浏览器中的结果应该如图 11-6 所示。
图 11-6
使用*ngFor 指令时浏览器中的结果
现在您有了一个 Angular 应用,它可以使用服务从 web 服务请求信息。在开发应用时,您可以使用代理文件将请求重定向到能够满足请求的节点服务器。
当结果返回到组件时,您会收到一个对象数组。然后使用*ngFor指令显示结果列表。
既然您已经能够连接到您的服务,那么您应该做一些事情。第一件事是制作一些视觉效果。您可以添加 Twitter Bootstrap 作为可视化框架,以帮助您的应用看起来更好。
您要做的最后一件事是创建一个允许您在数据库中创建新信息的表单。这将需要对节点应用和角度应用进行一些更新。
首先,让我们使用 Twitter Bootstrap 添加一些样式。下一节将展示如何将这个可视化框架添加到 Angular 应用中。
将 Twitter 引导添加到 Angular 应用中
Twitter 创建了一个开源框架,让你为你的网站创建一致的视觉效果。Bootstrap 使您能够开发一个具有一致的排版、表单和按钮的站点。
本节将展示如何将引导程序添加到您的应用中。您还将创建一个表单元素,该元素将使用 Bootstrap 来赋予它样式。为了更好地了解 Bootstrap 以及您可以用它做什么,请访问主页 https://getbootstrap.com/ 。
为了让您的 Angular 应用具有 Bootstrap 所能提供的视觉一致性,您需要将它安装到应用中。
在命令行中,使用 NPM 安装 Bootstrap、jQuery 和 Popper。在应用的基础上,在命令行中键入以下内容:
npm install bootstrap jquery popper
安装完成后,打开angular.json文件。样式和测试部分需要更新,以引用 Bootstrap 提供的 CSS 文件。这可以在node_modules文件夹中找到。在脚本部分,添加引导节点模块的路径。样式和脚本部分应该如下所示:
"styles":{
"src/styles.scss",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
}
"scripts":{
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
}
重新启动应用,你应该会看到字体的变化。为了确保这是可行的,将其中一个div标签改为一个h1标签。当应用重启时,你的页面应该如图 11-7 所示。
图 11-7
在组件中使用 Bootstrap 的 H1 标签
现在,Bootstrap 已经成为应用的一部分。您可以利用 Bootstrap 提供的布局和格式化功能。
在下一节中,您将创建一个表单。该表单将是您在数据库中添加新信息的方式。
在 Angular 中创建一个简单的表单,并用 Bootstrap 对其进行样式化
你在这一章中取得了很大的成就。您创建了自己的应用,开发了 UI 组件,并创建了一个调用节点服务器的服务。进行调用时,Node 会将结果发送回 Angular 应用,您可以在屏幕上显示结果。
现在,您将向该应用添加表单。Angular 有两种不同的表单处理方式:反应式表单和模板驱动表单。这个例子将使用模板驱动的表单。
当您赋予应用调用远程服务器的能力时,您必须将HttpClientModule添加到app.module.ts文件中。这增强了整个应用的功能。对于这个例子,您需要对表单做同样的事情。
重新打开app.module.ts文件,从@angular/forms导入FormsModule。将imports语句与其他语句一起添加到文档的顶部。
Import { FormsModule } from '@angular/forms';
还将模块添加到imports数组中。一旦完成,在boroughs.module.ts文件中做同样的事情。
现在,您可以返回组件,使用普通的 HTML 5 语法添加表单。你的表格会很简单。您希望有人在两个字段中输入文本,name和state *。*你将使用 Bootstrap 给这个表单添加一些形状。见清单 11-9 。
<div class="container-fluid">
<form #boroughForm="ngForm" (ngSubmit)="submitForm(boroughForm.form);">
<div class="row">
<div class="col">
<label for="boroughName">
Borough Name:
<input type="text" [(ngModel)] = "model.boroughName" id="boroughName" name="boroughName">
</label>
</div>
<div class="col">
<label for="state">
State:
<input types="text" [(ngModel)] = "model.state" id="state" name="state">
</label>
</div>
<div class="col">
<button class="btn btn-primary" type="submit">Submit</button>
</div>
</div>
</form>
</div>
Listing 11-9Updating list-borough-component.html with a HTML 5 Form and Using the ngModel Directive
这是您表单的模板。您正在使用 Bootstrap 创建行和列,就像网格一样。你也可以使用 Bootstrap 的 CSS 类来赋予你的 Submit 按钮一些风格。
在form标签中,使用 Angular 语法创建一个名为boroughForm的变量;在那之后,你给事件onSubmit?? 分配一个函数。
当点击提交按钮时,该事件被触发并调用函数submitBorough *。*当这个函数被调用时,你传递你的变量对象和form属性。您的函数在组件内部被解析。
您还有一个使用角度指令ngModel的表单。它将模板绑定到组件内部的一个对象。这个对象有两个属性,boroughName和state??。
现在创建一个具有这两种属性的类。然后,您可以将代码连接到模板。
在命令行中,创建一个类。这个类有两个与模板匹配的属性。类型
ng generate class boroughs/model/borough
这将进入boroughs文件夹,创建一个名为models的文件夹,并生成一个名为borough.ts的文件。在这个类中,你需要添加两个公共属性。
打开文件。在构造函数之前,添加两个公共属性。它应该类似于清单 11-10 。
export class Borough {
public boroughName:string;
public state:string;
constructor (){
}
}
Listing 11-10Creating a borough Class
这个类为您提供了一个保存输入到表单中的值的地方。接下来,将这个类导入到list-boroughs.component.ts文件中,就像其他类一样,然后创建一个名为model的类实例来匹配模板。
下面是代码的一部分:
import { Borough } from ../model/borough;
private model: Borough = new Borough();
constructor (private boroughService: BoroughService) {
}
submitBorough(value) {
console,log(value);
}
您的类现在应该已经导入了新创建的borough类。如果您单击 Submit 按钮,结果应该会显示在浏览器内的控制台中。通过使用ngModel,您可以将表单的值直接绑定到对象,而无需编写任何额外的代码。然后结果变成控制台中的一个对象,该对象应该包含表单中的值。
有很多种函数可以添加到表单中。可以添加验证和用户反馈,以确保在提交给服务器之前,有人在字段中键入了某些内容,以及任何符合您需要的格式的内容。
在这种情况下,您只想确保从表单中捕获数据,并让 Angular 控制它。在下一节中,您将把数据传递回本地节点服务器并更新数据库。
将信息从角度传递到节点
你已经准备好了你的前端。Bootstrap 为你的页面结构和表单提供了一些风格。您无需编写任何自定义 CSS 就可以获得这一点。
使用 Angular 的一些指令,您可以将 HTML 模板中的数据绑定到 Typescript 组件代码,并检测表单的 Submit 按钮何时被单击。
在前面的例子中,您创建了一个从本地 MySQL 数据库中检索数据的服务。现在,您需要在服务中创建一个新的函数,将信息发送回节点。然后,服务器将提取数据并将其插入数据库。
您需要向您的 Express 应用添加一个库,以便它可以接受传入的数据。你所有的节点例子都是基于第十章所做的工作。如果这些看起来不熟悉,请参考那一章。如果您正在运行 Node,您可以停止服务器。在命令行中,添加该库:
npm install body-parser –save
当 Angular 发出 POST 请求时,这个库允许 Node 检索进入服务器的数据。
我已经介绍了 REST 动词的概念,比如使用GET从数据库中检索信息,现在您可以使用POST向数据库中添加新信息。
将这个库安装到您的节点应用中后,您现在可以更新您的app.js文件来利用它。以下是更新的部分视图:
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true });
您现在需要的是让服务器接受 Angular 应用发出的POST调用。为此,从controllers文件夹中打开boroughs.js文件。
这里有您的原始代码,它响应了一个GET请求。您现在可以添加响应POST请求的能力。
在您的新函数中,您将提取发送过来的信息,并将其分配给局部变量。您还将创建一个对象来表示正在更新的列和数据。最后,您将运行 SQL 查询来更新数据库。
更新后的boroughs.js文件应包含以下代码:
router.post('/' , (req, res) => {
let boroughName = req.body.boroughName;
let state = req.body.state;
let records = {name: boroughName, state: state};
pool.query('INSERT INTO boroughs SET ?', records, (error, results) => {
if (error) throw error;
});
});
您创建了一个允许您与节点服务器对话的代理。对于本地开发,您继续使用该代理连接到节点服务器,并传递来自 Angular 表单的数据。在节点端,提交到表单中的数据现在变成了body对象的属性。
属性包含键入表单的值。现在,您可以提取这些值,并将它们保存为局部变量。
然后,您可以创建一个对象,该对象将把列与要插入到数据库中的值相匹配。最后一件事是运行 SQL 查询。
当您从数据库中获取数据时,其中一项是数据库中该项的 ID。通过在查询中使用INSERT来添加新数据,数据库将自动为添加到数据库中的每个项目创建一个新的 ID 号。这很重要,因为这意味着您不需要跟踪每个项目的唯一 ID。
数据库准备好插入新数据,节点服务器准备好接收新数据,现在需要更新 Angular 应用来发送信息。打开borough.service.ts,添加一个名为addBorough的函数。它应该是这样的:
addBorough(value) {
return this.http.post('/boroughs', value,value);
}
就像GET示例一样,您使用 Angular 提供的 HTTP 客户端来进行您的POST调用。然后你把你的参数value,它是一个对象(你可以把名字改成任何你喜欢的)。这个对象有一个名为value的属性,这就是你发送给服务器的内容。
您需要更新您的组件,以便它可以向服务发送数据。打开list-boroughs.component.ts。最近添加的功能是submitBorough。您需要更新此功能,以便利用更新后的服务。该函数现在应该如下所示:
submitBorough(value) {
//console.log(value);
this,.boroughService.addBorough(value).subscribe((results) => {
console.log(results);
});
}
现在,当提交表单时,在文本字段中键入的信息将被发送到服务器。然后,服务器会在数据库中添加一条新记录。
当您测试应用时,您应该看到数据库已经更新。为了确保数据库已经更新,您可以查看 PHPMyAdmin 并查找之前创建的表。您还可以更新模板,以便获得数据库中的所有结果。
您要进行的最后一次更新是将新数据检索到同一页面中。这时,让我们把原来在ngOnInit中的函数移到一个名为getBoroughs的新函数中。你可以把这个电话作为一个整体,转移到你的新功能。它应该是这样的:
getBoroughs(){
this.boroughService.getBoroughs().subscribe((data) => {
this.results = data;
});
}
您有一个函数,当它被激活时,将进行相同的调用并检索数据库中的所有信息。最后,您需要更新 HTML 模板,以便在屏幕上显示所有结果。本次更新将利用上一课的内容。您将添加一个具有新按钮的新行,当该按钮被单击时,它将调用您的新函数并更新模板的后半部分。
新行应该如下所示:
<div class="row">
<div class="col-1">
<button class="btn btn-primary" (click) = 'getBoroughs()'>Get Boroughs</button>
<div>
<div class="col-11">
<div *ngFor = "let result of results">
id = {{ result.id }}
name = {{ result.name }}
state = {{ result.state }}
</div>
</div>
</div>
新行中有一些小的不同。Bootstrap 的工作原理是将屏幕分成 12 列网格。因此,您在第一列中添加按钮,结果在一列中横跨其他 11 列的宽度。
您使用与上一个示例相同的循环。当你的用户点击按钮时,Angular 会看到点击事件发生了。它将调用组件中定义的getBoroughs函数。你应该在屏幕上看到结果,类似于图 11-8 。
图 11-8
角度插入和显示信息
摘要
这一章涵盖了很多内容。你被介绍到了角度框架。您了解了 Typescript 与 JavaScript 的细微差别,以及如何在 Angular 应用中使用它。
您使用命令行界面为您的应用创建了一个新的 Angular 应用、组件、类和服务。您还创建了一个代理,允许您的 Angular 应用与您的节点服务器对话,就好像它们运行在同一个端口上,而不是两个独立的实例。
使用 Express 框架,您找到了一种连接到 MySQL 数据库并运行查询的方法,该查询将结果发送回 Angular 应用。您还运行了一个查询,将数据从 Angular 应用插入到数据库中。
Angular 提供了一长串现成的函数,可以用来构建应用。
下一章将介绍如何使用 React 做同样的事情。React 没有 Angular 拥有的所有特性,但它是一个值得学习的技能。