教程取自于Google官方课程之Cross Platform apps with Angular
了解 Angular 为了帮助开发者快速设置可维护且可扩缩的应用而提供的各种开发者工具,包括新工具和现有的工具。
什么是 Angular?
Angular 是一个基于 TypeScript 构建的开发平台。它包括:
- 一个基于组件的框架,用于构建可伸缩的 Web 应用
- 一组完美集成的库,涵盖各种功能,包括路由、表单管理、客户端-服务器通信等
- 一套开发工具,可帮助你开发、构建、测试和更新代码
借助 Angular,无论单人项目还是企业级应用,你都能获得平台带来的优势。 Angular 的设计目标之一就是让更新更容易,因此你可以用最小的成本升级到最新的 Angular 版本。
1 Angular 命令行界面 (CLI) 入门
CLI 概览与命令参考手册
Angular CLI 是一个命令行界面工具,可用于初始化、开发、构建和维护 Angular 应用。 你可以在命令行窗口中直接使用此工具,也可以通过 Angular Console 这样的交互式界面来间接使用。
安装 Angular CLI
Angular CLI 的主版本会跟随它所支持的 Angular 主版本,不过其小版本可能会独立发布。
使用 npm
包管理器来安装 CLI:
npm install -g @angular/cli
关于版本变更的详情,以及如何从以前版本升级的信息,参阅 GitHub 上的 Releases 页:https://github.com/angular/angular-cli/releases
基本工作流
通过 ng
可执行文件可以在命令行上调用此工具。 命令行中还提供了联机帮助。 输入下列命令列出命令或指定命令(如 generate)选项的简短说明。
ng help
ng generate --help
要想创建、构建或在开发服务器上运行一个新的、基本的 Angular 项目,请到这个新工作区的上级目录中运行下列命令:
ng new my-first-project
cd my-first-project
ng serve
在浏览器中,打开 http://localhost:4200/ 查看运行效果。 当你使用 ng serve 命令来构建应用并在本地启动开发服务器时,服务器会自动重新构建此应用,并在修改源码时重新加载此页面。
当你运行
ng new my-first-project
时,将在当前工作目录中创建一个名为my-first-project
的新文件夹。由于你希望在该文件夹中创建文件,因此在运行命令之前,请确保你在当前工作目录中具有足够的权限。
如果当前工作目录不适合放你的项目,可以先运行cd <path-to-other-directory>
来切换到更合适的目录。
工作区与项目文件
ng new 命令会创建一个 Angular 工作区目录,并生成一个新的应用骨架。 每个工作区中可以包含多个应用和库。 由 ng new 命令创建的初始应用位于工作区的顶层。 你在工作区中生成的其它应用或库,会放在 projects/
子目录下。
新生成的应用中包含根模块的源码,还有根组件和模板。 每个应用都有一个 src
目录,其中包含逻辑、数据和静态文件。
你可以直接编辑这些生成的文件,也可以使用 CLI 命令来添加或修改它们。 使用 ng generate 命令也可以添加其它组件和服务,以及管道、指令的源码等。 必须在工作区或项目目录下才能执行 add 或 generate 之类的命令,因为这些命令需要在应用或库上进行创建或其它操作。
- 欲知详情,参阅工作区的文件结构。
工作区与项目的配置
工作区的配置文件 angular.json
位于此工作区的顶层。 在这里,你可以设置全工作区范围的默认值,并指定当 CLI 为不同目标构建项目时要用到的配置。
ng config 让你可以从命令行中设置和获取配置项的值。你也可以直接编辑 angular.json
文件。 注意,此配置文件中的选项名称必须使用小驼峰(camelCase)形式,不过当在命令行中提供它是可以使用小驼峰和中线分隔(dash-case)两种形式。
CLI 命令语法
命令语法如下:
ng
commandNameOrAlias requiredArg [optionalArg] [options]
大多数命令以及少量选项,会有别名。别名会显示在每个命令的语法描述中。
选项名带有双中线前缀(--)。 选项别名带有单中线前缀(-)。 参数没有前缀。 比如:
ng build my-app -c production
通常,生成的工件(artifact)名称可以作为命令的参数进行指定,也可以使用 --name 选项。
参数和选项的名称可以用小驼峰或中线分隔的格式给出。
--myOptionName
等价于--my-option-name
。
逻辑型选项
逻辑型选项有两种形式:--this-option
可以把标志设置为 true
,而 --no-this-option
可以把它设置为 false
。 如果没有提供选项,该标志就会留在文档中所列出的默认状态。
相对路径
用来指定文件的选项可以用绝对路径,也可以用相对于当前目录的相对路径,当前目录通常是工作区或项目的根目录。
原理图(schematics)
ng generate 和 ng add 命令会把要生成或要添加到当前项目中的工件或库作为参数。 除了通用选项之外,每个工件或库还可以用原理图定义自己的选项。 原理图的选项和内置命令的选项使用同样的格式。
2 使用 Angular 开发者工具进行调试
DevTools 概述
Angular DevTools 是一个 Chrome 扩展程序,可为 Angular 应用程序提供调试和剖析功能。 Angular DevTools 支持 Angular v9 及更高版本,并支持 Ivy。
你可以在 Chrome 网上应用店中找到 Angular DevTools。
安装 Angular DevTools 后,你可以在 Chrome DevTools 的 Angular 标签下找到本扩展程序。
打开扩展程序时,你还会看到另外两个选项卡:
Components - 使你可以浏览应用程序中的组件和指令,并预览或编辑它们的状态。
-
Profiler - 使你可以剖析应用程序并了解变更检测执行期间的性能瓶颈。
在 Angular DevTools 的右上角,你将找到页面上正在运行哪个版本的 Angular 以及该扩展的最后一次提交的哈希串。
调试你的应用程序
Components 选项卡使你可以浏览应用程序的结构。你可以可视化并检查组件和指令实例,并预览或修改它们的状态。在接下来的两节中,我们将研究如何有效使用此选项卡来调试应用程序。
浏览应用程序的结构
查看属性
单击组件浏览器中的各个组件或指令,以选择它们并预览其属性。 Angular DevTools 在组件树的右侧显示其属性和元数据。
可以使用鼠标或下列键盘快捷键在组件树中导航:
用上下箭头键选择上一个或下一个节点。
用左右箭头键折叠或展开一个节点。
要通过名称查找组件或指令,请使用组件树上方的搜索框。要导航至下一个搜索匹配项,请按 Enter
。要导航至上一个搜索匹配项,请按 Shift + Enter
。
导航到宿主节点
要转到特定组件或指令的宿主元素,请在组件浏览器中找到它,然后双击它。 Chrome DevTools 将打开 “Elements” 选项卡,然后选择关联的 DOM 节点。
导航到源码
对于组件,Angular DevTools 还允许你导航到源码选项卡中的组件定义。选择特定组件后,单击属性视图右上角的图标:
修改属性值
与 Chrome DevTools 一样,属性视图可让你编辑输入属性、输出属性或其他属性的值。右键单击属性值。如果此值类型可使用编辑功能,则将看到一个文本输入框。键入新值,然后按 Enter 键。
在控制台中访问选定的组件或指令
作为控制台中的快捷方式,Angular DevTools 可以让你访问最近选择的组件或指令的实例。键入 $ng0
以获取对当前所选组件或指令的实例的引用,键入 $ng1
来获取前一个选择的实例。
选择指令或组件
与 Chrome DevTools 相似,你可以检查页面以选择特定的组件或指令。单击Devtools 中左上角的 Inspect element(审查元素)图标,然后将鼠标悬停在页面上的 DOM 元素上。 Angular DevTools 可以识别关联的指令和/或组件,并允许你在组件树中选择相应的元素。
剖析你的应用程序
Profiler 选项卡使你可以预览 Angular 变更检测的执行。
通过 Profiler,你可以开始进行剖析或导入现有的剖析记录文件。要开始对应用程序进行性能剖析,请将鼠标悬停在 Profiler 选项卡内左上角的圆圈上,然后点击 Start recording。
在剖析过程中,Angular DevTools 会捕获执行过的事件,例如变更检测和生命周期挂钩。要完成录制,请再次单击那个圆圈以 Stop recording。
你也可以导入现有剖析记录。在导入记录部分了解有关此功能的更多信息。
了解应用程序的执行过程
在下面的屏幕截图中,可以在完成录制后找到 Profiler 的默认视图。
在此视图的顶部附近,你可以看到一系列条形图,每个条形图表示应用程序中的变更检测周期。竖线越高,应用程序在此周期中花费的时间越长。选择条形图时,DevTools 会渲染一个条形图,其中包含在此循环中捕获的所有组件和指令。
在变更检测时间轴前面,你可以发现 Angular 在此周期中花费了多少时间。 Angular DevTools 会试图估算出掉帧情况,以指示应用程序的执行何时可能会影响用户体验。
Angular DevTools 还会指出触发这次变更检测的原因(即变更检测的来源)。
了解组件的执行过程
单击条形图时,你会发现有关应用程序在特定指令或组件上花费了多少时间的详细视图:
这张图展示了 NgForOf 指令花费的总时间,以及其中调用了哪个方法。它还展示了所选指令的父级层次。
分层视图
你也可以在类似火焰图的视图中预览变更检测的执行情况。该图形中的每个图块代表屏幕上渲染树中位于特定位置的元素。
例如,如果在组件树中特定位置的一个变更检测周期中,我们原本有一个 ComponentA
,然后该组件被删除,而在它的位置上,Angular 再渲染出 ComponentB
,这样你就会在同一图块上看到两个组件。
每个图块的颜色取决于 Angular 在这里花费了多少时间。 DevTools 通过相对于我们花费最多时间进行变更检测的图块所花费的时间来确定颜色的深浅。
单击某个图块时,你会在右侧面板中看到关于该图块的详细信息。双击图块将其放大,以便你可以预览嵌套的子级图块。
调试 OnPush
要预览 Angular 进行变更检测的组件,请选择火焰图上方顶部的 Change detection 复选框。
此视图将把所有在 Angular 中执行过变更检测的图块显示为绿色,其余显示为灰色:
导入剖析记录
单击已记录的性能剖析会话左上角的 Save Profile 按钮,以将其导出为 JSON 文件并将其保存到磁盘。然后,你可以通过单击 Choose file 输入框来将此文件导入到剖析器的初始视图中:
3 使用 Angular Universal 进行服务器端渲染
Angular Universal:Angular 统一平台简介
本指南讲的是Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术。
标准的 Angular 应用会运行在浏览器中,它会在 DOM 中渲染页面,以响应用户的操作。 而Angular Universal 会在服务端运行,生成一些静态的应用页面,稍后再通过客户端进行启动。 这意味着该应用的渲染通常会更快,让用户可以在应用变得完全可交互之前,先查看应用的布局。
要了解 SSR 的其它技术和概念的详细信息,请参阅这篇文章。
可以使用 Angular CLI 来轻松为应用做好服务端渲染的准备。CLI 的 @nguniversal/express-engine
模板会执行如下必要步骤。
Angular Universal 需要活跃 LTS 或 维护中 LTS版本的 Node.js。 参见 package.json 文件中的
engines
属性,以了解当前支持的版本。
注意: <live-example downloadonly="" ng-version="13.0.3">下载已完成的范例代码</live-example>,并将其运行在一个 Node.js® Express 服务器中。
Universal 教程
这次演练的基础是“英雄之旅”教程。
在这个例子中,Angular CLI 使用 预先(AoT)编译器编译并打包了该应用的 Universal 版本。Node.js Express Web 服务器则会根据客户端的请求,利用 Universal 编译 HTML 页面。
要创建服务端应用模块 app.server.module.ts
,请运行以下 CLI 命令。
ng add @nguniversal/express-engine
该命令会创建如下文件夹结构。
src/
index.html app web page
main.ts bootstrapper for client app
main.server.ts * bootstrapper for server app
style.css styles for the app
app/ ... application code
app.server.module.ts * server-side application module
server.ts * express web server
tsconfig.json TypeScript base configuration
tsconfig.app.json TypeScript browser application configuration
tsconfig.server.json TypeScript server application configuration
tsconfig.spec.json TypeScript tests configuration
标有 * 的文件都是新增的,不在原始的教程范例中。
Universal 实战
要使用 Universal 在本地系统中渲染你的应用,请使用如下命令。
npm run dev:ssr
打开浏览器,导航到 http://localhost:4200/。你会看到熟悉的“英雄之旅”仪表盘页面。
通过 routerLinks
导航时能正常工作,因为它们使用的是内置的链接标签(<a>
)。你可以从仪表盘进入 英雄列表页面,然后返回。你可以点击仪表盘页面上的一个英雄来显示他的详情页面。
如果你限制下网速(稍后会讲操作步骤),让客户端脚本下载时间变长,你会注意到:
你无法添加或删除英雄。
仪表盘页面上的搜索框会被忽略。
“详情”页面上的后退和保存按钮不起作用。
不支持除了点击 [routerLink](http://angular.cn/api/router/RouterLink)
以外的任何用户事件。你必须等待完整的客户端应用启动并运行,或者使用 preboot 之类的库来缓冲这些事件,这样你就可以在客户端脚本加载完毕后重放这些事件。
在开发机器上,从服务端渲染的应用过渡到客户端应用的过程会很快,但是你还是应该在实际场景中测试一下你的应用。
你可以通过模拟速度较慢的网络来更清晰地看到这种转换,如下所示:
打开 Chrome 开发者工具,进入 Network 标签页。
找一下菜单栏最右侧的 Network Throttling 下拉菜单。
尝试一下 “3G” 的速度吧。
服务端渲染的应用仍然可以快速启动,但完整的客户端应用可能需要几秒钟才能加载完。
为何需要服务端渲染?
有三个主要的理由来为你的应用创建一个 Universal 版本。
通过搜索引擎优化(SEO)来帮助网络爬虫。
提升在手机和低功耗设备上的性能
迅速显示出第一个支持首次内容绘制(FCP)的页面
帮助网络爬虫(SEO)
Google、Bing、Facebook、Twitter 和其它社交媒体网站都依赖网络爬虫去索引你的应用内容,并且让它的内容可以通过网络搜索到。
这些网络爬虫可能不会像人类那样导航到你的具有高度交互性的 Angular 应用,并为其建立索引。
Angular Universal 可以为你生成应用的静态版本,它易搜索、可链接,浏览时也不必借助 JavaScript。 它也让站点可以被预览,因为每个 URL 返回的都是一个完全渲染好的页面。
提升手机和低功耗设备上的性能
有些设备不支持 JavaScript 或 JavaScript 执行得很差,导致用户体验不可接受。 对于这些情况,你可能会需要该应用的服务端渲染的、无 JavaScript 的版本。 虽然有一些限制,不过这个版本可能是那些完全没办法使用该应用的人的唯一选择。
快速显示第一页
快速显示第一页对于吸引用户是至关重要的。 加载速度更快的页面效果更好,即使其差异只有 100 毫秒也是如此(https://web.dev/shopping-for-speed-on-ebay/)。 你的应用要启动得更快一点,以便在用户决定做别的事情之前吸引他们的注意力。
使用 Angular Universal,你可以为应用生成“着陆页”,它们看起来就和完整的应用一样。 这些着陆页是纯 HTML,并且即使 JavaScript 被禁用了也能显示。 这些页面不会处理浏览器事件,不过它们可以用 [[routerLink](http://angular.cn/api/router/RouterLink)](guide/router-reference#router-link)
在这个网站中导航。
在实践中,你可能要使用一个着陆页的静态版本来保持用户的注意力。 同时,你也会在幕后加载完整的 Angular 应用。 用户会觉得着陆页几乎是立即出现的,而当完整的应用加载完之后,又可以获得完整的交互体验。
Universal Web 服务器
Universal Web 服务器使用 Universal 模板引擎渲染出的静态 HTML 来响应对应用页面的请求。 服务器接收并响应来自客户端(通常是浏览器)的 HTTP 请求,并回复静态文件,如脚本、CSS 和图片。 它可以直接响应数据请求,也可以作为独立数据服务器的代理进行响应。
这个例子中的范例 Web 服务器是基于常见的 Express 框架的。
注意: 任何一种 Web 服务器技术都可以作为 Universal 应用的服务器,只要它能调用 Universal 的
[renderModule](http://angular.cn/api/platform-server/renderModule)()
函数。 这里所讨论的这些原则和决策点也适用于任何 Web 服务器技术。
Universal 应用使用 platform-server
包(而不是 platform-browser
),它提供了 DOM 的服务端实现、XMLHttpRequest
以及其它不依赖浏览器的底层特性。
服务器(这个例子中使用的是 Node.js Express 服务器)会把客户端对应用页面的请求传给 NgUniversal 的 ngExpressEngine
。在内部实现上,它会调用 Universal 的 [renderModule](http://angular.cn/api/platform-server/renderModule)()
函数,它还提供了缓存等有用的工具函数。
[renderModule](http://angular.cn/api/platform-server/renderModule)()
函数接受一个模板 HTML 页面(通常是 index.html
)、一个包含组件的 Angular 模块和一个用于决定该显示哪些组件的路由作为输入。
该路由从客户端的请求中传给服务器。
每次请求都会给出所请求路由的一个适当的视图。
[renderModule](http://angular.cn/api/platform-server/renderModule)()
在模板中的 <app>
标记中渲染出这个视图,并为客户端创建一个完成的 HTML 页面。
最后,服务器就会把渲染好的页面返回给客户端。
使用浏览器 API
由于 Universal 应用并没有运行在浏览器中,因此该服务器上可能会缺少浏览器的某些 API 和其它能力。
比如,服务端应用不能引用浏览器独有的全局对象,比如 window
、document
、navigator
或 location
。
Angular 提供了一些这些对象的可注入的抽象层,比如 Location
或 DOCUMENT
,它可以作为你所调用的 API 的等效替身。 如果 Angular 没有提供它,你也可以写一个自己的抽象层,当在浏览器中运行时,就把它委托给浏览器 API,当它在服务器中运行时,就提供一个符合要求的代用实现(也叫垫片 - shimming)。
同样,由于没有鼠标或键盘事件,因此 Universal 应用也不能依赖于用户点击某个按钮来显示某个组件。 Universal 应用必须仅仅根据客户端过来的请求决定要渲染的内容。 把该应用做成可路由的,就是一种好方案。
Universal 模板引擎
server.ts
文件中最重要的部分是 ngExpressEngine()
函数:
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
ngExpressEngine()
是对 Universal 的 [renderModule](https://angular.cn/api/platform-server/renderModule)()
函数的封装。它会把客户端请求转换成服务端渲染的 HTML 页面。 它接受一个具有下列属性的对象:
bootstrap
:在服务器上渲染时用于引导应用程序的根[NgModule](https://angular.cn/api/core/NgModule)
或[NgModule](https://angular.cn/api/core/NgModule)
工厂。对于这个范例应用,它是AppServerModule
。它是 Universal 服务端渲染器和 Angular 应用之间的桥梁。extraProviders
:这是可选的,可以让你指定仅在服务器渲染应用程序时才适用的依赖提供者。当你的应用需要某些只能由当前运行的服务器实例确定的信息时,可以执行此操作。
ngExpressEngine()
函数返回了一个会解析成渲染好的页面的承诺(Promise)。 接下来你的引擎要决定拿这个页面做点什么。 在这个引擎的 Promise
回调函数中,把渲染好的页面返回给了 Web 服务器,然后服务器通过 HTTP 响应把它转发给了客户端。
注意: 这个包装器帮助隐藏了
[renderModule](https://angular.cn/api/platform-server/renderModule)()
的复杂性。 在 Universal 代码库中还有更多针对其它后端技术的包装器。
过滤请求的 URL
注意:当使用 NgUniversal Express 原理图时,将自动处理稍后描述的基本行为。当你要尝试理解其底层行为或在不使用原理图的情况下自行实现它时,这一节会很有用。
Web 服务器必须把对应用页面的请求和其它类型的请求区分开。
这可不像拦截对根路径 /
的请求那么简单。 浏览器可以请求应用中的任何一个路由地址,比如 /dashboard
、/heroes
或 /detail:12
。 事实上,如果应用只会通过服务器渲染,那么应用中点击的任何一个链接都会发到服务器,就像导航时的地址会发到路由器一样。
幸运的是,应用的路由具有一些共同特征:它们的 URL 一般不带文件扩展名。 (数据请求也可能缺少扩展名,但是它们很容易识别出来,因为它们总是以 /api
开头,所有的静态资源的请求都会带有一个扩展名,比如 main.js
或 /node_modules/zone.js/dist/zone.js
)。
由于使用了路由,所以我们可以轻松的识别出这三种类型的请求,并分别处理它们。
数据请求:请求的 URL 用
/api
开头应用导航:请求的 URL 不带扩展名
静态资源:所有其它请求。
Node.js Express 服务器是一系列中间件构成的管道,它会挨个对 URL 请求进行过滤和处理。 你可以调用 app.get()
来配置 Express 服务器的管道,就像下面这个数据请求一样:
// TODO: implement data requests securely
server.get('/api/**', (req, res) => {
res.status(404).send('data requests are not yet supported');
});
注意:这个范例服务器不会处理数据请求。
本教程的“内存 Web API” 模块(一个演示及开发工具)拦截了所有 HTTP 调用,并且模拟了远端数据服务器的行为。 在实践中,你应该移除这个模块,并且在服务器上注册你的 Web API 中间件。
下列代码会过滤出不带扩展名的 URL,并把它们当做导航请求进行处理。
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
安全的提供静态文件
单独的 server.use()
会处理所有其它 URL,比如对 JavaScript 、图片和样式表等静态资源的请求。
要保证客户端只能下载那些允许他们访问的文件,你应该把所有面向客户端的资源文件都放在 /dist
目录下,并且只允许客户端请求来自 /dist
目录下的文件。
下列 Node.js Express 代码会把剩下的所有请求都路由到 /dist
目录下,如果文件未找到,就会返回 404 - NOT FOUND
。
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
在服务端使用绝对 URL 进行 HTTP(数据)请求
本教程的 HeroService
和 HeroSearchService
都委托 Angular 的 [HttpClient](https://angular.cn/api/common/http/HttpClient)
模块来获取应用数据。这些服务会向 api/heroes
之类的相对 URL 发送请求。在服务端渲染的应用中,HTTP URL 必须是绝对的(例如,https://my-server.com/api/heroes
)。这意味着当在服务器上运行时,URL 必须以某种方式转换为绝对 URL,而在浏览器中运行时,它们是相对 URL。
如果你正在使用 @nguniversal/*-engine
包之一(例如 @nguniversal/express-engine
),就会自动为帮你做这件事。你无需再做任何事情来让相对 URL 能在服务器上运行。
如果出于某种原因,你没有使用 @nguniversal/*-engine
包,你可能需要亲自处理它。
建议的解决方案是将完整的请求 URL 传给 renderModule() 或 renderModuleFactory() 的 options
参数(具体取决于你在服务器上渲染 AppServerModule
的目的)。此选项的侵入性最小,因为它不需要对应用进行任何更改。这里的“请求 URL” 是指当应用在服务器上渲染时的地址。例如,如果客户端请求了 https://my-server.com/dashboard
并且要在服务器上渲染该应用以响应该请求,那么 options.url
应设置为 https://my-server.com/dashboard
。
现在,作为在服务端渲染应用的一部分,每次发送 HTTP 请求时,Angular 都可以使用这里提供的 options.url
正确地将请求 URL 解析为绝对 URL。
实用脚本
-
npm run dev:ssr
此命令类似于
ng serve
,它在开发期间提供实时重新加载,但使用服务器端渲染。该应用程序以监视模式运行并在每次更改后刷新浏览器。这个命令要比实际的ng serve
命令慢。 -
ng build && ng run app-name:server
此命令会在生产模式下构建服务器脚本和应用程序。当你要构建用于部署的项目时,请使用此命令。
-
npm run serve:ssr
此命令启动服务器脚本,用于通过服务器端渲染在本地为应用程序提供服务。它使用由
ng run build:ssr
创建的构建工件,因此请确保你也运行了该命令。请注意,
serve:ssr
不能用于在生产环境为你的应用程序提供服务,而仅用于在本地测试服务器端渲染的应用程序。 -
npm run prerender
此脚本可用于预先渲染应用程序的页面。在此处阅读有关预先渲染的更多信息。