非常教程

AngularJS参考手册

服务端渲染

服务端渲染

本指南讲的是Angular Universal(统一平台),一项在服务端运行 Angular 应用的技术。

标准的 Angular 应用会运行在浏览器中,它会在 DOM 中渲染页面,以响应用户的操作。

Angular Universal 会在服务端通过一个名叫服务端渲染(server-side rendering - SSR)的过程生成静态的应用页面。

它可以生成这些页面,并在浏览器请求时直接用它们给出响应。 也可以把页面预先生成为 HTML 文件,然后把它们作为静态文件供服务器使用。

本指南讲的是一个 Universal 的范例应用,它启动得和在服务端渲染好的页面一样快。 稍后,浏览器就会下载完整的客户端版本,并在代码加载完之后自动切换过去。

你可以下载最终的范例代码,并将其运行在一个 Node.js® Express 服务器中。

为何需要 Universal

有三个主要的理由来为你的应用创建一个 Universal 版本。

  1. 帮助网络爬虫(SEO)
  2. 提升在手机和低功耗设备上的性能
  3. 迅速显示出第一个页面

帮助网络爬虫

Google、Bing、Facebook、Twitter 和其它社交媒体网站都依赖网络爬虫去索引你的应用内容,并且让它的内容可以通过网络搜索到。

这些网络爬虫可能不会像人类那样导航到你的具有高度交互性的 Angular 应用,并为其建立索引。

Angular Universal 可以为你生成应用的静态版本,它易搜索、可链接,浏览时也不必借助 JavaScript。 它也让站点可以被预览,因为每个 URL 返回的都是一个完全渲染好的页面。

启用对网络爬虫的支持通常也称作搜索引擎优化 (SEO)。

提升手机和低功耗设备上的性能

有些设备不支持 JavaScript 或 JavaScript 执行得很差,导致用户体验不可接受。 对于这些情况,你可能会需要该应用的服务端渲染的、无 JavaScript 的版本。 虽然有一些限制,不过这个版本可能是那些完全没办法使用该应用的人的唯一选择。

快速显示第一页

快速显示第一页对于吸引用户是至关重要的。

如果页面加载超过了三秒钟,那么 53% 的移动网站会被放弃。 你的应用要启动得更快一点,以便在用户决定做别的事情之前吸引他们的注意力。

使用 Angular Universal,你可以为应用生成“着陆页”,它们看起来就和完整的应用一样。 这些着陆页是纯 HTML,并且即使 JavaScript 被禁用了也能显示。 这些页面不会处理浏览器事件,不过它们可以用 routerLink在这个网站中导航。

在实践中,你可能要使用一个着陆页的静态版本来保持用户的注意力。 同时,你也会在幕后加载完整的 Angular 应用,就像稍后解释的那样。 用户会觉得着陆页几乎是立即出现的,而当完整的应用加载完之后,又可以获得完整的交互体验。

工作原理

要制作一个 Universal 应用,就要安装 platform-server 包。 platform-server 包提供了服务端的 DOM 实现、XMLHttpRequest 和其它底层特性,但不再依赖浏览器。

你要使用 platform-server 模块而不是 platform-browser 模块来编译这个客户端应用,并且在一个 Web 服务器上运行这个 Universal 应用。

服务器(这个例子中使用的是 Node Express 服务器)会把客户端对应用页面的请求传给 renderModuleFactory 函数。

renderModuleFactory 函数接受一个模板 HTML 页面(通常是 index.html)、一个包含组件的 Angular 模块和一个用于决定该显示哪些组件的路由作为输入。

该路由从客户端的请求中传给服务器。 每次请求都会给出所请求路由的一个适当的视图。

renderModuleFactory 在模板中的 <app> 标记中渲染出哪个视图,并为客户端创建一个完成的 HTML 页面。

最后,服务器就会把渲染好的页面返回给客户端。

使用浏览器 API

