Angular5-项目教程-全-

Angular5 项目教程(全)

原文:Angular 5 Projects

协议:CC BY-NC-SA 4.0

一、Web 应用和 AJAX 通信

这本书是为对 web 开发有非常基础的知识的开发人员编写的。它不需要预先安装软件,但是在后面的章节中,您将需要安装软件来运行示例代码。该书提供了如何在需要时下载和安装软件的信息。

在我们深入 Angular 之前,我想介绍一些 web 开发的基本概念。本章介绍了 web 应用的基本架构,以及它如何将数据从服务器传递到 web 浏览器。它还介绍了一些工具,在调试服务器和 web 浏览器之间的通信时,这些工具可能会让您的工作变得更加轻松。

更有经验的开发人员可以跳过这一章。

客户端和服务器简介

Web 应用基本上包括两台相互通信的计算机,称为服务器和客户端。该概念如图 1-1 所示。

A458962_1_En_1_Fig1_HTML.jpg

图 1-1

Client/server architecture

该服务器位于公司办公室或数据中心,监听 HTTP 请求,并通过应答进行响应。服务器还访问 web 应用使用的数据(存储在数据库中)。

用户使用他们的 web 浏览器与 web 应用进行交互。用户的计算机与服务器通信,发送 HTTP 请求并接收回答。客户端电脑可能是各种机器,从智能手表到手机到平板电脑再到电脑。

在 web 上,客户端和服务器使用 HTTP(超文本传输协议)进行通信。HTTP 是客户端和服务器之间的请求-响应协议。第二十章详细介绍 HTTP。

服务器端 Web 应用

在服务器端 web 应用中,大部分应用在服务器上执行,而客户端仅用于一次显示一个 HTML 页面。当用户在 web 应用中执行一个操作时,客户机向服务器发送一个请求,服务器执行一些操作,并返回一个全新的 HTML 页面作为响应显示在客户机上。每次都重新生成网页,并发回显示在客户端的 web 浏览器上,如图 1-2 所示。

A458962_1_En_1_Fig2_HTML.jpg

图 1-2

Server-side web application

客户端 Web 应用

客户端 web 应用(也称为单页应用,或简称为 SPAs)是一个较新的现象,计算行业正在更多地向这种模式发展。这里,许多应用仍然在服务器上执行,但是一些代码也在客户机(web 浏览器)上执行,以避免频繁地重新生成页面。当用户在客户端执行一个操作时,它向服务器发送一个请求,服务器执行一些操作并返回结果信息——而不是一个全新的 HTML 页面。客户端代码监听来自服务器的回答,并在不生成新页面的情况下自己决定如何响应。客户端 web 应用往往更具交互性和灵活性,因为它们可以更快地响应用户交互——它们不必等待服务器发回同样多的数据。他们只需要等待服务器返回结果,而不是整个 HTML 页面。该架构如图 1-3 所示。

A458962_1_En_1_Fig3_HTML.jpg

图 1-3

Client-side web application

取得平衡

因此,基本上有两种类型的 web 应用:服务器端和客户端(SPA)。如果这些被认为是黑白的,你的 web 应用应该在中间的某个地方,在“灰色”区域。

服务器端应该保留聪明东西的存储库——业务规则、数据存储和设置应该保留在服务器上,并在需要时从客户端调用或检索。

客户端(浏览器)应该使用更现代的客户端技术来避免整页刷新。但是,不能太聪明,也不能太臃肿。它应该知道足够多的信息来完成与用户交互的工作,仅此而已。它应该调用服务器端的代码来做智能的事情或执行业务流程。它不应该有太多的业务逻辑、内部系统数据(除了用户可以查看或修改的数据之外的数据)或硬编码信息,因为这些在服务器上管理更好。

Caution

你必须避免把“除了厨房水槽以外的所有东西”都扔给客户。

用 AJAX 创建 Web 应用

AJAX 代表异步 JavaScript 和 XML。AJAX 是一种借助 XML、HTML、CSS 和 JavaScript 创建更好、更快、更具交互性的 web 应用的技术。

当客户端 web 应用需要与服务器通信时,它使用 AJAX 发送一些东西,并等待结果返回。请记住,它返回的结果只包含数据,而不是一个全新的网页。此外,客户端代码在等待时不会停止运行,因为它仍然需要显示用户界面并响应用户。这是 AJAX 的异步部分。

客户端 web 应用使用 JavaScript 来调用 AJAX 请求并对其做出响应。这是 AJAX 的 JavaScript 部分。

AJAX 请求过去使用 XML(可扩展标记语言)作为在客户机和服务器之间来回传递的请求和结果数据的数据格式。如今,AJAX 倾向于使用 JSON (JavaScript 对象表示法)作为数据格式,而不是 XML。这是因为 JSON 更紧凑,更直接地映射到现代编程语言中使用的数据结构。但是 XML 和 JSON 都是以文本形式传输数据的常用格式。

前面,我使用了术语异步。你可以这样看待异步:你打电话给你的配偶请求帮助。他们的电话占线,所以你留言让他们在超市停一下,给你买一箱啤酒。同时,你继续看电视——因为这些事情是异步发生的。这一进程的成果将包括以下内容:

  • 成功:配偶给你回电话,告诉你啤酒在路上。
  • 失败:配偶打电话给你,告诉你商店关门了。

在 AJAX 中,客户端代码在等待服务器响应时不会停止运行,就像你在等待配偶回电时不会停止看电视一样。

回收

通常,当您进行 AJAX 调用时,您必须告诉它在收到服务器响应时该做什么。AJAX 系统代码在收到响应时应该触发的这段代码称为回调。

当您执行 AJAX 操作时,您用参数和一两个函数调用 AJAX 代码——回调。回调有两种类型:

  • Success:如果服务器响应成功,并且客户机收到正确的回答,则调用 success(或 done)回调。
  • failure:fail 或 error 回调是可选的,如果服务器返回一个错误(或者 AJAX 调用无法与服务器通信),就会调用这个回调。

承诺

有时你调用 AJAX 代码,它会返回一个承诺或延期。承诺是来自 AJAX 操作的“响应承诺”的对象。当您收到一个承诺时,您可以向该承诺注册您的成功或失败回调,使该承诺能够在成功或失败发生时调用回调。

编码

当您使用 AJAX(或者客户端和服务器之间的其他通信)时,您需要确保信息以适合传输的形式发送。你可以通过编码来实现。如果不使用编码,很有可能某些信息不会像发送时那样被准确接收。对于一些特殊的字符信息尤其如此,例如空格、引号等等。

表 1-1 列出了信息编码的三种主要方法。

表 1-1

Three Main Methods of Encoding Information