由于 Universal 的 platform-server 应用并没有运行在浏览器中,因此那些与浏览器 API 有关的工作都没法在这个服务器中使用。

你不能引用浏览器独有的原生对象,比如 windowdocumentnavigatorlocation。 如果你在服务端渲染的页面中不需要它们,就可以使用条件逻辑跳过它们。

另一种方式是查找一个可注入的 Angular 对所需对象的抽象服务,比如 LocationDocument,它可能作为你调用的指定 API 的等价替身。 如果 Angular 没有提供它,你也可以写一个自己的抽象层,当在浏览器中运行时,就把它委托给浏览器 API,挡在服务器中运行时,就提供一个符合要求的代用实现。

由于没有鼠标或键盘事件,因此 Universal 应用也不能依赖于用户点击某个按钮来显示每个组件。 Universal 应用应该仅仅根据客户端过来的请求决定要渲染的内容。 把该应用做成可路由的,就是一种好方案。

由于服务端渲染页面的用户只能点击链接,所以你应该尽快让它切换到真正的客户端应用,以提供正常的交互体验。

例子

《英雄指南》教程是本章所讲的 Universal 范例的基础。

除了下面讲的少量修改之外,应用中的核心文件几乎不用动。 你只需要添加一些额外的文件来支持使用 Universal 进行构建和提供服务。

在这个例子中,Angular CLI 会使用 AOT (预先) 编译器对该应用的 Universal 版本进行编译和打包。 Node.js® 的 Express Web 服务器会把客户端请求转换成由 Universal 渲染出的页面。

你将会创建:

  • 一个服务端的 app 模块 app.server.module.ts
  • 一个服务端的入口点 main.server.ts
  • 一个用于处理请求的 express Web 服务器
  • 一个 TypeScript 配置文件 tsconfig.server.json
  • 一个供服务器使用的 Webpack 配置文件 webpack.server.config.js

当做完这些后,文件夹的结构是这样的:

content_copysrc/
  index.html                 应用的宿主页
  main.ts                    客户端应用的引导程序
  main.server.ts             * 服务端应用的引导程序
  tsconfig.app.json          TypeScript 的客户端配置
  tsconfig.server.json       * TypeScript 的服务端配置
  tsconfig.spec.json         TypeScript 的测试配置
  style.css                  应用的样式表
  app/ ...                   应用代码
    app.server.module.ts     * 服务端的应用模块
server.ts                    * Express 的服务程序
tsconfig.json                TypeScript 的客户端配置
package.json                 npm 配置
webpack.server.config.js     * Webpack 的服务端配置

那些标有 * 的文件都是新的,而不是来自原来的范例应用。 本文稍后的部分会涉及它们。

准备工作

下载《英雄指南》项目,并为它安装依赖。

安装工具

在开始之前,要安装下列包。

  • @angular/platform-server - Universal 的服务端元件。
  • @nguniversal/module-map-ngfactory-loader - 用于处理服务端渲染环境下的惰性加载。
  • @nguniversal/express-engine - Universal 应用的 Express 引擎。
  • ts-loader - 用于对服务端应用进行转译。

使用下列命令安装它们:

content_copynpm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader @nguniversal/express-engine

修改客户端应用

Universal 应用可以扮演一个动态的、内容丰富的 “封面页”,以吸引用户。 它能让应用看上去几乎是立即呈现出来的。

同时,浏览器会在后台下载客户端应用的脚本。 一旦加载完毕,Angular 就会从静态的服务端渲染的页面无缝转换成动态渲染的可交互的客户端应用。

你要对应用代码做少量修改,以支持服务端渲染,并无缝转换成客户端应用。

根模块 AppModule

打开 src/app/app.module.ts 文件,并在 NgModule 的元数据中找到对 BrowserModule 的导入。 把该导入改成这样:

src/app/app.module.ts (withServerTransition)

content_copyBrowserModule.withServerTransition({ appId: 'tour-of-heroes' }),