| 方法 | 笔记 | | :-- | :-- | | 编码器(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器)(编码器) | 这对于用特殊字符的转义序列将整个 URL 编码成 UTF 8 非常有用。它以与 encodeURIComponent 相同的方式对字符串进行编码(见下一项),只是它不涉及组成 URL 路径的字符(如斜杠)。例如: [`http://www.cnn.com`](http://www.cnn.com) 转换为`http://www.cnn.com%0A`。 | | 中文 | 这对编码参数很有用。它不适合编码整个 URL,因为它可以用转义序列替换重要的 URL 路径信息。例如: [`http://www.cnn.com`](http://www.cnn.com) 转换为`http` %3A%2F%2F www.cnn.com%0A。 | | 逃跑 | 这将返回一个包含[参数]内容的字符串值(Unicode 格式)。使用时要小心,因为默认情况下,服务器不会接收 Unicode 格式的数据。例如: [`http://www.cnn.com`](http://www.cnn.com) 转换为`http%3A//www.cnn.com%0A`。 |

为了测试这些方法,请前往 http://pressbin.com/tools/urlencode_urldecode/ 。图 1-4 显示了这个网络界面的样子。

A458962_1_En_1_Fig4_HTML.jpg

图 1-4

Web page that displays different encodings for what you type

哈尔和哈特奥斯

为了与服务器通信,客户端需要知道服务器在哪个 URL 上可用。这些信息不应该硬编码在客户端上。相反,服务器应该告诉客户端使用什么 URL 来获取信息。将这些信息发送回客户端的格式有多种标准,包括 HAL 和 HATEOAS。

例如,如果客户机向服务器发送一个 AJAX 请求来检索客户列表,那么返回的信息应该包括每个客户的 AJAX 请求的 URL。这避免了在客户机上硬编码客户 AJAX 请求 URL。你可以分别在 https://martinfowler.com/articles/richardsonMaturityModel.htmlhttps://en.wikipedia.org/wiki/HATEOAS 阅读更多关于 HAL 和 HATEOAS 的内容。

监控数据流量

您的网络浏览器内置了开发工具。其中一个工具是网络工具,它允许您监控客户端和服务器之间的数据流量。该数据流量以带有时间线的列表形式呈现,如图 1-5 所示。您可以选择列表上的一个项目来更详细地查看它,并确切地看到哪些数据被发送到服务器,哪些数据被返回。您可以过滤想要跟踪的网络流量类型。例如,您可以选择“XHR”来查看 AJAX 请求。

A458962_1_En_1_Fig5_HTML.jpg

图 1-5

Viewing data traffic with the network developer tool in the Google Chrome browser

Fiddler 是一个免费的 web 调试代理,其工作方式类似于浏览器开发工具中的网络选项卡(见图 1-6 )。Fiddler 有一些额外的功能,比如创建自己的 AJAX 请求和运行脚本。在 www.telerik.com/fiddler 阅读更多关于提琴手的信息。

A458962_1_En_1_Fig6_HTML.jpg

图 1-6

Viewing data traffic with Fiddler

邮差很像提琴手( www.getpostman.com )。两者都很有用。

分析 JSON

您经常会收到来自服务器的很长的 JSON 响应,并且需要遍历响应数据来提取您需要的数据。您的响应数据通常会作为参数传递给 AJAX 成功回调函数。以下是检查这些数据的一些提示:

  • 将其转换为字符串:可以调用 JSON.stringify 函数将响应数据转换为字符串。这将使您能够在成功回调时将其输出到控制台,如下所示:

    function success(data){
     console.log('success - data:' + JSON.stringify(data));
     //
     // do something with data
     //
    }
    
    
  • 将 JSON 数据复制到控制台之外:要将 JSON 数据复制到剪贴板,请执行以下操作:

    1. 打开你的浏览器。
    2. 转到开发者工具菜单。
    3. 单击控制台操作。
    4. 选择 JSON 文本。
    5. 右键单击并选择复制。
  • 格式化 JSON 数据,使其更具可读性:现在,您已经将 JSON 数据放在剪贴板中,您可以将它复制并粘贴到网站中,使其更具可读性:

    1. 打开你的浏览器。

    2. Go to https://jsonformatter.curiousconcept.com (or a similar service—there are lots of these). Figure 1-7 shows what this website looks like.

      A458962_1_En_1_Fig7_HTML.jpg

      图 1-7

      Formatting JSON data

    3. 将 JSON 粘贴到大文本框中。

    4. Click the Process button. The website will show you the JSON data in a validated, formatted, easy-to-read web page, as shown in Figure 1-8. You can even view the JSON full-screen.

      A458962_1_En_1_Fig8_HTML.jpg

      图 1-8

      Formatted JSON data

  • 复制 JSON 数据并将其粘贴到您的编辑器中:然后您可以应用编辑器的格式命令。您可能需要首先将该文件保存为. js 文件,以确保将其格式化为 JavaScript。

摘要

近年来,web 应用的世界已经发生了很大的变化。客户端应用(也称为 spa)变得越来越普遍。在本章中,我们看到 SPA 最重要的方面之一是客户端(浏览器)和服务器之间的 AJAX 通信。

作为一名开发人员,了解如何使用 web 浏览器开发工具的网络部分非常重要,这样您就可以调试 AJAX 通信。您可能还需要知道如何使用其他工具,如 Postman 或 Fiddler。

在下一章,我将介绍 Angular,并展示它是如何随着版本的变化而变化的。

二、Angular 旧的和新的

在学习 Angular 之前,了解一下称为 AngularJS 的原始版本,并谈谈第一个版本和后来的版本之间最重要的差异,会有所帮助。

以下是一些关于 AngularJS 和 Angular 的基本事实:

  • AngularJS 于 2009 年发布,是最初的 Angular。
  • 这是一个动态 web 应用的 JavaScript 框架——不需要重新加载页面。动态 web 应用也称为 SPAs(单页应用)。
  • 它很流行用在任何浏览器上都能快速运行的小部件来创建网页。
  • 它允许用户扩展 HTML 来添加特定领域的标签,比如<CAR>
  • 它允许用户将数据从模型绑定到特定于 HTML/域的标签。
  • Angular 2 于 2009 年和 2014 年开发。
  • 谷歌于 2014 年 9 月宣布开发 Angular 4,并于 2015 年 1 月进入测试阶段。
  • Angular 4 发布于 2017 年 3 月。
  • Angular 5 于 2017 年 11 月发布。

AngularJS 像野火一样迅速发展起来,因为它是快速构建应用原型的一个很好的工具。它也很灵活,因为你可以使用 HTML 来创建页面,并在此基础上快速构建,将静态的 HTML 变成一个移动的、反应灵敏的、性感的应用。以下是如何:

  1. 获取一个 HTML 模板,修改一些 HTML 代码元素来添加数据绑定。数据绑定允许可视控件(如文本框、选择框等)将其值与变量同步。例如,您可以将一个city变量绑定到一个“城市”文本框。如果用户在文本框中输入内容,那么city变量的值将被更新。如果代码改变了city变量的值,“城市”文本框会更新以匹配
  2. 添加 JavaScript Angular 控制器:
    1. 为要绑定到的 HTML 标记添加变量。
    2. 添加行为 JavaScript 代码(响应按钮点击等事件的代码)。

搞定了。

很明显,您可以做更多的事情,但是关键是开发人员可以很快地将原始的 HTML 转换成一个有效的、响应迅速的应用。

语义版本控制

Angular 2、4 和 5 非常相似,它们都与原始 AngularJS 非常不同。很奇怪,我们有了好几年的 AngularJS,然后在很短的时间内有了 Angular 2,4 和 5。这是因为 Google 的人决定从版本 2 开始实现语义版本化。语义版本化是软件版本化的新标准,它现在如此流行的原因是版本号(或版本号的变化)提供了关于自上一版本以来所做的变化的信息。

使用语义版本化,版本号被分成三个部分,每个部分用句点分隔。

[主要版本号]。[次要版本号]。[补丁版本号]

因此,当 Angular 从 4 更改为 5 时,这是主版本号的更改。

主要版本号的变化表明软件在很大程度上发生了变化,这意味着您过去可以工作的代码可能不再工作,因为 api 已经发生了变化。

次要版本号更改表示软件被更改,但更改的方式允许您的代码仍然可以工作。

补丁版本号是用来修正错误的,一切都应该正常。

Angular 5 是 Angular 4 的基础,有许多小的改进,其中一些改进导致了 api 的修改。如语义主要版本号变化所示,当从 4 转换到 5 时,您的代码可能需要修改。从 4 到 5 最重要的变化包括:

  • http 模块的修改(这已经包含在 Angular 版本中)。
  • 构建优化器已经过修改,可以生成更小、更高效的部署模块。当您从 Angular 项目中部署文件时,这些文件将会更小。
  • 有新的工具可以从浏览器和服务器传输状态数据(反之亦然)。
  • 编译器被重新编写得更快更彻底。以前 Angular 写的是在运行你的 app 时使用 jit(即时编译)。当您加载组件和对象时,它们会在需要时被编译。Angular 现在更倾向于 aot 模型,在这种模型中,你的代码是提前编译的,而不是在需要的时候。5 中的这些编译器更新推进了向 aot 的转移,这将使您的应用运行得更快,因为它在运行应用时将执行更少的编译。
  • 改进了对多语言应用的国际化支持。

平台

AngularJS 运行在 web 浏览器上,web 浏览器运行 JavaScript,所以,JavaScript 是 AngularJS 和 Angular 的平台。

术语 evergreen 浏览器指的是自动升级到未来版本的浏览器,而不是像旧浏览器那样通过制造商发布的新版本进行更新。这个术语反映了浏览器的设计和交付在过去几年中的快速变化。现在广泛使用的浏览器都是常青树,自己更新。

浏览器使用 JavaScript 引擎运行 JavaScript

我们曾经认为网络浏览器和它运行 JavaScript 的能力是一回事。自从 Node(它使用 Google Chrome 的 JavaScript 引擎在远离浏览器的地方运行程序)以来,这种情况已经发生了变化,你可以在远离浏览器的地方独立运行这些引擎。

JavaScript 引擎是一个执行 JavaScript 的程序或解释器,可以利用 JIT(实时)编译成字节码。自 AngularJS 以来,JavaScript 引擎随着 ECMA JavaScript 的新版本(ECMA 指的是版本)而稳步改进。AngularJS 运行在运行名为 ECMA5 的 JavaScript 版本的网络浏览器上。现在大多数浏览器运行的都是更高版本。随着 ECMA6(也称为 ECMA 2016),JavaScript 朝着成为像 Java 或. NET 一样的结构化、类型化语言迈出了一大步。两个更重要的变化是用于创建类和模块的新语法,这对于本书来说很重要也很相关。

您可能知道,客户端 JavaScript 的世界变化很快。ECMA 维基百科页面定期更新最新信息: https://en.wikipedia.org/wiki/ECMAScript

垫片和聚合填料

填充和聚合填充是软件组件,旨在允许旧浏览器运行更现代的代码。shim 是一段代码,它拦截浏览器上的现有 API 调用并实现不同的行为,从而实现跨不同环境的标准化 API。因此,如果两个浏览器以不同的方式实现相同的 API,您可以使用一个填充程序来拦截其中一个浏览器中的 API 调用,并使其行为与另一个浏览器保持一致。polyfill 是一段 JavaScript,它可以将缺失的 API“植入”旧浏览器。例如,填充和聚合填充使旧的 ECMA5 浏览器能够运行 ECMA6 代码。

以打字打的文件

在 AngularJS 和 Angular 的出现之间,JavaScript 得到了改进,变得更像一种结构化语言。但是您可以更进一步,使用 TypeScript 语言,它是结构化的,甚至更像 Java 之类的语言。NET 和 C#。事实上,TypeScript 是由微软开发的,是 JavaScript 的改进版。图 2-1 简洁地表达了 TypeScript。

A458962_1_En_2_Fig1_HTML.jpg

图 2-1

How to think of TypeScript

为什么 TypeScript 很重要?Google 使用 TypeScript 开发 Angular。因此 Angular 和 TypeScript 语言是一个很好的组合,我们将在本书中对此进行大量讨论。

蒸发

TypeScript 如何在 web 浏览器上运行?嗯,没有,至少目前没有。使用一个称为 transpilation 的过程将 TypeScript 转换回兼容的 JavaScript。翻译程序是将一种语言的源代码转换成另一种语言的源代码的软件。例如,TypeScript、CoffeeScript、Caffeine、Kaffeine 和二十多种其他语言都被转换成了 JavaScript。

如果你想直接看到 transpilation,请查看 www.typescriptlang.org/play/ 并查看一些示例。如果您从该网页的弹出框中选择 Using Classes,您可以看到现代的 TypeScript 类是如何转换成兼容的 JavaScript 的。

清单 2-1 显示了您将为一个 TypeScript 类编写的代码,清单 2-2 显示了到 JavaScript 的转换。

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

Listing 2-1TypeScript Class

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
}());
Listing 2-2Transpiled to Browser-Compatible JavaScript

调试和映射文件

因此,您以一种方式编写代码,以另一种方式部署它——这一定是调试的噩梦,对吗?是的,如果你没有地图文件,调试将是一场噩梦。映射文件由 transpiler 自动生成,并为浏览器提供将原始(TypeScript)代码映射到已部署(JavaScript)代码所需的信息。这意味着 JavaScript 调试器可以让您调试源代码,就像浏览器正在运行它一样。这有多酷?如果你让。地图文件在您的浏览器中,它会自动寻找它们,拿起它们,并使用它们。我用。我在 Chrome 调试的时候一直在映射文件。

地图文件执行以下操作:

  • 将合并/缩小/传输的文件映射回未构建状态。
  • 将浏览器中的 JavaScript 代码行映射回 TypeScript 代码行
  • 使浏览器和调试器能够显示您在 TypeScript 中编写的原始代码并对其进行调试

蒸腾作用和

有许多方法可以设置您的项目,将您的 TypeScript 代码转换成浏览器友好的 JavaScript。这完全取决于您的项目设置。在这方面你有很多选择,这可能会变得复杂和混乱。

我建议您开始使用 Angular CLI 工具。这个工具可以非常简单地生成具有简单构建过程设置的现成项目,包括 transpilation。它在大型项目中也很有用。

模块

模块这个词指的是独立的、可重用的软件代码的小单元,例如,执行动画的代码。我想到的是乐高积木之类的模块(图 2-2 )。每个块都有自己的用途,但是被插入到一个更大的结构(应用)中。

AngularJS 有自己的模块系统,使用简单。那时候 JavaScript 还没有自己的模块化代码的体系。Angular 有自己的模块系统将 Angular 代码打包成模块,还有现代的 JavaScript 模块。

A458962_1_En_2_Fig2_HTML.jpg

图 2-2

Modules are like software LEGO blocks

不要担心,稍后会更详细地介绍这些内容。

控制器和组件

AngularJS 使用控制器来表示 HTML 页面上用户界面中的小部件。

Angular(从版本 2 开始)用Component对象替换控制器。组件可以有自己的标签,比如<Component1>。组件有一个包含数据和代码的类。

第八章更详细地介绍了组件。组件是 Angular 5 应用的构建块。

依赖注入和构造函数注入

正如我提到的,作为 Java Spring 的一员,我喜欢依赖注入,因为它让生活变得更简单。我们可以在这个主题和依赖注入提供的好处上花费大量的篇幅。

AngularJS 提供了依赖注入。现代 Angular 还提供依赖注入。因为您的组件有类,所以依赖项现在通常通过构造函数注入,使用构造函数注入模式。这种软件模式是另一种服务器端技术,现在正用于客户端。让我们看一个 Java Spring 使用构造函数注入的例子。以下配置指定了一个构造函数参数—一个字符串消息,"Spring is fun":

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="message"
          class="org.springbyexample.di.xml.ConstructorMessage">
        <constructor-arg value="Spring is fun." />
    </bean>

</beans>

以下 bean 类期望在构造函数中接收消息:

public class ConstructorMessage {

    private String message = null;

    /**
     * Constructor
     */
    public ConstructorMessage(String message) {
        this.message = message;
    }

    /**
     * Gets message.
     */
    public String getMessage() {
        return message;
    }

    /**
     * Sets message.
     */
    public void setMessage(String message) {
        this.message = message;
    }

}

这有什么了不起的?在这种情况下,这是一个字符串的简单例子。但是它展示了一个软件对象(在本例中是一个字符串对象)是如何使用构造函数“插入”另一个软件对象的。

例如,在 Angular 中,您可以创建一个可重用的软件对象来处理与服务器的通信。您可以通过构造函数将它传递给每个需要它的对象(类)。这样,在课堂上,你就有了与服务器对话的现成方法。

一次编写一个服务,在许多地方多次使用它。

范围、控制器和组件

在 AngularJS 中,Scope ( $scope)曾经是控制器的“数据容器”。您的变量将包含在$scope对象中。例如,如果您有一个地址输入表单的控制器,地址的每一行可能是控制器的$scope中的一个变量。

在 Angular 中,您不再拥有控制器,而是拥有组件,并使用这些组件来构建用户界面。您可以使用 composition 将组件嵌套在其他组件中。组件有一个类,类似于 Java 或. NET。这个类是“数据容器”,包含你的变量。这更像是传统的服务器端编码。例如,如果您有一个带有地址的输入表单组件,地址的每一行可能是组件类中的一个变量,类似于 Java swing(或 Windows Form)类。

  • 组件使用类来包含它们的变量和应用代码。
  • 类有实例变量、构造函数和方法。
  • 可以使用构造函数将依赖项注入到类中。
  • 实例变量可以绑定到模板,以创建一个响应用户界面。

第八章详细介绍了组件。

形式

编写代码来处理表单上的数据输入是很重要的。编写处理表单、数据输入和验证的 AngularJS 代码很容易,但 Angular 有新的表单模块,可以更容易地完成以下任务:

  • 动态创建表单
  • 用通用验证器验证输入(必需)
  • 用自定义验证器验证输入
  • 测试表格

模板

AngularJS 和 Angular 都使用 HTML 模板(见图 2-3 和 2-4 )。模板中的 HTML 被绑定到数据变量和代码上,以便创建一个工作的应用。不幸的是,模板语法出现了分歧。第十二章详细介绍了新的语法。

A458962_1_En_2_Fig4_HTML.jpg

图 2-4

Angular template

A458962_1_En_2_Fig3_HTML.jpg

图 2-3

AngularJS template

摘要

读完这一章后,你应该对 Angular 的不同版本有更好的理解。最初的 AngularJS 像野火一样迅速发展,因为它是编写跨浏览器应用的一种快捷方式,但它有一些不一致之处,需要更新才能使用更新的浏览器提供的功能。

更现代的 Angular 类似于 AngularJS,只是开发环境更简单,支持使用更新的 JavaScript 和 TypeScript。

Angular 依赖于 JavaScript 和 TypeScript。第三章介绍了 JavaScript 以及它是如何从一个版本变化到另一个版本的。

三、JavaScript 语言

当网景公司在 1995 年 4 月雇用布伦丹·艾希时,他被告知他有 10 天的时间来创造和制作一种可以在网景浏览器上运行的编程语言的工作原型。

十天来创造我们现在所知的 JavaScript!考虑到他被给予的时间,我会说他做得相当好。

JavaScript 在不断发展。目前,大多数 web 浏览器支持 JavaScript ES5,但 ES6 将在未来一两年内成为标准。

JavaScript ES5:局限性和缺点

这一节讨论了当前版本 ES5 之前的 JavaScript 中的限制和缺点。这些缺点在 ES6 中已经解决了,将在本章后面介绍。

类型

当对变量进行运算时,计算机可能知道也可能不知道所涉及的每个变量的类型。

如果类型是已知的,那么操作就很简单,因为操作非常具体。

示例:

const a: number = 123;
const b: number = 456;
const c: number = a + b;

如果不知道类型,事情就会变得更加复杂。计算机必须试图找出正在使用的变量的类型,或者将它们强制转换成预期的类型。逻辑可能会变得复杂。

示例:

var foo = 123 + "Mark";

答案是什么?

  • 123Mark
  • 错误—因为 123 是数字而"Mark"是字符串?

JavaScript 仅支持六种类型:

  • 不明确的
  • 布尔代数学体系的
  • 线
  • 数字
  • 目标

没错,只有一个号码类型。然而,有这么多不同类型的数字,包括整数和小数。我不认为我在类型方面做了什么,JavaScript 并没有削减它。

快速失败行为

代码要么应该准确地工作,要么应该快速(立即)失败。因为 JavaScript 的类型和规则很少,所以它经常会继续运行而不是失败,并带来奇怪的副作用。你认为不起作用的东西会起作用。

例如,以下代码不会失败:

alert((![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]);

值/对象比较

当你在 Java 或. NET 语言中比较两个变量时,你不需要成为一个火箭专家就能知道如何比较它们。您实现了一个.equals()方法。但是,因为 JavaScript 的类型很少,所以它使用复杂的逻辑来比较值或对象。要了解 JavaScript 如何比较变量,请看图 3-1 所示的等式算法。你可能想先吃一片泰诺。

A458962_1_En_3_Fig1_HTML.jpg

图 3-1

Abstract equality comparison algorithm

A458962_1_En_3_Figa_HTML.jpg

辖域

在 JavaScript 中,未声明的变量被隐式提升为全局变量。对我来说,这似乎是不合逻辑和危险的,因为肯定要有一个全局变量,你应该这样声明它?

在图 3-2 中,变量foo1是全局变量,变量foo2不是。当这段代码运行时,您只会看到一个提示框,显示“hello”你看不到第二个,因为foo2没有设置,因为它超出了范围,不是全局变量。

A458962_1_En_3_Fig2_HTML.jpg

图 3-2

Only one variable is shown in an alert

JavaScript 严格模式

JavaScript 严格模式在 ES5 中发布。它不会影响旧代码——换句话说,例如,如果在 ES4 中运行,使用strict mode 命令不会破坏 JavaScript 代码。严格模式旨在通过实施更好的编程实践来防止意外错误。

祈祷

"use strict"指令仅在脚本或函数的开头被识别。这种模式可以在两个不同的范围内运行:文件和函数。如果将该指令放在脚本文件的开头,该文件中的所有代码都将以该模式运行。如果将它放在函数的开头,函数中的所有代码都将以该模式运行。

我不能涵盖严格模式的每一个方面,但我会在本节中讨论其中的主要方面。

赋给未声明的变量或对象

当用户将一个值赋给一个未赋值的变量或对象时,严格模式抛出一个错误,防止创建一个非预期的全局变量(我将在本章后面详细讨论这个主题)。以下代码在严格模式下抛出错误:

"use strict";
pie = 3.14;

"use strict";
obj = {str:10, zip:30350};

删除变量或对象

严格模式不允许使用delete关键字删除变量或对象。以下代码在严格模式下抛出错误:

"use strict";
var pie = 3.14;
delete pie;

复制函数参数

严格模式不允许一个函数中有多个同名的参数。以下代码在严格模式下抛出错误:

"use strict";
function concat(word1, word1) {};

复制对象属性

严格模式不允许一个函数在一个对象中有多个同名的属性。以下代码在严格模式下抛出错误:

"use strict";
var obj = {
  prop1 : 0,
  prop2 : 1,
  prop1 : 2
};

只读属性

在 ES5 中,用户可以使用函数Object.defineProperties定义对象属性。该函数允许开发人员将一些属性定义为不可写的(即只读的)。在正常模式下,当代码试图写入只读属性时,代码不会引发错误。但是,在严格模式下,代码会在这种情况下抛出一个错误:

var obj = Object.defineProperties({}, {
              prop1 : {
                value : 1,
                writable : false
              }
            });

obj.prop1 = 2;

不可扩展的变量或对象

在 ES5 中,用户可以使用函数Object.preventExtensions来防止对象被扩展。在正常模式下,当代码试图扩展对象时,代码不会引发错误,但是在严格模式下,代码会在以下情况下引发错误:

"use strict";
var obj = {prop1 : 1};
Object.preventExtensions(obj);
obj.prop2 = 2;

关键词

严格模式引入了以下保留关键字,这些关键字不能在该模式下的代码中使用:

  • implements
  • interface
  • let
  • package
  • private
  • protected
  • public
  • static
  • yield

JavaScript ES6:变化和改进

JavaScript ES6 比 ES5 改进了很多。我不打算涵盖 ES5 和 ES6 之间的所有改进——只是主要的改进。涵盖所有的改进将需要几个章节。注意,如果你想玩玩 ES6,但不确定该做什么,请访问 www.es6fiddle.net 并尝试一下。

常数

常量适用于不能重新分配新值的变量:

const TAX = 0.06;

块范围的变量和函数

在 ES6 之前,JavaScript 在变量方面有两大缺陷。首先,在 JavaScript 中,未声明的变量被隐式提升为全局变量。正如我之前提到的,在我看来,这似乎是不合逻辑和危险的。如果脚本试图将赋值给未声明的变量,JavaScript 中的严格模式会引发错误,如下例所示:

"use strict";
mark = true; // no ‘var mark’ to be found anywhere....

此外,当您用var语句声明变量时,这会将变量的范围缩小到最接近的整函数。以下示例分配了两个x变量:一个在函数内部,但在if块外部,另一个在函数内部,但在if块内部。注意代码是如何运行的,就好像只有一个x变量一样。这是因为它的作用域是整个函数。即使它离开了if语句的范围,它仍然保持相同的值:

function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // same variable!
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

现在 ES6 允许开发者在块范围内声明变量和函数。ES6 有一个新的用于声明变量的let语句。它类似于var语句,只是变量的作用域是最近的封闭块,就像在{' and '}中一样。

下一个例子展示了内部变量x如何作用于if语句中最近的块。当代码退出if语句时,内部x变量超出了作用域。因此,当控制台日志打印在if下面的语句中时,它显示的是外部x变量的值:

function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // different variable
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

ES6 还允许您定义块内的函数。当块终止时,这些函数立即超出范围。例如,以下代码在带有 ES5 的 Plunker 上运行良好,但在 Es6fiddle.net 上运行时会抛出“未捕获的引用错误:日志未定义”:

if (1 == 1){
    function log(){
      console.log("logging");
    }
    log();
}
log();

箭头功能

箭头函数是一种用于编写 JavaScript 函数的新的 ES6 语法(见图 3-3 )。箭头函数是一个匿名函数,您可以在源代码中内嵌它(通常是为了传递给另一个函数)。您不需要通过使用function关键字来声明箭头函数。关于箭头函数需要记住的一件非常重要的事情是,this变量的值保存在函数内部。

A458962_1_En_3_Fig3_HTML.jpg

图 3-3

Arrow function

函数参数现在可以有默认值

如果某些参数未定义,您可以指定默认值。

例如,以下内容

function multiply(a = 10, b = 20){
  return a * b;
}
console.log(multiply(1,2));
console.log(multiply(1));
console.log(multiply());

产生以下输出:

2
20
200

函数现在接受 Rest 参数

这个参数语法使我们能够将无限数量的参数表示为一个数组。

例如,以下内容

function multiply(...a){
  var result = 1;
  for (let arg in a){
    result = result * a[arg];
  }
  return result;
}
console.log(multiply(5,6));
console.log(multiply(5,6,2));

产生以下输出:

30
60

字符串插值

字符串插值使变量能够被数据绑定到文本字符串中。注意,插值只对用于模板文字的新引号字符```ts 起作用。模板文字允许用户使用多行字符串和字符串插值。字符串插值不适用于普通引号"'中的字符串。

例如,以下内容:

var person = {name: "julie", city: "atlanta"};
console.log(person.name);
// works
console.log(`${person.name} lives in ${person.city}`);
// doesnt work
console.log("${person.name} lives in ${person.city}");
console.log('${person.name} lives in ${person.city}');

```ts

产生以下输出:

julie
julie lives in atlanta
${person.name} lives in \({person.city} \){person.name} lives in ${person.city}


### 模块

模块化编程是一种软件设计技术,它强调将程序的功能分成独立的、可互换的模块,每个模块都包含执行所需功能的一个方面所需的一切。

目前,大多数网络浏览器运行的是 JavaScript 版本 ECMA 5,它不是为模块化编程而编写的。然而,ECMA 6 旨在与模块一起工作,其规范于 2015 年 6 月达成一致。ES6 JavaScript 允许你将代码编写成模块,并像乐高积木一样使用它们。

例如,您可以拥有一个包含大量国际化代码的国际化实用程序模块,包括为不同地区加载资源包的代码等等。然而,您只需要其他代码来访问这个代码的一个方法,一个叫做`getI18N(locale, key)`的方法,它将返回一个地区和一个键的文本。JavaScript 模块为您提供了这种能力,让您编写一个只能通过公共接口访问的代码“黑盒”——在本例中是一个导出函数。

#### 一条命

在 ES6 中,每个模块都写在自己的 JavaScript 文件中——一个。js 文件。每个文件只有一个模块,每个模块只有一个文件。有两种方法可以从一个模块中导出东西,使它们在外部可用,这两种方法都使用了`export`关键字。您可以在同一个模块中混合使用这两种导出方式,但如果不这样做,会更简单。随便选一个。一旦导出了代码,就可以通过导入在其他地方使用它。

#### 导出方法 1:命名导出

如果希望模块导出多个对象(例如,常数、函数、对象等),请使用命名导入。命名导入使您能够导出带有名称的代码。

模块`mymath.js`:

export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}