Angular 会把 appId 值(它可以是任何字符串)添加到服务端渲染页面的样式名中,以便在客户端应用启动时可以找到并移除它们。

你可以通过依赖注入取得关于当前平台和 appId 的运行时信息。

src/app/app.module.ts (platform detection)

content_copyimport { PLATFORM_ID, APP_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(APP_ID) private appId: string) {
    const platform = isPlatformBrowser(platformId) ?
      'in the browser' : 'on the server';
    console.log(`Running ${platform} with appId=${appId}`);
  }

Build Destination

A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.

The Angular CLI outputs the client-side code in the dist directory by default, so you modify the outputPath for the build target in the angular.json to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server.

content_copy...
"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "outputPath": "dist/browser",
    ...
  }
}
...

在 HTTP 中使用绝对地址

教程中的 HeroServiceHeroSearchService 都委托了 Angular 的 HttpClient 模块来获取应用数据。 那些服务都把请求发送到了相对URL,比如 api/heroes

在 Universal 应用中,HTTP 的 URL 必须是绝对地址(比如 https://my-server.com/api/heroes), 只有这样,Universal 的 Web 服务器才能处理那些请求。

你还要修改这些需要发起请求的服务,当它运行在服务端时使用绝对地址,运行在浏览器中时用相对地址。

解决方案之一是通过 Angular 的 APP_BASE_HREF 令牌来提供服务器的源地址(origin),把它注入到服务中,并把这个源地址添加到所请求的 URL 之前。

先为 HeroService 的构造函数添加第二个 origin 参数,它是可选的,并通过 APP_BASE_HREF 令牌进行注入。

src/app/hero.service.ts (constructor with optional origin)

content_copyconstructor(
  private http: HttpClient,
  private messageService: MessageService,
  @Optional() @Inject(APP_BASE_HREF) origin: string) {
    this.heroesUrl = `${origin}${this.heroesUrl}`;
  }

注意,这个构造函数是如何把这个 origin(如果存在)添加到 heroesUrl的前面的。

在浏览器版本中,你不用提供 APP_BASE_HREF,因此 heroesUrl 仍然是相对的。

如果你在 index.html 中为了满足路由器的需求已经指定过了 <base href="/">,那就可以在浏览器中忽略 APP_BASE_HREF。参见教程的例子。

服务端代码

要想运行 Angular 的 Universal 应用,你需要一个服务器,用它来接受客户端的请求,并返回渲染好的页面。

服务端应用模块

服务端应用模块(习惯上叫作 AppServerModule)是一个 Angular 模块,它包装了应用的根模块 AppModule,以便 Universal 可以在你的应用和服务器之间进行协调。 AppServerModule 还会告诉 Angular 在把你的应用以 Universal 方式运行时,该如何引导它。

src/app/ 目录下创建 app.server.module.ts 文件,代码如下:

src/app/app.server.module.ts

content_copyimport { NgModule } from '@angular/core';import { ServerModule } from '@angular/platform-server';import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader'; import { AppModule } from './app.module';import { AppComponent } from './app.component'; @NgModule({  imports: [    AppModule,    ServerModule,    ModuleMapLoaderModule  ],  providers: [    // Add universal-only providers here  ],  bootstrap: [ AppComponent ],})export class AppServerModule {}

注意它首先导入了客户端应用的 AppModule 和来自 Angular Universal 的 ServerModuleModuleMapLoaderModule

ModuleMapLoaderModule 是一个服务端模块,用于实现路由的惰性加载。

这里还可以注册那些在 Universal 环境下运行应用时特有的服务提供商。

应用服务器的入口点

Angular CLI 使用 AppServerModule 来构建服务端发布包。

src/ 目录下创建一个 main.server.ts 文件,并导出 AppServerModule

src/main.server.ts

content_copyexport { AppServerModule } from './app/app.server.module';

稍后,这个 main.server.ts 文件将会被引用,以便往 Angular CLI 的配置中添加一个名为 server 的目标(target)。

Universal Web 服务器

Universal Web 服务器负责响应对本应用的页面请求。它使用的是由 Universal 模板引擎渲染出的 HTML。

它接受并响应来自客户端(通常是浏览器)的 HTTP 请求。 它还会提供那些静态文件,比如脚本、css 和图片。 它还可以响应数据请求,可能直接响应也可能将其代理到一个独立的数据服务器。

本文中的范例 Web 服务器基于常见的 Express 框架。

任何 Web 服务器技术都可以作为 Universal 应用的服务器,只要它能调用 Universal 的 renderModuleFactory 即可。 下面讨论的这些原则和决策要点适用于你选择的任何 Web 服务器技术。

在根目录下创建 server.ts 文件,并添加下列代码:

server.ts

content_copy// These are important and needed before anything elseimport 'zone.js/dist/zone-node';import 'reflect-metadata'; import { enableProdMode } from '@angular/core'; import * as express from 'express';import { join } from 'path'; // Faster server renders w/ Prod mode (dev mode never needed)enableProdMode(); // Express serverconst app = express(); const PORT = process.env.PORT || 4000;const DIST_FOLDER = join(process.cwd(), 'dist'); // * NOTE :: leave this as require() since this file is built Dynamically from webpackconst { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main'); // Express Engineimport { ngExpressEngine } from '@nguniversal/express-engine';// Import module map for lazy loadingimport { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader'; app.engine('html', ngExpressEngine({  bootstrap: AppServerModuleNgFactory,  providers: [    provideModuleMap(LAZY_MODULE_MAP)  ]})); app.set('view engine', 'html');app.set('views', join(DIST_FOLDER, 'browser')); // TODO: implement data requests securelyapp.get('/api/*', (req, res) => {  res.status(404).send('data requests are not supported');}); // Server static files from /browserapp.get('*.*', express.static(join(DIST_FOLDER, 'browser'))); // All regular routes use the Universal engineapp.get('*', (req, res) => {  res.render('index', { req });}); // Start up the Node serverapp.listen(PORT, () => {  console.log(`Node server listening on http://localhost:${PORT}`);});

这个范例服务器是不安全的! 如果你要把它作为正式的 Angular 应用服务器,别忘了添加中间件来对用户进行认证和授权。

Universal 模板引擎

这个文件中最重要的部分是 ngExpressEngine 函数:

server.ts

content_copyapp.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,
  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ]
}));

ngExpressEngine 是对 Universal 的 renderModuleFactory 函数的封装。它会把客户端请求转换成服务端渲染的 HTML 页面。 你还要在某个适用于你服务端技术栈的模板引擎中调用这个函数。

第一个参数是你以前写过的 AppServerModule。 它是 Universal 服务端渲染器和你的应用之间的桥梁。

第二个参数是 extraProviders。它是在这个服务器上运行时才需要的一些可选的 Angular 依赖注入提供商。

当你的应用需要那些只有当运行在服务器实例中才需要的信息时,就要提供 extraProviders 参数。

这里所需的信息就是正在运行的服务器的源地址,它通过 APP_BASE_HREF令牌提供,以便应用可以 计算出 HTTP URL 的绝对地址。

ngExpressEngine 函数返回了一个会解析成渲染好的页面的承诺(Promise)

接下来你的引擎要决定拿这个页面做点什么。 现在这个引擎的回调函数中,把渲染好的页面返回给了 Web 服务器,然后服务器通过 HTTP 响应把它转发给了客户端。

这个包装器对于隐藏 renderModuleFactory 的复杂性非常有帮助。 在 Universal 代码库中还有更多针对其它后端技术的包装器。

过滤请求的 URL

Web 服务器必须把对应用页面的请求和其它类型的请求区分开。