导入和使用模块代码:

import { square, diag } from 'mymath';
console.log(square(11));
console.log(diag(4, 3));


注意导出不需要分号,并且名称必须与模块中的原始名称相匹配。

#### 导出方法 2:默认导出

每个模块只能有一个默认导出。如果您只想让您的模块导出一个东西,这是很有用的。

模块`mymath.js`:

export default function square(x) {
return x * x;
}


导入和使用模块代码:

import sq from 'mymath';
sq();


再次注意导出不需要分号,名字`sq`与模块中的函数不匹配。使用默认导出允许您使用“昵称”,因为它知道将要使用的对象,因为只有一个。

Note

如果需要编写可以部署到运行 ES5 的浏览器上的现代代码(ES6 或更高版本),可以使用 transpilation 来转换代码。

### 以打字打的文件

ES6 是 ES5 的一大进步,但它仍然缺少 Java 和 C#等现代结构化语言提供的一些功能,例如,强类型、装饰器、枚举等。

别担心。已经有一些构建在 ES6 之上的东西,并把它推进了一步。这叫打字稿。我们将在 ES6 和 TypeScript 中编写现代代码,并使用 transpilation 将其转换为可在主流 web 浏览器上部署的兼容代码。

TypeScript 由微软编写,是一种非常现代的结构化语言,类似于 Java 和 C#。谷歌与微软在 TypeScript 上合作,用它自己编写 Angular。这使得在 Angular 中使用 TypeScript 成为一个非常好的主意!

## 摘要

本章讨论了 JavaScript 的许多陷阱,并指出当前大多数 web 浏览器仍然运行 ES5,而不是 ES6。我提到过,虽然 ES6 是对 ES5 的改进,但 TypeScript 扩展并改进了它,提供了打字和许多其他功能。打字稿是下一章的主题。

# 四、TypeScript

TypeScript 是 JavaScript(由微软编写)的超集,主要提供可选的静态类型、类和接口。它是开源的,正在 GitHub 上开发。编译器在 TypeScript 中实现,可以在任何 JavaScript 主机上工作。

作为 JavaScript 的严格超集意味着 JavaScript 程序也是有效的 TypeScript 程序,TypeScript 程序可以无缝地使用 JavaScript。TypeScript 编译成兼容的 JavaScript。TypeScript 与 Java/非常相似。NET 有一些不同,例如,构造函数和接口。

您不需要下载或安装 TypeScript。当您使用 Angular CLI 时(在第七章中介绍),它会自动为您的项目设置 TypeScript。

简而言之,您可以这样看待 TypeScript:

*   TypeScript = JavaScript +类型+类+模块+更多

这些增加中最重要的是类型,我们将在本章中讨论。类型使 ide 能够提供一个更丰富的环境,以便在您键入代码时发现常见错误。

Note

浏览器不能直接运行 TypeScript 至少现在还不能。TypeScript 代码被编译成 JavaScript。

微软学习打字稿的网站是 [`www.typescriptlang.org`](http://www.typescriptlang.org) ,其操场如图 4-1 所示。

请注意,您可以在左侧输入 TypeScript,并在右侧看到它被转换为 JavaScript。

![A458962_1_En_4_Fig1_HTML.jpg](https://gitee.com/OpenDocCN/vkdoc-frontend-framework-zh/raw/master/docs/ng5-proj/img/A458962_1_En_4_Fig1_HTML.jpg)

图 4-1

Playground area of [`www.typescriptlang.org`](http://www.typescriptlang.org)

本章的其余部分集中在 JavaScript 和 TypeScript 语言之间的主要区别。

## 强力打字

TypeScript 提供了强类型,类型非常有用,因为它使开发人员能够指定如何使用变量(它将存储什么类型的信息)。这使编译器能够验证情况是否如此。如果您的代码没有像预期的那样以有效的方式使用变量,它将无法编译。

使用 TypeScript 进行相等性比较比使用 ECMA5 JavaScript 更容易,因为您可以很容易地检测到所比较的两项是否属于同一类型。如果不是,就会产生一个错误。类型检查完成后,相等检查会更容易,因为两个项属于同一类型。代码中包含类型给了 ide 更多的信息。例如,如果 IDE 知道某个变量是字符串,它可以将自动完成选择范围缩小到字符串。

TypeScript 提供了以下基本类型:

*   布尔代数学体系的
*   数字
*   线
*   排列
*   列举型别
*   任何的
*   空的

## 班级

ECMAScript 5 没有类,但是 TypeScript 和 ECMAScript 6 有。

类具有以下格式的构造函数:

```ts
class Animal {
    private name:string;
    constructor(theName: string) { this.name = theName; }
}

Note that the code below will do the same thing as the code above (ie assign a value to the 'name' instance variable):

class Animal {
    constructor(private name: string) {}
}

类可以扩展其他类:

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number = 0) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 5) {
        alert("Slithering...");
        super.move(meters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 45) {
        alert("Galloping...");
        super.move(meters);
    }
}

类可以实现接口(见下一节)。和类可以对成员变量或方法使用公共和私有修饰符。如果没有为变量或方法指定 public 或 private,编译器会假定该成员是公共的。

接口

把接口想象成做某事(例如以某种方式实现某个功能)或存储某种数据(例如属性、数组)的承诺。TypeScript 接口可以应用于函数:

interface SearchFunc {
  (source: string, subString: string): boolean;
}
var mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  var result = source.search(subString);
  if (result == -1) {
    return false;
  }
  else {
    return true;
  }
}

TypeScript 接口也可以应用于属性。接口可以强制属性,但也可以有可选属性(例如,下面代码中的color):

interface LabelledClothing {
  label: string;
  size: number;
  color? : string;
}

function printLabel(labelled: LabelledClothing) {
  console.log(labelled.label + " " + labelled.size);
}

var myObj = {size: 10, label: "Dress"};
printLabel(myObj);

Typescript 接口可以应用于数组:

interface StringArray {
  [index: number]: string;
}

var myArray: StringArray;
myArray = ["Bob", "Fred"];

类可以实现接口:

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface  {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

您可以拥有扩展其他接口的接口:

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

var square = <Square>{};
square.color = "blue";
square.sideLength = 10;

模块

模块不包含在 ECMAScript 5 中,但它们包含在 TypeScript 和 ECMAScript 6 中。关键字export允许你在一个模块中导出你的 TypeScript 对象,这样它们就可以在其他地方使用。

有两种主要类型的 TypeScript 模块:内部模块和外部模块。在 Angular 中,大多数时候你将使用外部模块。

内部模块

内部模块是 TypeScript 自己模块化代码的方法。您使用module关键字来创建一个模块。内部模块可以跨多个文件,有效地创建一个名称空间。在浏览器中,你使用<script/>标签加载模块,因为没有运行时模块加载机制。或者你可以把 TypeScript 文件编译成一个 JavaScript 文件,用一个<script/>标签包含进来。

您可以像这样声明内部模块:

module mymod {

  export function doSomething() {
    // this function can be accessed from outside the module
  }

  export class ExportedClass {
    // this class can be accessed from outside the module
  }

  class AnotherClass {
    // this class can only be accessed from inside the module
  }
}

要使用内部模块,您可以使用它们的完全限定名:

var exportedClassInstance = new mymod.ExportedClass();

或者您可以导入它们:

import ExportedClass = mymod.ExportedClass;
var exportedClassInstance = new ExportedClass();

外部模块

这些是在 Angular 中开发时最常用的模块类型。外部模块使用运行时模块加载机制。我们将在第九章中讨论模块加载机制。

要使用外部模块,您需要决定是使用 AMD 还是 CommonJS(您的两种模块系统选择),然后使用带有值amdcommonjs–module编译器标志编译您的源代码。

在计算中,名称空间是用来组织各种对象的一组符号。对于外部模块,文件的名称和路径将创建名称空间,用于标识该项。

下面是一个名为 projectdir/ExportedClass.ts 的文件的示例:

class ExportedClass {
  // code ....
}
export = ExportedClass;

要使用外部模块:

import ExportedClass = require("projectdir/ExportedClass");
var exportedClassInstance = new ExportedClass();

枚举和泛型

枚举用于设置常数值列表。他们将熟悉 Java 和。NET 开发人员:

enum Color {Red, Green, Blue};
var c: Color = Color.Green;

仿制药也是如此:

interface LabelledClothing {
  label: string;
  size: number;
}
var arr: Array<LabelledClothing> = new Array<LabelledClothing>();

构造器

TypeScript 使用constructor关键字来声明构造函数,而不是类名。另一个区别是,TypeScript 自动将构造函数参数作为属性进行赋值。您不需要在您的构造函数中分配实例变量——这已经为您完成了。

这个:

class Person {
    constructor(private firstName: string, private lastName: string) {
    }
}

等于这个:

class Person {
    private firstName: string;
    private lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

功能

ECMAScript 5 中不存在箭头函数,但在 TypeScript 和 ECMAScript 6 中存在。箭头函数是可以在源代码中内联编写的函数(通常是为了传递给另一个函数)。图 4-2 至 4-4 显示箭头功能。

A458962_1_En_4_Fig4_HTML.jpg

图 4-4

The functions in the preceding two figures could be written in shorter form, like this

A458962_1_En_4_Fig3_HTML.jpg

图 4-3

The function in Figure 4-2 could be written into an arrow function in this manner

A458962_1_En_4_Fig2_HTML.jpg

图 4-2

Regular function Note

语法不是开发人员在 TypeScript 中使用箭头函数的主要原因。主要原因是变量this的值保存在箭头函数中。这对开发人员有很大的好处,因为常规 JavaScript 函数有一种称为装箱的机制,它在进入被调用函数的上下文之前包装或更改this对象。在匿名函数中,this对象代表全局窗口。在其他函数中,它代表其他的东西。许多开发人员在绝对希望确保this变量是他们所期望的时,会使用箭头函数。

图 4-5 显示了一个常规函数的例子。

A458962_1_En_4_Fig5_HTML.jpg

图 4-5

A regular function

运行图 4-5 中的代码后,person.age的值为 1。它的值应该是 2,因为Person函数中的this变量实际上并不代表Person函数。

图 4-6 显示了一个箭头功能的例子。

A458962_1_En_4_Fig6_HTML.jpg

图 4-6

An arrow function

运行图 4-6 中的代码后,person.age的值为 2,这是正确的。那是因为Person函数中的this变量按照预期代表了Person函数。

省略号运算符(用...表示)允许方法接受一组参数作为数组,如下例所示:

function sum(...numbers: number[]) {
    var aggregateNumber = 0;
    for (var i = 0; i < numbers.length; i++)
        aggregateNumber += numbers[i];
    return aggregateNumber;
}

console.log(sum(1, 5, 10, 15, 20));

Getters 和 Setters

如果您的目标是使用 ECMAScript 5 的浏览器,这个脚本版本支持Object.defineProperty()特性。如果您使用 TypeScript getters 和 setters,那么您可以用.符号定义和直接访问属性。如果您习惯于 C#,那么您已经非常习惯于:

class foo {
  private _bar:boolean = false;

  get bar():boolean {
    return this._bar;
  }
  set bar(theBar:boolean) {
    this._bar = theBar;
  }
}

...

var myBar = myFoo.bar;
myFoo.bar = true;

类型

TypeScript 中可以有变量类型,但这是可选的。它之所以是可选的,是因为 TypeScript 是向后兼容的,这意味着它可以运行所有的 JavaScript 代码。在 Typescript 中声明变量时,可以通过在变量名称后添加: [type]来指定变量类型。

例如,我们声明一个类型为number的标记变量:

var mark: number = 123;

如果我们编辑代码将一个字符串赋给这个变量,我们会得到如图 4-7 所示的语法错误高亮显示。请注意,这不会在 Plunker 中发生——只会在像 Visual Studio Code 这样的编辑器中发生。

A458962_1_En_4_Fig7_HTML.jpg

图 4-7

Syntax error highlighting

如果我们保存这个(坏的)代码并编译 TypeScript,我们会得到以下错误:

Type 'string' is not assignable to type 'number'.

原始类型

TypeScript 提供了以下基本类型:

  • 任何的
  • 空的
  • 数字
  • 线
  • 布尔代数学体系的

原始类型不是从Object类继承的,也不可扩展(不能子类化)。基本类型通常以小写首字母命名,例如,number

对象类型

对象类型不是基本类型。它们继承自Object类,并且是可扩展的。对象类型通常以大写首字母命名,例如,Number

这种类型的对象可以访问它们的prototype,因此您可以向对象添加额外的功能:

String.prototype.Foo = function() {
    // DO THIS...
}

对象类型也让你使用instanceof来检查类:

myString instanceof String

工会类型

有时您希望变量是多种类型中的一种,例如,字符串或数字。为此,您可以使用 union ( |)类型。以下变量可以是字符串或数字,并且代码有效:

var name: string|number;

...

constructor(){
      this.name = 'abc';
      this.name = 22;
}

这是另一个例子:

var action = ActionNew | ActionSave | ActionDelete ;
...

if (action instanceof ActionNew){
      ...do something...
}

联合类型也可以应用于函数参数和结果:

function format(value: string, padding: string | number) { // ... }

function getFormatted(anyValue:any): string | number { // ... }

别名类型

您也可以使用type关键字来定义类型别名:

type Location = string|number;
var loc: Location;

元组类型

元组是元素的有限有序列表,例如:姓名、地址、数字邮政编码。TypeScript 允许您使用使用类或元组的变量来访问这些数据。元组类型允许您将变量定义为一系列类型:

var contactInfo: [string, string, number];

contactInfo = ['Mark', '12 Welton Road', 30122];

编译选项

您可以根据自己的喜好配置 TypeScript:源代码位于何处、编译有多严格、您想要(或不想要)什么编译检查、生成的 transpiled 代码位于何处等等。您可以通过在 JSON 格式的文件中指定配置选项来配置 TypeScript。通常,这个文件被称为“tsconfig.json ”,当您使用 CLI 生成 Angular 项目时,您会发现这样一个文件。我有时会编辑这个文件,如果你要做一些不寻常的事情,让不相关的编译检查进行下去。例如,如果您在 TypeScript 类中使用一些常规的 JavaScript 代码。

摘要

现在,您应该对 TypeScript 如何改进 JavaScript 有了基本的了解。TypeScript 有助于开发人员更好地声明和指定代码,允许您用指定的类型声明变量并启用更多的编译时检查。它还帮助您使用注释向编译器提供关于您正在编写的有 Angular 的对象的信息。TypeScript 确实使 Angular 2 和 4 更容易使用。

我们很快就要开始编码了,但是首先我们需要设置我们的代码编辑器。第六章封面编辑。

五、Visual Studio Code

许多编辑器都可以使用 TypeScript,包括 Visual Studio、Visual Studio Code、WebStorm、WebEssentials、Eclipse 等等。

不同的人喜欢不同的编辑器,不同的开发人员可以在同一个项目中使用不同的编辑器,而不会引起很多问题。没有“正确”或“错误”的编辑器。

我将介绍 Visual Studio Code,因为它非常好用,而且是免费的。我建议你去 https://code.visualstudio.com 点击下载链接安装这个编辑器。如果您最终不喜欢 Visual Studio Code,您可以轻松地删除它并选择不同的代码。

我选择 Visual Studio Code 是因为它是微软开发的开源源代码编辑器,可用于 Windows、Linux 和 macOS。它包括对调试、嵌入式 Git 控件、语法高亮、智能代码完成、代码片段和代码重构的支持。它是由编写 TypeScript 的同一批人编写的,所以我们知道它可以很好地与它一起工作。它还可以很好地与 JavaScript、PHP 等等一起工作。而且比较紧凑。

有时我会转而使用 Webstorm,因为它能更好地重构代码。然而,Visual Studio Code 90%的时间都工作得很好。

Visual Studio Code 入门

如果您还没有这样做,请转到 https://code.visualstudio.com ,下载 Visual Studio Code,并安装它。图 5-1 显示下载页面。

A458962_1_En_5_Fig1_HTML.jpg

图 5-1

Download page for Visual Studio Code

安装程序后,要启动 shell,请双击 Visual Studio Code 图标将其打开。单击文件➤打开文件夹,然后选择项目的根文件夹。

导航到项目的根文件夹。输入命令code .(代码空格周期)。

查看文件、命令和热键

按 Ctrl+P 会在顶部的文本框下面列出文件,如图 5-2 所示。当您键入时,它会过滤列表。

A458962_1_En_5_Fig2_HTML.jpg

图 5-2

Filtering the list of files

按 Ctrl+Shift+P 会在顶部文本框的下方列出顶部的命令,如图 5-2 所示。当您键入时,它会过滤列表。

A458962_1_En_5_Fig3_HTML.jpg

图 5-3

Filtering the list of commands

开始构建

编辑项目根文件夹中的 tasks.json 文件(见图 5-4 )。这个配置文件指定了我们将在示例项目中使用的构建命令。它在命令行上运行npm run build来调用构建。有关 Webpack 和构建过程的更多信息,请参见第十章。

A458962_1_En_5_Fig4_HTML.jpg

图 5-4

Editing tasks.json

若要开始生成,请按 Ctrl+Shift+B。生成输出将显示在输出窗格中。通常运行需要 10-30 秒。见图 5-5 。

A458962_1_En_5_Fig5_HTML.jpg

图 5-5

Build output will appear in the Output pane

要查看构建错误,请按 Ctrl+Shift+M。错误列在屏幕顶部(参见图 5-6 )。单击错误以导航到错误的来源。

A458962_1_En_5_Fig6_HTML.jpg

图 5-6

Any errors would be listed at the top of the screen

介绍界面

Visual Studio Code 在左侧显示了一个侧边栏,提供不同的模式(图 5-7 )并在右侧显示了一个编辑区域。

A458962_1_En_5_Fig7_HTML.jpg

图 5-7

The Visual Studio sidebar has four modes: Explorer, Search, Git, and Debug (from top to bottom).

您可以使用 Ctrl+B 键盘快捷键来显示和隐藏侧栏。

您可以在四种主要的侧边栏模式之间轻松切换:Explorer、Search、Git 和 Debug。有不同的方式来切换模式:

  • 单击侧边栏图标。
  • 单击查看并选择您的模式。
  • 使用热键(在以下章节中给出)。

探险家

浏览器窗格(图 5-8 )是侧边栏之后的第一个窗格。它分为两个部分:工作文件(上面的)和项目文件(下面的—在这种情况下,称为 Temp)。单击文件列表中的文件,将其显示在右侧进行编辑。

A458962_1_En_5_Fig8_HTML.jpg

图 5-8

Explorer pane

要激活或聚焦浏览器窗格,请单击边栏中的文件图标,单击查看➤浏览器,或按 Ctrl+E

工作文件

编辑文件时,它们会出现在工作文件中。如果您一次只编辑几个项目文件,将这些文件列在工作文件部分的顶部会很方便。当你将鼠标悬停在“工作文件”标题上时,它会显示一个 X,允许你根据需要清除列表。

项目文件

项目文件是项目中所有文件以及文件夹的列表。

搜索

搜索(图 5-9 )的工作方式就像它在大多数程序中一样(见图 5-9 )。要激活或聚焦搜索窗格,请单击边栏中的放大镜图标,单击查看➤搜索,或按 Ctrl+Shift+F

A458962_1_En_5_Fig9_HTML.jpg

图 5-9

Search

饭桶

要激活或聚焦 Git 窗格,单击侧栏中的 Git 图标,单击查看➤ Git,或按 Gtrl+Shift+G。参见图 5-10 。

A458962_1_En_5_Fig10_HTML.jpg

图 5-10

Git

  • “视图”菜单“选项”“Git”。
  • control–Shift–G

调试

要激活或聚焦调试窗格,请单击边栏中的调试图标,单击查看➤调试,或按 Ctrl+Shift+D。参见图 5-11 。

A458962_1_En_5_Fig11_HTML.jpg

图 5-11

Debug

调试对于调试服务器端代码比调试浏览器端代码更有用,所以对于 Angular 来说可能没有太大用处。如果您在浏览器上启用远程调试并连接到它,您可以使用该调试器来调试浏览器代码,但使用可用的(优秀的)浏览器调试器可能更容易,如 Google Chrome 中的调试器。

要调试您的服务器端代码,您必须首先设置一个调试启动任务。这使您能够设置调试启动配置,用于启动服务器端代码并开始调试它。

要进行调试,请执行以下操作:

  1. 单击边栏中的调试图标,或者使用已经提到的另一个选项。
  2. 单击齿轮图标打开调试配置设置(在中)。settings/launch.json)。
  3. 选择您的调试配置(在齿轮图标旁边),然后单击 play 启动它。

扩展ˌ扩张

要激活或聚焦扩展窗格,单击左侧的扩展图标(图 5-12 ),单击查看➤扩展,或按 Ctrl+Shift+X。图 5-13 显示了扩展窗格。

A458962_1_En_5_Fig13_HTML.jpg

图 5-13

Extensions pane

A458962_1_En_5_Fig12_HTML.jpg

图 5-12

Extensions icon

将扩展安装到 Visual Studio Code 中非常容易。我工作的 Angular 5 项目有一个包含林挺的构建过程,它检查代码以确保它遵循风格指南。如果用户添加了太多空白,构建通常会失败。这变得很烦人,在代码中安装 linter 扩展是一个好主意,这样它可以在林挺问题发生时突出显示这些问题(在左下角有一个警告)。

要查看与扩展相关的命令,请输入以下内容:

extensions

这将显示如图 5-14 所示的可用扩展命令列表。

A458962_1_En_5_Fig14_HTML.jpg

图 5-14

Extensions commands

要在代码中安装扩展,请输入以下命令并按照说明进行操作:

ext install

要在代码中设置 TypeScript linter,请输入以下命令并遵循说明(参见图 5-15 ):

A458962_1_En_5_Fig15_HTML.jpg

图 5-15

Installing the TypeScript linter

ext install tslin

A Few Other Handy Things To Note

作为一个丰富的编辑环境,虚拟工作室代码提供了智能感知代码选择和完成。如果语言服务知道可能的完成方式,IntelliSense 建议将在您键入时弹出。您可以通过按 Ctrl+空格键来手动触发它。

要保存您的工作,可以使用普通的“文件”菜单“保存”命令,就像 Ctrl+S 快捷键一样。

Visual Studio Code 允许您自由地进出代码。例如,您可以按住 Ctrl 并单击来“钻取到”代码,例如钻取到一个方法。当您需要详细查看某个东西时,这非常有用,但是您需要能够回到您所在的位置。这就是向后和向前导航的用武之地。要向后导航,请按 Ctrl+或单击查看➤向后导航。要向前导航,请按 Ctrl+Shift+或单击查看➤向前导航。

摘要

希望现在您已经安装了 Visual Studio Code 并检查了它。请注意,您安装的版本中显示的屏幕可能与本章中的屏幕截图略有不同。这个程序经常更新。

还要记住,你没有被“锁定”在任何特定的编辑器中。我选择 Visual Studio Code 与本书一起使用,因为它简单易用,易于上手。如果您想使用另一个编辑器来处理本书中的代码示例,请继续。

现在您已经安装了一个编辑器,我们可以继续我们的开发环境,并准备开始编码。当你用 Angular 开发代码时,你最终会使用大量的第三方代码——也就是其他人写的代码。你尽量不要从头开始写!

因此,您的项目将依赖于其他人的代码。Node 的目的是管理这些依赖关系,所以我们将在下一章讨论 Node。

六、Node

我们需要尽快编码,但我们需要一个项目。要创建一个项目,我们需要使用 CLI (Angular 的命令行界面,将在下一章介绍)。CLI 需要节点才能工作。因此,在开始编码之前,我们需要讨论节点。

Node 是安装在计算机上的 JavaScript 运行时。它是开发工具和服务器(或其他任何东西)的平台。Node 易于使用,并且已经为它编写了数百个模块——这意味着您可以重用大量代码。

Node 使用谷歌为 Chrome 浏览器编写的 V8 JavaScript 引擎代码,结合额外的模块来完成文件 I/O 和其他在浏览器中无法完成的有用任务。Node 本身什么也不做——它是一个可以运行许多有用的 JavaScript 代码模块的平台,包括 web 服务器、传输器等等。

Node 还提供了节点包管理器的依赖管理,我们将在本章中介绍。它将使你能够管理你的项目对第三方 JavaScript 库的依赖。掌握 npm 的运作是非常重要的。节点包管理器也允许你发布你的 Angular 代码作为一个模块,以及使用其他人。

你需要 Node。要下载它,去 nodejs.org/download 为你的电脑安装核心节点软件。图 6-1 所示为节点网址。

A458962_1_En_6_Fig1_HTML.jpg

图 6-1

The Node website

您可以选择下载并安装最推荐的版本或最新版本。显然,前者更稳定——这就是为什么推荐它。

设置和运行节点

以下命令在项目中设置节点。它会向您提出一些问题,然后生成 package.json 文件(稍后会介绍)。在项目的根文件夹中运行此命令:

npm init

Note

“npm”命令用于调用节点程序包管理器。

一旦安装了 Node,您就可以通过命令行访问命令node。输入不带参数的命令将允许您键入 JavaScript 并按 Enter 键运行它:

$ node
> console.log('Hello World');
Hello World

使用命令node更有用的方法是输入这个命令加上一个文件名作为参数。这将把文件的内容作为 JavaScript 执行。在这种情况下,我们将创建文件 hello.js:

setTimeout(function() {
   console.log('Hello World!');
}, 2000);

现在我们可以运行它了:

node hello.js

该程序等待两秒钟,然后将“Hello World”写入控制台。

节点程序包管理器(npm)

现在我们知道了如何通过 Node 运行 JavaScript 代码,我们需要看看如何安装这些有用的模块。您可能认为这很简单,但这并不是因为许多节点模块依赖于其他节点模块来工作。因此,当您安装一个节点模块时,node 需要确保所有依赖的节点模块也被安装。这就是为您发明节点包管理器(npm)的原因——添加、更新和删除项目中的节点模块,并管理这些相互依赖关系。

为此,Node 提供了对命令npm的命令行访问。这个命令有许多不同的参数,允许您安装、更新或卸载模块。

网站 http://docs.npmjs.com 是关于国家预防机制的详细文献的一个很好的资源。而 www.npmjs.com 是可用节点包的绝佳资源。

节点模块安装级别

节点模块安装有两个级别:

  • 全局:如果您要在命令行上安装某个东西,可以通过在命令行上将-g添加到npm install来进行全局安装:

  • 本地:如果你正在安装你想在你的程序中使用的东西(不是从命令行),使用本地级别。要在本地安装一个模块,可以在命令行中省略npm install中的-g:

npm install -g typescript

npm install express

运行 NPM Install[模块名称]来安装模块

如果您正在做一些简单的事情,比如向项目中添加一个额外的节点模块,这将非常有用。例如,最有用的模块之一是 Express,一个有能力的 web 服务器。要安装 Express,我们可以在命令行中输入以下内容:

npm install express

这不会更新您的节点依赖文件 package.json(稍后会详细介绍)。如果您需要将该模块保存为该文件中的项目依赖项,请在命令中添加--save--save-dev参数:

  • save 参数--save:这将添加您将要安装的节点模块,作为您的项目在生产中所需的节点模块。您的 package.json 文件被修改为包含这个依赖项。

    npm install express --save
    
    
  • save 参数--save-dev:这将添加您将要安装的节点模块,作为您的项目在开发中只需要的节点模块(也就是说,在生产中不需要)。您的 package.json 文件再次被修改以包含这个依赖项。

    npm install express --save-dev
    
    

更新节点模块

有时你的依赖会改变。您希望添加一个附加模块,但是添加该模块要求其他模块具有更高的版本号。节点提供以下命令来检查您的模块是否过期:

npm outdated

有两种不同的方式来更新节点中的模块。

  • 您可以运行命令npm update来指定要更新的模块。如果您希望您的 package.json 文件更新为更高的版本,还可以添加--save选项。如果指定了-g标志,这个命令将更新全局安装的软件包。
  • 可以编辑 package.json 文件,更新模块依赖关系,然后运行npm update。这将更新您的模块以匹配该文件中的规范。

Checking Your Node Version

如果已经安装了 Node,可以通过运行以下命令来检查其版本:

npm –v

卸载节点模块

您可以运行命令npm uninstall,指定要卸载的模块。如果您希望您的 package.json 文件用从依赖项列表中删除的模块进行更新,也可以添加--save选项。如果指定了-g标志,这个命令将删除全局安装的软件包。

package.json 文件

节点旨在从项目文件夹中的命令行运行。它允许开发人员将与项目相关的信息存储在 package.json 文件中,该文件应该位于项目的根文件夹中。该文件指定了许多关于项目的有用信息:

  • 项目的名称和版本。
  • 您的项目依赖于哪些节点模块(以及您需要这些模块的哪些版本)。
  • 您的项目在生产中需要哪些节点模块。
  • 你的项目在开发中需要哪些节点模块(也就是生产中不需要)。

更新 package.json

您可以通过两种方式更新这个“packages.json”文件:

  • 通过使用安装/更新/删除节点模块和更新该文件的节点命令(在命令行上)。
  • 通过自己编辑这个文件。然后运行节点命令来安装/更新/删除节点模块以匹配该文件。

版本号

package.json 文件允许开发人员指定项目所需的节点模块。当您在此文件中指定依赖项时,您也可以指定这些依赖项的版本,例如 1.0.1。Node 允许您灵活地以许多不同的方式指定您需要的版本号,如表 6-1 中所总结的。

表 6-1

Ways of Specifying Version Numbers

| 1.2.1 | 必须与版本 1.2.1 匹配 | | >1.2.1 | 必须高于版本 1.2.1 | | >=1.2.1 | 必须是版本 1.2.1 或更高版本 | | <1.2.1 | 必须在版本 1.2.1 之前 | | <=1.2.1 | 必须早于或等于版本 1.2.1 | | ~1.2.1 | 必须大致相当于版本 1.2.1 | | ¹.2.1 | 必须与版本 1.2.1 兼容 | | 1.2.x | 必须是从 1.2 开始的任何版本。 | | * | 任何版本 |

文件夹节点 _ 模块

当您安装一个节点模块时,它被下载并放置在项目文件夹的子文件夹 node_modules 中。通常情况下,您会得到比您预想的更多的东西,因为您安装的节点模块有许多依赖项,所以您最终会得到一个巨大的 node_modules 文件夹,里面有几十个模块子目录。有时 npm 需要很长时间来下载和安装项目节点模块。另外,注意不要把这个文件夹从一个地方复制到另一个地方,因为这可能会花去你似乎永远也不会有的时间。如果要将项目从一台计算机复制到另一台计算机,请先删除“node_modules”文件夹,然后在目标计算机上运行“npm install”。

有两种不同的方式将模块安装到节点中。您可以运行命令npm install来指定模块(来安装它),或者您可以编辑 package.json 文件,然后运行npm install

编辑 package.json 文件并运行 npm install

当您的项目依赖于多个模块时,手动编辑 package.json 文件是安装多个模块的最佳方式。首先,您必须在项目的根文件夹中建立一个 package.json 文件,该文件包含应用的概述。有许多可用的字段,但在下面的示例 package.json 文件中,您看到的是最少的字段。dependencies部分描述了您想要安装的模块的名称和版本。在这种情况下,我们还将依赖于快速模块:

{
  "name" : "MyStaticServer",
  "version" : "0.0.1",
  "dependencies" : {
    "express" : "3.3.x"
  }
}

要安装 package.json 文件中概述的依赖项,请在项目根文件夹的命令行中输入以下内容:

npm install

Installing The Latest Angular

转到您的项目文件夹,发出以下命令:

npm install @angular/{common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router,animations}@latest typescript@latest --save

摘要

现在您已经知道了什么是 Node,以及如何使用它来管理项目对第三方代码的依赖,是时候开始使用 Angular CLI(命令行界面)进行编码了。让我们在下一章开始吧!

七、开始使用 CLI 编码

当我第一次在 Angular 2 中开始开发时,我发现一开始有一个尖锐的学习曲线。因为没有一个标准的 Angular 2 项目蓝图来简单地处理项目的构建和运行,所以很难让项目进行下去。您必须在 Node 中设置您依赖项(稍后将详细介绍),设置您的构建过程,并设置部署过程。这使得 Angular 2 一开始很难,因为你必须同时学习概念和语法。

输入 CLI。Angular CLI(命令行界面)的开发是为了让开发人员能够快速使用 Angular。这很棒——它可以生成结构良好、设计良好的项目。我怎么强调都不为过,这是一个多么好的工具。难怪这么快就被采用了。

Angular CLI 是一个开源项目。你可以在 https://github.com/angular/angular-cli/ 看它的代码。官方 Angular CLI 文档可通过 https://cli.angular.io/ 在线获取。你可以在 https://angular.io/docs/ts/latest/cli-quickstart.html 查看快速入门页面。

本章的目的是让您使用 CLI 创建项目。现在还不打算详细介绍 CLI,因为还没有必要——在后面的章节中会有更多关于 CLI 的内容。然而,如果你现在想要大量的信息,我推荐你在 www.sitepoint.com/ultimate-angular-cli-reference/ 的优秀文章。

Angular CLI 名副其实,它使用命令行界面。您在终端中使用ng命令,它就开始工作了。使用命令行可能会让您想起“糟糕的旧时光”,那时您必须记住一堆命令,但是当您看到 CLI 所做的事情时,您会忘记这一切:

  • 它允许您创建新的 Angular 应用。
  • 它允许您运行一个开发服务器,并实时重载变更。
  • 它允许您向 Angular 应用添加更多代码
  • 它运行您的应用的测试。
  • 它为部署构建您的应用。
  • 它部署您的应用。

如前一章所述,要运行 CLI,首先需要安装 node . js 4 . 0 . 0 版或更高版本。如果您还没有这样做,请返回并阅读如何做到这一点。

要安装 CLI,请在终端中输入以下命令,这将启动各种节点下载:

npm install -g angular-cli

请注意,-g参数将 Angular CLI 作为全局包安装。这将把ng命令放在 path 中,使它可以在任何目录中使用。

您可以通过运行以下命令来检查您的 CLI 版本:

ng --version

要更新 CLI 版本,您应该卸载它,然后使用以下命令重新安装:

npm uninstall -g angular-cli
npm cache clean
npm install -g angular-cli

创建一个开始项目

最后,我们要做一些编码!不完全是。还没有。让我们创建基本项目并运行它。请遵循以下步骤:

  1. 打开终端窗口。

  2. 导航到合适的文件夹,例如“文档”。

  3. 输入以下命令,这将在一个名为 start 的文件夹中创建一个新的 Angular 应用,并会产生大量它创建的文件:

    ng new start
    
    
  4. 导航到开始文件夹。

    cd start
    
    
  5. 输入以下命令启动应用:

    ng serve
    
    
  6. Open your web browser and browse to localhost:4200. You should see the text “welcome to app!” as shown in Figure 7-1. That means your app is running.

    A458962_1_En_7_Fig1_HTML.jpg

    图 7-1

    The app is working

现在,您可以对项目中的文件进行更改,只要您运行ng serve,项目就会自动重新编译代码,并在 web 浏览器中刷新应用。这造就了一个高效的开发环境。

现在,让我们来看看这个项目以及其中的内容。启动 Visual Studio Code 并打开文件夹 start。表 7-1 显示了内部的内容及其结构。

表 7-1

What’s in the Root Folder?

| 文件或文件夹 | 事实真相 | | :-- | :-- | | e2e | 用于测试文件的文件夹(本书后面会有更多关于测试、Karma 和量角器的内容) | | 节点 _ 模块 | 项目节点相关性的文件夹 | | 科学研究委员会 | 项目源代码的文件夹 | | .editorConfig | 编辑器配置文件 | | 。吉蒂尔 | Git 忽略文件 | | angular angular-CLI . JSON | CLI 配置文件。您可以在此文件中更改 CLI 选项 | | 因果报应日 json | Karma 配置文件(本书后面会有更多关于测试、Karma 和量角器的内容) | | package.json | 节点相关性配置文件 | | 量角器-conf.js | 量角器配置文件(本书后面会有更多关于测试、Karma 和量角器的内容) | | README.md | 自述信息文件,包含有关 CLI 命令的信息 | | 林顿. json | Lint 配置文件 |

表 7-2 显示了源代码。这是真正重要的东西 CLI 为您的项目生成的源代码。这是您编码的起点。

表 7-2

CLI-Generated project code

| 文件或文件夹 | 事实真相 | | :-- | :-- | | 应用 | 应用源代码文件的文件夹,当前包含应用组件的源代码(稍后将详细介绍) | | 资产 | 应用图像和 CSS 文件的文件夹 | | 环境 | 环境配置文件的文件夹,例如,开发和生产配置 | | favicon.ico | 应用图标 | | index.html | Angular 单页应用的 HTML 页面 | | 主页面 | 启动应用的代码(稍后将详细介绍) | | 样式. css | 全局样式定义 | | 测试. ts | 运行应用测试的代码 | | tsconfig.json 文件 | TypeScript/编译器配置文件 |

修改开始项目

让我们修改开始项目,看看会发生什么。请遵循以下步骤:

  1. 打开终端窗口。

  2. 导航到 start 文件夹,确保ng start命令正在运行,并且导航到 localhost:8080 会产生“欢迎使用 app!”如预期的网页。保持ng start命令运行。

  3. 编辑文件 src/app/app.component.ts,将其更改为以下内容:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'app works! and has been modified....';
    }
    
    
  4. Go back to your web browser . It should now display what you see in Figure 7-2.

    A458962_1_En_7_Fig2_HTML.jpg

    图 7-2

    The app has been modified

请注意,当您在编辑器中单击“保存”时,应用是如何自动重新编译和重新加载的。这是因为 CLI 项目包括 Watchman,它可以监视更改的文件,并在您更改应用时对其进行重建和重新加载。我很快会说更多关于 Watchman 的内容。

启动项目:编译错误

让我们在项目中引入一个编译错误,看看会发生什么。

编辑文件 src/app/app.component.ts,并将其更改为以下内容(记住省略“app works”中的引号):

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = app works;
}

请注意,应用不会改变或重新加载,并且您会在终端窗口中得到错误信息。浏览器控制台中也会出现错误消息。在 Chrome 中,您可以通过选择更多工具,然后在菜单中选择开发者工具来查看浏览器控制台。

启动项目:运行时错误

让我们在项目中引入一个运行时错误,看看会发生什么:

  1. 编辑文件 src/app/app.component.ts,改回原代码:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'app works!';
    }
    
    
  2. 编辑文件 src/app/app.component.html 并将其更改为以下内容(以创建一个错误):

    <h1>
      {{title.test.test}}
    </h1>
    
    

应用变成空白。如果你检查终端,它说“webpack:编译成功。”所以,编译成功了。然而,页面没有加载,因为我们(故意)引入了一个运行时错误(只有在应用运行时才会出现)。要查找错误,请转到浏览器控制台(参见图 7-3 )。在谷歌浏览器中,你可以通过打开“汉堡”菜单,选择更多工具,然后在菜单中选择开发者工具来查看浏览器控制台。

A458962_1_En_7_Fig3_HTML.jpg

图 7-3

The app has been modified

文件监视器和网络服务器

如前所述,如果让ng serve运行,它会监视我们的文件(必要时执行编译和重新部署),并在 localhost:4200 上运行一个本地 web 服务器。当您更改某项内容并单击“保存”时,观察器会执行以下操作:

  • 创建一个 Webpack 构建,包括编译兼容的 JavaScript 和捆绑代码(本书后面会详细介绍 Webpack)
  • 生成一个新的 index.html 文件,根据需要添加脚本引用来引用 Webpack 捆绑的 JavaScript 文件
  • 在本地 web 服务器上执行新的部署
  • 刷新网页

拔靴带

自举通常是指一个自启动的过程,它应该在没有外部输入的情况下进行。在这种情况下,它指的是 Angular 应用如何启动。这一节看一下 starter 项目如何启动。

当我们转到 localhost:4200 时,会发生以下情况:

  1. 默认情况下,web 浏览器会打开文件 index.html。

  2. 浏览器在最后加载脚本文件。这包括 main.bundle.js,它是 typescript 文件 main.ts 的 transpiled 版本。这是我们的主要应用入口点。

  3. Main.bundle.js 加载一些模块然后调用下面的 Angular 系统代码:

    platformBrowserSpecific().bootstrapModule(AppModule)
    
    
  4. AppModule 已加载——它是用于引导应用的根 Angular 模块。这是一个 Angular 模块,不是 JavaScript 模块——它们是不同的东西(我将在本书的后面介绍 Angular 模块)。如果您查看 AppModule.ts,您会看到它包含以下代码行,告诉模块使用 AppComponent 进行引导:

    @NgModule({
    
        ...
    
      bootstrap: [AppComponent]
    })
    
    
  5. AppModule 用 AppComponent 引导,将组件注入开始和结束标记app-root :

    <app-root>Loading...</app-root>
    
    

    之间的空间

有用的 CLI 选项

我们经常在本书中使用 CLI,以下是我们在示例中使用的一些 CLI 选项:

--flat

生成具有平面文件结构的 cli 项目,而不是在自己的目录中生成每个组件。

--inline-template

使用内联模板生成组件(稍后将详细介绍)。组件模板标记将在组件中生成,而不是在单独的文件中生成。

--inline-style

生成具有内联样式的组件(稍后将详细介绍)。组件样式将在组件中生成,而不是在单独的文件中生成。

--spec false

生成不带单元测试“规范”文件的组件,默认情况下通常会为您生成这些文件。

自从我编写了示例之后,又添加了一个非常有用的新 CLI 选项:

--minimal

生成一个包含内联模板、样式且不包含测试的最小 cli 项目。

提前编译

正如在第二章中提到的,Angular 现在更倾向于 aot 模型,在这种模型中,你的代码是提前编译的,而不是在需要的时候。5 中的这些编译器更新推进了向 aot 的转移,这将使您的应用运行得更快,因为它在运行应用时将执行更少的编译。

如果您正在处理一个 CLI 项目,并且希望提前执行 aot 编译,那么您可以在 CLI 命令中添加'—aot '选项。例如,您可以使用以下命令运行带有 aot 编译的应用:

ng serve -aot

这对于提前发现模板中的错误非常有用。如果你的组件行为异常,而你不知道为什么,试着用 aot 编译来编译或运行你的应用!这在很多场合都帮了我大忙!在运行 ng serve 时使用'—aot '选项可以提前发现我们在 3 页前引入模板的运行时错误:

<h1>
  {{title.test.test}}
</h1>

摘要

本章向您介绍了 Angular CLI。除了创建一个启动项目之外,您还可以做更多的事情:

  • 将不同类型的对象添加到项目中
  • 测试您的代码
  • 构建您的代码
  • 部署您的代码
  • 更加

我们将在所有的编码示例中使用 CLI,所以不要担心:我将会更多地介绍它,并且我们将用它做更多的事情。

下一章非常重要:它介绍了组件,有 Angular 的用户界面的构建块。

八、组件介绍

Angular 组件类似于 AngularJS 控制器。组件基本上是标记、元数据和一个类(包含数据和代码),它们组合在一起创建一个 UI 小部件。组件是我们用来构建交互式 UI 的主要工具。所有 Angular 应用都有一个根组件,通常称为应用组件。

Angular 为组件之间传递数据和响应彼此的事件提供了方法。我们将在第十二章讨论组件输入和输出。

您可以编写一个组件,并在其他几个组件中将它作为子组件使用——出于这个目的,它们被设计为自包含和松散耦合的。每个组件都包含关于自身的有价值的数据:

  • 它需要什么数据作为输入
  • 它可能向外界发射什么事件
  • 怎么画自己
  • 它的依赖关系是什么

通常当你开发组件时,三个文件中的每一个都有一个组件,因为一个组件有三个部分:模板、类和样式。默认情况下,CLI 就是这样工作的。例如,当您使用命令ng new [project name]在 CLI 中创建一个应用时,CLI 会为该应用组件生成三个文件(如果您包括. spec.ts 测试文件,则会生成更多文件):

  • app.component.css:样式
  • app.component.html:模板
  • 应用组件:类

然而,这不是你唯一的选择。以下是更多选项:

  • 将样式包含在。ts 类文件:这被称为内联样式,它使你不必为组件准备一个样式文件。如前一章所述,使用 CLI --inline-style参数生成具有内联样式的组件。
  • 将模板包含在。ts 类文件:这被称为内联模板,它使你不必为组件准备模板文件。如前一章所述,使用 CLI --inline-template参数生成具有内联样式的组件。
  • 在同一个文件中包含多个组件类:您可以在同一个文件中组合多个组件,如下所示:
import { Component } from '@angular/core';

@Component({
  selector: 'Paragraph',
  template: `
  <p><ng-content></ng-content></p>
  `,
  styles: ['p { border: 1px solid #c0c0c0; padding: 10px }']
})
export class Paragraph {
}

@Component({
  selector: 'app-root',
  template: `
  <p>
  <Paragraph>Lorem ipsum dolor sit amet, consectetur adipiscing elit. </Paragraph>
  <Paragraph>Praesent eget ornare neque, vel consectetur eros. </Paragraph>
  </p>
  `,
  styles: ['p { border: 1px solid black }']
})
export class AppComponent {
  title = 'welcome to app!';
}

您可能会在同一个文件中找到包含多个组件的代码示例。这样做是有意的,以便您可以将更多的代码复制并粘贴到更少的文件中。

当您在应用中使用组件时,您需要确保每个组件都在模块中声明。第九章更详细地介绍了模块。以下是声明两个组件的模块示例:AppComponent 和 Paragraph:

import { AppComponent, Paragraph } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
    Paragraph
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

组件的剖析

注释提供了将所有部分组合成一个组件的元数据。模板通常是 HTML 标记,用于在浏览器中呈现组件,即模型-视图-控制器(MVC)中的视图。它可以包含嵌套组件的标记。一个类有添加元数据的注释并包含数据(was$scope)——MVC 中的模型。它包含行为代码 MVC 中的控制器。

@组件注释

注释位于类的顶部附近,是类中最重要的元素。这是一个将类标记为组件并接受对象的函数。它使用对象向 Angular 提供关于组件以及如何运行它的元数据。注释也称为装饰器。

如果您使用 CLI 生成一个项目,并检查生成的组件 app.component.ts,您将看到下面的@Component注释:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

表 8-1 显示了可以添加到@Component注释中的基本元素。

表 8-1

Basic Elements for the @Component Annotation

| 注释元素 | 笔记 | | :-- | :-- | | `selector` | 该组件对应的标记、元素。 | | `template/templateUrl` | 指定包含组件标记的模板。您有两种选择:您可以使用`template`在引号块中指定内联模板。这对简单的模板非常有用。或者您可以使用`templateUrl`来指定外部模板文件的相对路径。这对于更大或更复杂的模板更好。如果模板长度超过 10 行,我通常会将其放在外部模板文件中。 | | `styles/styleUrls` | 指定模板标记的 CSS 信息。您有两种选择:您可以使用`styles`来指定一个内联样式数组。这对于一些样式定义来说非常有用。或者您可以使用`styleUrls`来指定样式定义文件的相对路径数组。当你使用多种风格时,这样更好。如果组件中使用的样式超过 5 种,我通常会将它们放在一个外部样式文件中。 |

selector语法类似于 JQuery 选择器,如表 8-2 所示。

表 8-2

selector Syntax

| 类型 | 例子 | 所选标记的示例 | 笔记 | | :-- | :-- | :-- | :-- | | 名字 | `welcome` | `` | 这是使用选择器最常见的方式。只要确保这个标签是唯一的,并且永远不会被 HTML 使用。为您的项目和其中的所有组件使用一个公共前缀通常是一个好主意。例如,奖励计划项目可以有前缀`rp_`。 | | 身份 | `#welcome` | `
` |   | | CSS 类 | `.welcome` | `'
` |   |

选择器和 DSL

在 Angular 中,您可以创建映射到特定标签(或属性)的 can 组件和指令。例如,如果你正在创建一个销售汽车的应用,你可以像这样使用标签和属性:<CarSearch></CarSearch><CarList></CarList><CarDetail></CarDetail>等等。实际上,通过 Angular 组件和指令,我们正在为我们的应用创建 DSL(特定领域语言)。DSL 是一种专用于特定应用领域的计算机语言。DSL 非常强大,因为它们允许代码特定于应用的领域(它的使用),并以语言形式表示所代表的业务实体。

其他元素

表 8-3 显示了你可以添加到@Component注释中的其他更高级的元素。我们将在后面详细讨论其中的许多内容。

表 8-3

Advanced Elements

| 注释元素 | 笔记 | | :-- | :-- | | `animations` | 该组件的动画列表 | | `changeDetection` | 此组件使用的更改检测策略 | | `encapsulation` | 此组件使用的样式封装策略 | | `entryComponents` | 动态插入到该组件视图中的组件列表 | | `exportAs` | 在模板中导出组件实例时使用的名称 | | `hosts` | 将类属性映射到事件、属性和特性的宿主元素绑定 | | `Inputs` | 要作为组件输入进行数据绑定的类属性名称列表 | | `interpolation` | 此组件模板中使用的自定义插值标记 | | `moduleId` | 定义该组件的文件的 ES/CommonJS 模块 ID | | `outputs` | 公开其他人可以订阅的输出事件的类属性名称列表 | | `providers` | 此组件及其子组件可用的提供程序列表 | | `queries` | 配置可以注入组件的查询 | | `viewProviders` | 此组件及其视图子级可用的提供程序列表 |

组件模板

该模板包含在 web 浏览器中显示组件的标记代码。关于组件模板的信息由注释提供。

模板位置

模板标记可以包含在与Component类相同的文件中,也可以包含在单独的文件中:

以下是@ Component注释中内嵌的模板标记:

@Component({
  selector: 'app-root',
  template: `
  <div class="app">
  [app]
  <app-customer-list>
  </app-customer-list>
  </div>
  `,
  styles: ['.app {background-color:#d5f4e6;margin:10px;padding:10px;}']
})

下面是包含在一个单独文件中的模板标记:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

脚本标签

组件模板中不允许使用<script>标记。为了消除脚本注入攻击的风险,这是被禁止的。实际上,<script>被忽略,浏览器控制台中会出现一条警告。

换句话说,永远不要这样做:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
  <h1>
    {{title}}
  </h1>
  <script>
    alert('app works');
  </script>
  `,
  styles: []
})
export class AppComponent {
  title = 'welcome to app!';
}

Elvis 操作员

该操作员也被称为“安全导航操作员”。

Angular 经常出现空值问题,尤其是模板表达式。通常你会有一个模板突然停止工作,因为你添加的代码引用了一个未初始化的变量。例如,假设我们有一个为空的对象x,我们有以下代码:

Total {{x.totalAmt}}

这将导致 JavaScript 和 Zone 问题(稍后会有更多关于 Zone 的内容),您的组件将突然无法呈现。我希望我每次遇到这种事都能得到一块钱。

幸运的是,“猫王”接线员帮助了我们。简单来说,Elvis 操作符就是模板表达式中可能为空的变量旁边的一个问号。一旦发现该变量为空,Elvis 操作符就告诉代码退出,留下一个空白。这将停止对属性的评估,并绕过 JavaScript 问题:

Total {{x?.totalAmt}}

有时在模板表达式中需要多个 Elvis 运算符:

Total {{x?.amt?.total}}

组件样式

样式包含更改组件样式所需的 CSS 规则。关于组件模板的信息由style注释提供。您可以在元件或外部文件中指定元件的样式。创建 Angular CLI 项目时,其样式文件在。angular-cli.json 文件。

样式可以包含在与Component类相同的文件中,也可以包含在单独的文件中。

以下是@ Component注释中内联包含的样式标记:

@Component({
  selector: 'app-root',
  template: `
  <div class="app">
  [app]
  <app-customer-list>
  </app-customer-list>
  </div>
  `,
  styles: ['.app {background-color:#d5f4e6;margin:10px;padding:10px;}']
})

下面是包含在一个单独文件中的样式标记:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

组件类

此 TypeScript 类包含组件的数据和代码。数据包含在变量中,变量可以绑定到模板中的 HTML 标记。代码可以响应用户事件(比如单击按钮)或者调用自身来开始做事情。

别担心,这只是一个介绍——我们将在第十二章更详细地讨论组件类。

MVC: Model View Controller

MVC 是一种编写程序的方式,主要用于在计算机上实现用户界面。它将给定的软件应用分成三个相互联系的部分:模型(数据)、视图(用户看到的)和控制器(更新模型的命令)。在 Angular 的上下文中,可以说模型是Component类中的数据,视图是组件模板,控制器可以是Component类中的代码。

数据绑定简介

数据绑定是 Angular 如此受欢迎的原因——组件 UI 小部件的元素与组件类中的数据的同步,反之亦然,由 Angular 为您管理。您在组件类中设置变量来存储数据,并在组件模板中编辑 HTML 来添加数据绑定。现在,您的 HTML 不再是静态的—它会随着您的数据而变化!如果您希望您的组件与用户交互,您必须在其中使用数据绑定。

就 MVC 而言,在运行时 Angular 使用变化检测来确保组件视图总是反映组件模型。使用数据绑定,您可以通过更改变量来控制组件的用户界面,并且可以接受用户输入,允许他们更改某些变量的值。数据绑定可以控制用户界面的每个方面:隐藏东西、关闭东西、显示结果、接受用户输入等等。它非常强大而且易于使用。

示例:登录组件中的数据绑定

假设你有一个带有两个字段的登录表单,如图 8-1 所示。每个字段在组件模板的 HTML 中都有一个文本框,每个字段在Component类中都有一个对应的实例变量。文本框和实例变量相互绑定。如果有人输入用户名,username实例变量会用新值更新。当开发人员编写提交按钮时,他们从实例变量中获得用户名和密码,而不是从 HTML 中提取它们。

A458962_1_En_8_Fig1_HTML.jpg

图 8-1

Login form with two fields

示例:数据绑定和客户数据输入

您可以拥有一个表单,该表单允许您使用字段输入客户信息。每个字段在组件模板的 HTML 中都有一个文本框,每个字段在Component类中都有一个对应的实例变量。Angular 数据绑定使您能够在用户向 HTML 字段输入信息时自动更新实例变量。它还使您能够在更改实例变量的值时自动更新 HTML 字段,以及在用户向文本框中键入内容时自动更新实例变量。当开发人员想要默认输入字段的值时,他们需要做的就是设置实例变量值。HTML 文本框将自动更新。

有两种主要类型的数据绑定—单向和双向:

A458962_1_En_8_Fig2_HTML.jpg

图 8-2

Two-way data binding

  1. 单向数据绑定:当模板(视图)自动与类实例变量(模型)中的最新值保持同步时,就会出现这种情况。更新只向一个方向流动。当类实例变量(模型)自动与从模板(视图)输入的值保持同步时,也会发生单向数据绑定。更新仍然是单向的。
  2. 双向数据绑定:这是类实例变量(模型)和模板(视图)互相保持最新的时候。更新流向两个方向,如图 8-2 所示。

单向数据绑定

本节重点介绍 Angular 中单向数据绑定的各个方面。

与{{和}}的单向数据绑定

那些双花括号也被称为小胡子或插值。双花括号用于单向绑定模板表达式,根据模型中的可用数据进行计算,并将其包含在视图中。表达式产生一个值,它包含在视图中(来自组件模板的标记)。模型——即Component类中的数据——从不更新。

模板表达式通常是一个简单的 JavaScript 表达式。通常模板表达式只是模型中一个属性的名称(即Component类中的一个实例变量)。Angular 用属性的字符串值(实例变量的字符串值)替换该属性名称。

有时模板表达式会变得更复杂。Angular 试图对表达式(可以包含数学、属性名、方法调用等等)求值,并将求值结果转换为字符串。然后用结果替换内容和花括号。

以下是花括号和模板表达式的一些示例:

  • {{2+2}}
  • {{firstName}}
  • {{1 + 1 + getVal()}}

单向数据绑定:示例代码组件-ex100

下面的讨论将是关于示例组件-ex100:

  1. 使用 CLI 构建应用:输入以下命令,这将在一个名为 Start 的文件夹中创建一个新的 Angular 应用,还将创建并输出大量文件:

    ng new components-ex100 --inline-template --inline-style
    
    
  2. 开始ng serve:使用以下代码:

    cd components-ex100
    ng serve
    
    
  3. Open app: Launch your web browser and browse to localhost:4200. You should see the text “welcome to app!” as shown in Figure 8-3. That means your project is running.

    A458962_1_En_8_Fig3_HTML.jpg

    图 8-3

    Your project is running

  4. 编辑组件:编辑文件 src/app/app.component.ts,并将其更改为:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      template: `
      <h1>
        {{title}}
      </h1>
      <p>
        Length: {{title.length}}
      </p>
      <p>
        Reversed: {{getReversed(title)}}
      </p>
      `,
      styles: []
    })
    export class AppComponent {
      title = 'welcome to app!';
    
      getReversed(str: string){
        let reversed = '';
        for (let i=str.length-1;i>=0;i--){
          reversed += str.substring(i,i+1);
        }
        return reversed;
      }
    }
    
    

你的应用应该工作在本地主机:4200。请注意模板如何使用两个表达式:一个显示标题的长度,另一个使用类中的方法反转标题。

A458962_1_En_8_Figa_HTML.jpg

使用[ and ]或*的单向数据绑定

方括号可用于单向绑定。通过这些,您可以绑定一个模板表达式,根据模型中的可用数据进行计算,并将其包含在数据绑定目标中。

您也可以使用前缀*来代替双方括号:

[Data Binding Target] = "Template Expression"

或者:

*Data Binding Target = "Template Expression"

数据绑定目标是 DOM 中的一些东西(包括元素属性、组件属性和指令属性),可以绑定到目标右侧的表达式的结果,如表 8-4 所示。

表 8-4

Data Binding Target Markup

| 利润 | 描述 | | :-- | :-- | | `` | 将图像源设置为模型中的属性`imageUrl`。 | | `
` | 根据模型中的属性`isSelected`设置 CSS 类。 | | `` | 将模型中`car-detail`的`car`属性设置为属性`selectedCar`。`car-detail`可以是一个组件,它会使用`car`属性将信息从当前模板传递给那个组件。 | | `
posted @ 2024-08-13 14:02  绝不原创的飞龙  阅读(91)  评论(0)    收藏  举报