这可不像拦截对根路径 / 的请求那么简单。 浏览器可以请求应用中的任何一个路由地址,比如 /dashboard/heroes/detail:12。 事实上,如果应用会通过服务器渲染,那么应用中点击的任何一个链接都会发到服务器,就像导航时的地址会发到路由器一样。

幸运的是,应用的路由具有一些共同特征:它们的 URL 一般不带文件扩展名。

数据请求也可能不带扩展名,不过他们很容易识别出来,因为它们总是用 /api 开头。

所有静态资源请求都具有一个扩展名(比如 main.js/node_modules/zone.js/dist/zone.js)。

所以,我们很容易识别出这三种类型的请求,并用不同的方式处理它们。

  1. 数据请求 - 请求的 URL 用 /api 开头
  2. 应用导航 - 请求的 URL 不带扩展名
  3. 静态资源 - 所有其它请求。

Express 服务器是一系列中间件构成的管道,它会挨个对 URL 请求进行过滤和处理。

你通过通过调用 app.get() 来配置 Express 服务器的管道,就像下面这个数据请求一样:

server.ts (data URL)

content_copy// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
  res.status(404).send('data requests are not supported');
});

这个范例服务器并没有处理数据请求。

本教程的“内存 Web API” 模块(一个演示及开发工具)拦截了所有 HTTP 调用,并且模拟了远端数据服务器的行为。 在实践中,你应该移除这个模块,并且在服务器上注册你的 Web API 中间件。

Universal HTTP 请求具有不同的安全需求

从浏览器的应用中发起 HTTP 请求和从服务器的 Universal 应用中发起请求是不一样的。

当浏览器发起 HTTP 请求时,服务器可以假设存在 Cookie、XSRF 头等等。

比如,浏览器会自动发送当前用户的认证 Cookie。 但 Angular Universal 不能把这些凭证发送给独立的数据服务器。 如果你的服务器要处理 HTTP 请求,你就得自行添加安全装置。

下列代码会过滤出不带扩展名的 URL,并把它们当做导航请求进行处理。

server.ts (navigation)

content_copy// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render('index', { req });
});

安全的提供静态文件

单独的 app.use() 会处理所有其它 URL,比如对 JavaScript 、图片和样式表等静态资源的请求。

要保证客户端只能下载那些允许他们访问的文件,你应该把所有面向客户端的资源文件都放在 /dist 目录下,并且只允许客户端请求来自 /dist目录下的文件。

下列 Express 代码会把剩下的所有请求都路由到 /dist 目录下,如果文件未找到,就会返回 404 - NOT FOUND

server.ts (static files)

content_copy// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

配置 Universal

这个服务端应用需要自己的构建配置。

Universal 的 TypeScript 配置

在项目的根目录下创建一个 tsconfig.server.json 文件来配置 TypeScript 和这个 Universal 应用的 AOT 编译选项。

src/tsconfig.server.json

content_copy{  "extends": "../tsconfig.json",  "compilerOptions": {    "outDir": "../out-tsc/app",    "baseUrl": "./",    "module": "commonjs",    "types": []  },  "exclude": [    "test.ts",    "**/*.spec.ts"  ],  "angularCompilerOptions": {    "entryModule": "app/app.server.module#AppServerModule"  }}

这个配置扩展了根目录下的 tsconfig.json 文件,注意它们在某些设置上的差异。

  • module 属性必须是 commonjs,这样它才能被 require() 进你的服务端应用。
  • angularCompilerOptions 部分有一些面向 AOT 编译器的选项:
    • entryModule - 服务端应用的根模块,其格式为 path/to/file#ClassName

Universal 的 Webpack 配置

Universal 应用不需要任何额外的 Webpack 配置,CLI 会帮你处理它们,但是由于这个服务器是 TypeScript 应用,所以你要使用 Webpack 来转译它。

在项目的根目录下创建一个 webpack.server.config.js 文件,代码如下:

webpack.server.config.js

content_copyconst path = require('path');const webpack = require('webpack'); module.exports = {  entry: { server: './server.ts' },  resolve: { extensions: ['.js', '.ts'] },  target: 'node',  mode: 'none',  // this makes sure we include node_modules and other 3rd party libraries  externals: [/node_modules/],  output: {    path: path.join(__dirname, 'dist'),    filename: '[name].js'  },  module: {    rules: [{ test: /\.ts$/, loader: 'ts-loader' }]  },  plugins: [    // Temporary Fix for issue: https://github.com/angular/angular/issues/11580    // for 'WARNING Critical dependency: the request of a dependency is an expression'    new webpack.ContextReplacementPlugin(      /(.+)?angular(\\|\/)core(.+)?/,      path.join(__dirname, 'src'), // location of your src      {} // a map of your routes    ),    new webpack.ContextReplacementPlugin(      /(.+)?express(\\|\/)(.+)?/,      path.join(__dirname, 'src'),      {}    )  ]};

Webpack 配置超出了本文的讨论范围。

Angular CLI 配置

CLI 提供了针对不同目标的构建器。常见的目标,如 buildserve 都已经存在于 angular.json 的配置中了。要指定一个服务端渲染的构建,请在 architect 配置对象中添加一个 server 目标。

  • outputPath 表明要把构建结果放到哪里。
  • main 提供了主入口点,指向前面创建的 main.server.ts
  • tsConfig 表明要用 tsconfig.server.json 作为供 TypeScript 和 AOT 编译器使用的配置。
content_copy"architect": {  ...  "server": {    "builder": "@angular-devkit/build-angular:server",    "options": {      "outputPath": "dist/server",      "main": "src/main.server.ts",      "tsConfig": "src/tsconfig.server.json"    }  }  ...}

使用 Universal 构建和运行

现在,你已经创建了 TypeScript 和 Webpack 的配置文件并配置好了 Angular CLI,可以构建并运行这个 Universal 应用了。

First add the build and serve commands to the scripts section of the package.json:

content_copy"scripts": {
    ...
    "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
    "serve:ssr": "node dist/server",
    "build:client-and-server-bundles": "ng build --prod && ng run angular.io-example:server",
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors"
    ...
}

构建

在命令行提示中输入

content_copynpm run build:ssr

Angular CLI 就会把这个 Universal 应用编译进两个目录:browserserver。 Webpack 会把 server.ts 文件转译成 JavaScript。

启动服务器

构建完应用之后,启动服务器。

content_copynpm run serve:ssr

在控制台窗口中应该看到

content_copyNode server listening on http://localhost:4000

Universal 实战

打开浏览器,访问 http://localhost:4000/。 你会看到熟悉的《英雄指南》仪表盘页。

通过 routerLink 能进行正常导航。 你可以从仪表盘导航到英雄列表页,还可以返回。 你可以在仪表盘页点击一个英雄,以显示他的详情页。

但是点击、鼠标移动和键盘操作都不行。

  • 点击英雄列表页中的英雄没反应。
  • 你也不能添加或删除英雄。
  • 仪表盘页面上的搜索框不理你。
  • 详情页中的 BackSave 按钮也没反应。

除了点击 RouterLink 之外的用户事件都不支持。用户必须等待完整的客户端应用就绪。

直到你编译出客户端应用,并把它们的输出移到 dist/ 目录下,这个客户端应用才会就绪。 你自己稍微花点时间完成这步就可以了。

限流

在开发机上,从服务端渲染应用到客户端应用的转换完成的太快了。 你可以模拟一个慢速网络,来把这个转换过程看得更清楚一点,以便更好地欣赏在性能低、网络烂的设备上 Universal 应用的启动速度优势。

打开 Chrome 开发工具,并打开 Network 页。 在菜单栏的最右侧找到 Network Throttling(网络限流) 下拉框。

随便试一个 “3G” 速度。 服务端渲染的应用将会立即启动,不过加载完整的客户端应用可能要花几秒钟。

小结

本文为你演示了如何把一个现有 Angular 应用转换成支持服务端渲染的 Universal 应用。 还解释了为何要这么做的一些关键原因。

  • 帮助网络爬虫(SEO)
  • 支持低带宽或低功耗设备
  • 快速加载首屏

Angular Universal 可以大幅提升应用的启动性能观感。 在越是慢速的网络下,使用 Universal 来为用户展现首屏就越能体现出更大的优势。

服务端渲染
AngularJS

Angular 是一个开发平台。它能帮你更轻松的构建 Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。

AngularJS目录

1.快速上手 | quick start
2.语言服务
3.安全
4.环境准备与部署
5.Service Worker
6.保持最新
7.从 AngularJS 升级
8.服务端渲染
9.Visual Studio 2015 快速上手
10.风格指南
11.国际化
12.测试
13.路由与导航
14. 教程 | Tutorial
15.架构
16.组件与模板
17.表单
18.可观察对象与RxJS
19.引导启动
20.Angular 模块
21.依赖注入
22.HttpClient
23.词汇表
24.AngularJS 应用
25.AngularJS 模块
26.AngularJS 事件
27.AngularJS HTML DOM
28.AngularJS 过滤器
29.AngularJS 控制器
30.AngularJS 指令
31.AngularJS 表达式
32.AngularJS 简介
33.AngularJS 参考手册
34.AngularJS 实例
35.AngularJS 输入验证
36.AngularJS 表单
37.AngularJS SQL
38.AngularJS 表格
39.AngularJS Http
40.AngularJS 包含
41.AngularJS Bootstrap
42.AngularJS API
43.AngularJS ng-checked 指令
44.AngularJS ng-change 指令
45.AngularJS ng-blur 指令
46.AngularJS ng-bind-template 指令
47.AngularJS ng-bind-html 指令
48.AngularJS ng-bind 指令
49.AngularJS ng-app 指令
50.AngularJS Scope(作用域)
51.AngularJS ng-model 指令
52.AngularJS ng-dblclick 指令
53.AngularJS ng-cut 指令
54.AngularJS ng-csp 指令
55.AngularJS ng-copy 指令
56.AngularJS ng-controller 指令
57.AngularJS ng-cloak 指令
58.AngularJS ng-click 指令
59.AngularJS ng-class-odd 指令
60.AngularJS ng-class-even 指令
61.AngularJS ng-class 指令
62.AngularJS ng-keyup 指令
63.AngularJS ng-keypress 指令
64.AngularJS ng-keydown 指令
65.AngularJS ng-init 指令
66.AngularJS ng-include 指令
67.AngularJS ng-if 指令
68.AngularJS ng-href 指令
69.AngularJS ng-hide 指令
70.AngularJS ng-focus 指令
71.AngularJS ng-disabled 指令
72.AngularJS ng-non-bindable 指令
73.AngularJS ng-mouseup 指令
74.AngularJS ng-mouseover 指令
75.AngularJS ng-mousemove 指令
76.AngularJS ng-mouseleave 指令
77.AngularJS ng-mouseenter 指令
78.AngularJS ng-mousedown 指令
79.AngularJS ng-model-options 指令
80.AngularJS ng-model 指令
81.AngularJS ng-list 指令
82.AngularJS ng-style 指令
83.AngularJS ng-srcset 指令
84.AngularJS ng-src 指令
85.AngularJS ng-show 指令
86.AngularJS ng-selected 指令
87.AngularJS ng-repeat 指令
88.AngularJS ng-readonly 指令
89.AngularJS ng-paste 指令
90.AngularJS ng-options 指令
91.AngularJS ng-open 指令
92.AngularJS ng-value 指令
93.AngularJS ng-switch 指令
94.AngularJS ng-submit 指令
95.AngularJS 服务(Service)
96.AngularJS Select(选择框)
97.AngularJS 动画
98.AngularJS 依赖注入