Angular 初学者快速上手教程

课程介绍

本课程是一个系列基础教程,目标是带领读者上手实战,课程以新版本 Angular 的 3 个核心概念作为主线:组件、路由、模块,加上业务开发过程中必须用到的特性:工具、指令、表单、RxJS、i18n、测试,一共分为 9 部分,34 篇文章。

除了组件、路由、模块这 3 个核心小节具有很强的关联性之外,其它内容都是完全独立的,您可以在用到的时候再翻阅。

认真读完这个系列文章之后,您会深入理解新版本 Angular 的概念模型,具备使用 Angular 上手进行开发的基本能力。

课程内容

Angular 初学者快速上手教程导读

课程特色

你可能会问:Angular 的文章到处有,网上一大片,我为什么要来读你这个系列文章?

这是非常好的一个问题,说明你对阅读内容有质量要求。

如果是我,我也会问这个问题。

整体上说,这个系列的文章有以下特色:

  1. 我会按照初学者一般的学习过程,用我自己的语言一步一步进行讲解。如你所知,最近的5年我一直在玩前端方面的东西,从 jQuery、SVG、ExtJS、Adobe Flex、Angular,这样一路玩过来。尤其是2016年,这一整年的时间我都代表 Angular 项目组在中国进行技术推广。在这5年,我在超过40家企业、开源组织、大学里面进行了大量演讲,在网络上发布了大量的视频和文章。在到处交流的过程中,认识了很多人,有经验丰富的后端开发者,也有新入行的初学者,他们跟我讲过很多自己的困惑。所以,这个系列文章里面的内容我至少反复讲过20遍以上,我会把常见的一些疑问融入在内容里面。

  2. 我会帮你扫平日常开发中常见的坑,这些坑大部分都是开发者们反馈给我的,或者说到我这里吐槽过的。举几个典型的例子:

    • 很多开发者到我这里来抱怨说,在 Windows 平台上安装 @angular/cli 会报很多 error,那是因为 @angular/cli 在 Windows 平台上面依赖 Python 和Visual Studio 环境,而很多开发者的机器上并没有安装这些东西。
    • node-sass 模块被墙的问题,强烈推荐使用 cnpm 进行安装,可以非常有效地避免撞墙。
    • 一些开发者来抱怨说 @angular/cli 在打包的时候加上 --prod 参数会报错,无法编译。这是一个很常见的问题,因为 @angular/cli 最新的版本经常会有 bug,只要在你项目的 package.json 里面降低一个小版本号就OK。
    • @angular/cli 默认生成的 karma.conf.js 配置文件里面采用了一个有 bug 的 html 报告生成器,导致 ng test 运行报错,我们需要把这个 reporter 改成mocha(摩卡)。
    • 有一些朋友说,本地开发的时候运行得很好,上线之后所有请求404。这也是一个常见的坑,因为你需要给 Web 容器配置一下处理 http 请求的规则,把前端路由扔回去交给 Angular 处理,请参考这份文档

    诸如此类的坑还有不少,我都是一个坑一个坑踩过来的。当然,我相信你自己也能踩过来,但是从节约时间的角度看,还是跟着我的思路走一遍更快不是吗?

  3. 这个系列的文章全部聚焦使用层面的话题,覆盖日常开发中使用频最高的特性。除非迫不得已,尽量不扯原理。长期以来,我发现有很多朋友的学习方式存在误区。比如:有一些人上来就去研究“变更检测”的原理,还有 RxJS 的原理,这种方式除了打击你自己的自信心之外并不能得到任何好处。因为你迟早会发现,在计算机领域,任何东西研究到最底层都和“算法”、“数据结构”、“设计模式”有关。而就我所知,很多朋友并不具备研究这些内容的基础知识,不过是白白浪费自己的时间而已。所以,我推荐采用更加务实一点的方案,首先学会如何使用,等用熟了,有时间、有闲情的时候再去研究那些底层的原理。设计发动机很难,但是学会开车并不难,对吧?所以我写这个系列的目标很简单,就是带你学会开车,而不是教你设计发动机。

  4. 这个系列的文章非常看重“概念模型”( Mental Model )的构建。我发现,很多开发者已经做过非常多的项目,但是当你跟他聊的时候,你很快就会发现他并没有掌握这门框架的精髓。打几个比方,当别人提到 Spring 的时候,你的大脑里面第一个想到一定是 DI、IOC、AOP 这些东西;当别人提到 Hibernate 或者 Mybatis 的时候,你的大脑里面立即会浮现出 ORM 的概念;当别人提到 React 的时候,你想到的应该是 VDom、JSX;当别人提到 jQuery 的时候,你首先想到的应该是$对吧?所以,你可以看到,任何一个成功的框架都有自己独创的“概念模型”,或者叫“核心价值”也可以。这是框架本身存在的价值,也是你掌握这门框架应该紧扣的主线,而不是上来就陷入到茫茫多的技术细节里面去。

  5. 文章里面所涉及到例子总数量大约200个左右,有少量例子来自官方文档,其它都是我自己一点一点手动敲出来的。我把这些例子分成了9个开源项目,它们互相独立,方便大家进行参考和练习。这些教学用的开源项目本身是免费的,列在这篇文章的尾部。

Angular 的概念模型

既然如此,问题就来了,新版本的 Angular 的核心概念是什么呢?

非常简单,一切都是围绕着“组件”( Component )的概念展开的:

enter image description here

  • Component(组件)是整个框架的核心,也是终极目标。“组件化”的意义有2个:第一是分治,因为有了组件之后,我们可以把各种逻辑封装在组件内部,避免混在一起;第二是复用,封装成组件之后不仅可以在项目内部复用,而且可以沉淀下来跨项目复用。
  • NgModule(模块)是组织业务代码的利器,按照你自己的业务场景,把组件、服务、路由打包到模块里面,形成一个个的积木块,然后再用这些积木块来搭建出高楼大厦。
  • Router(路由)的角色也非常重要,它有3个重要的作用:第一是封装浏览器的 History 操作;第二是负责异步模块的加载;第三是管理组件的生命周期。

所以,在这个系列的文章里面,Component、NgModule、Router 加起来会占据绝大部分篇幅,而一些琐碎的小特性会被忽略掉。我相信,你只要紧扣“组件化”这个主线,就能站在一个很高的角度统摄全局,从而掌握到这门框架的精髓。

适合阅读的人群

这个系列的文章适合以下人群阅读:

  • Angular 新版本的初学者
  • 有 AngularJS 1.x 经验的开发者
  • 希望了解 Angular 新版本核心特性的开发者

特别注意:这个系列的文章不是前端入门读物,你至少需要会一门编程语言,无论前端还是后端都可以,如果你曾经使用过一门前端框架,那就更好了。

集中回答一些常见的问题

浏览器兼容性

关于 Angular 的浏览器兼容性,请看下图:

enter image description here

有一些国内的开发者会来争论兼容 IE8 的问题,我想给你两个事实:

  • 第一个事实是:截至2017年7月底,Chrome 的全球市场份额已经接近60%,加上 Firefox的12.28%,真的没有那么多人用 IE 了。

enter image description here

数据来源

  • 第二个事实是:天猫已经于2016年4月宣布放弃支持 IE6、7、8。而根据百度流量研究院的统计,IE8目前的整体市场份额已经下降到了9.31%:

enter image description here

数据来源

不值得为了这么少的市场份额付出那么多的研发和维护成本。

你完全可以以上两点事实去说服你的客户。

命名约定

老版本使用 AngularJS 指代,所有新版本都叫做 Angular。原因很好理解,因为老版本是用 JS 开发的,所以带一个 JS 后缀,而新版本是基于 TypeScript 的,带 JS 后缀不合适。

关于 TypeScript

这个系列的文章不会单独讲 TypeScript,正如我一直强调的:TypeScript 不难,JavaScript 才难。你跟着我的思路,TypeScript 绝对不会成为你学习 Angular 的障碍。相反,一旦你写熟练了之后,TypeScript 可以非常有效地提升编码效率和程序可读性。

关于版本号

根据官方的解释,Angular 从2.0之后会保证向下兼容,每隔半年会升级一个大版本,只有升级大版本的时候才会做一些 breaking change。

所以这个系列文章里面不再强调版本号,涉及到的所有实例代码都基于目前(2017-10)最新的4.x版本。

内容列表

这个系列文章一共分11章,34个小节。

  • 1 搭建开发环境
  • 2-1 组件概述
  • 2-2 把 CSS 预编译器改成 SASS
  • 2-3 模板
  • 2-4 组件间通讯
  • 2-5 生命周期钩子
  • 2-6 动效
  • 2-7 动态组件
  • 2-8 ShadowDOM
  • 2-9 内容投影
  • 2-10 @ContentChild@ContentChildren
  • 2-11 @ViewChild 与 @ViewChildren
  • 2-12 与 Polymer 封装组件的方式简单对比
  • 2-13 封装并发布你自己的组件库
  • 3-1 指令简介
  • 3-2 自定义指令
  • 3-3 直接在组件里面操作 DOM
  • 4 模块 @NgModule
  • 5-1 路由概述
  • 5-2 路由基本用法
  • 5-3 模块预加载
  • 5-4 路由守卫
  • 5-5 多重出口
  • 6-1 表单快速上手
  • 6-2 双向数据绑定
  • 6-3 表单校验
  • 6-4 模型驱动型表单
  • 6-5 动态表单
  • 7 服务
  • 8 RxJS 快速上手教程
  • 9 国际化
  • 10 自动化测试
  • 11 参考资源

本系列课程对应的所有示例项目列表:

  1. https://gitee.com/mumu-osc/learn-component
  2. https://gitee.com/mumu-osc/learn-directive
  3. https://gitee.com/mumu-osc/learn-router
  4. https://gitee.com/mumu-osc/learn-module
  5. https://gitee.com/mumu-osc/learn-form
  6. https://gitee.com/mumu-osc/learn-service
  7. https://gitee.com/mumu-osc/learn-test
  8. https://gitee.com/mumu-osc/learn-webpack
  9. https://github.com/damoqiongqiu/angular-seo

最后是那一句套话:水平有限,错漏难免,欢迎指正。可以在我的读者圈里跟我沟通交流。

第1课:搭建开发环境

NodeJS

enter image description here

2009年,NodeJS 发布了第一个版本,标志着前端开发正式告别了刀耕火种的原始状态,开始进入工业化时代。

在 NodeJS 出现之前,前端开发领域有很多事情我们是做不到的,例如:

  • JS 代码的合并、压缩、混淆。
  • CSS 预处理。
  • 前端自动化测试。

而这一切在 NodeJS 出现之后都得到了很好的解决:

  • 对 JS 代码的预处理经历了 Grunt、Gulp 的短暂辉煌之后,终于在 webpack 这里形成了事实标准的局面。
  • CSS 的预处理也从 LESS 发展到了 SASS。
  • 自动化测试一直是前端开发中的一个巨大痛点,由于前端在运行时严重依赖浏览器环境,导致我们一直无法像测试后端代码那样可以去编写测试用例。在有了 NodeJS 之后,我们终于有了 Karma+Jasmine 这样的单元测试组合,也有了基于 WebDriverJS 这样的可以和浏览器进行通讯的集成测试神器。

就前端开发目前整体的状态来说,无论你使用什么框架,NodeJS、webpack、SASS、Karma+Jasmine、WebDriverJS 这个组合是无论如何绕不过去的。

@angular/cli

enter image description here

在开发 Angular 应用的时候,当然也离不开大量基于 NodeJS 的工具,我们需要 TypeScript compiler、webpack、Karma、Jasmine、Protracter 等模块。

有相关经验的开发者都知道,自己从头开始去搭建一套基于 webpack 的开发环境是一件非常麻烦的事情。很多初学者在搭建环境这一步上面消耗了过多的精力,导致学习热情受到了沉重的打击。

当团队规模比较大的时候,在每个人的机器上配置环境需要消耗大量的时间。有一些团队为了避开这个坑,利用 Docker 来做开发环境的同步和版本升级,看起来也是一个非常不错的方案。

Angular 项目组从一开始就注意到了这个问题,所以有了 @angular/cli 这个神器,它的底层基于 webpack,集成了以上提到的所有 NodeJS 组件。你只要装好 @angular/cli 就够了,而不需要自己从头一步一步安装那些 NodeJS 插件。

当然,在安装 @angular/cli 之前你需要先把 NodeJS 安装好,请到官方网站下载安装包: https://nodejs.org/ ,安装过程和普通软件没有区别。装好 NodeJS 之后就可以安装 @angular/cli 了,由于 npm 会自动访问海外的服务器,所以强烈推荐使用 cnpm 进行安装:

npm i -g cnpm --registry=https://registry.npm.taobao.orgcnpm i -g @angular/cli

cnpm 是淘宝发布的一款工具,会自动把 npm 上面的所有包定时同步到国内的服务器上来,cnpm 本身也是一款 NodeJS 模块。

@angular/cli 安装成功之后你的终端里面将会多出一个名叫 ng 的命令,敲下 ng,将会显示完整的帮助文档:

enter image description here

创建第一个项目

我们来创建第一个入门项目 HelloAngular,请在你的终端里面运行:

ng new HelloAngular

@angular/cli 将会自动帮你把目录结构创建好,并且会自动生成一些模板化的文件,就像这样:

enter image description here

请特别注意:@angular/cli 在自动生成好项目骨架之后,会立即自动使用 npm 来安装所依赖的 Node 模块,所以这里我们要 Ctrl+C 终止掉,然后自己进入项目的根目录,使用 cnpm 来进行安装。

enter image description here

安装完成之后,使用 ng serve 命令启动项目:

enter image description here

打开你的浏览器,访问默认的4200端口,看到以下界面说明环境 OK 了:

enter image description here

请注意:

  • 这里是 serve,不是 server,我看到一些初学者经常坑在这个地方。
  • 如果你需要修改端口号,可以用 ng serve --port ****来进行指定。
  • 如果你想让编译的包更小一些,可以使用 ng serve --prod,@angular/cli 会启用 TreeShaking 特性,加了参数之后编译的过程也会慢很多。所以,在正常的开发过程里面请不要加 --prod 参数。
  • ng serve 是在内存里面生成项目,如果你想看到项目编译之后的产物,请运行 ng build。构建最终产品版本可以加参数,ng build --prod。

ng 提供了很多非常好用的工具,除了可以利用 ng new 来自动创建项目骨架之外,它还可以帮助我们创建 Angular 里面所涉及到的很多模块,最常用的几个如下:

  • 自动创建组件:ng generate component MyComponent,可以简写成 ng g c MyComponent。创建组件的时候也可以带路径,比如:ng generate component mydir/MyComponent
  • 自动创建指令:ng g d MyDirective
  • 自动创建服务:ng g s MyService
  • 构建项目:ng build,如果你想构建最终的产品版本,可以用 ng build --prod

更多的命令和参数请在终端里面敲 ng 仔细查看,尽快熟悉这些工具可以非常显著地提升你的编码效率。

一些常见的坑

@angular/cli 这种“全家桶”式的设计带来了很大的方便,同时也有一些人不太喜欢,因为很多底层的东西被屏蔽掉了,开发者不能天马行空地自由发挥。比如:@angular/cli 把底层 webpack 的配置文件屏蔽掉了,很多喜欢自己手动配 webpack 的开发者就感到很不爽。

对于国内的开发者来说,上面这些其实不是最重要的,国内开发者碰到的坑主要是由两点引起的:

  1. 第一点是网络问题:比如 node-sass 这个模块你很有可能就装不上,原因你懂的。
  2. 第二点是开发环境导致的问题:国内使用 Windows 平台的开发者比例依然巨大,而 @angular/cli 在 Windows 平台上有一些非常恶心的依赖,比如它需要依赖 python 环境、Visual Studio 环境。

所以,如果你的开发平台是 Windows,请特别注意:

  1. 如果你知道如何给 npm 配置代理,也知道如何翻墙,请首选 npm 来安装 @angular/cli。
  2. 否则,请使用 cnpm 来安装 @angular/cli,原因有三:1、cnpm 的缓存服务器在国内,你装东西的速度会快很多;2、用 cnpm 可以帮你避开某些模块装不上的问题,因为它在服务器上面做了缓存;3、cnpm 还把一些包都预编译好了缓存在服务端,不需要把源码下载到你本地去编译,所以你的机器上可以没有那一大堆麻烦的环境。
  3. 如果安装失败,请手动把 node_modules 目录删掉重试一遍,全局的@angular/cli 也需要删掉重装,cnpm uninstall -g @angular/cli。
  4. 如果 node_modules 删不掉,爆出路径过长之类的错误,请尝试用一些文件粉碎机之类的工具强行删除。
  5. 最新版本的 @angular/cli 经常会有 bug,尤其是在 Windows 平台上面,所以请不要追新版本追太紧。如果你发现了莫名其妙的问题,请尝试降低一个主版本试试。这一点非常重要,很多初学者会非常困惑,代码什么都没改,就升级了一下环境,然后就各种编译报错。
  6. 对于 Mac 用户或者 *nix 用户,请特别注意权限问题,命令前面最好加上 sudo,保证有 root 权限。
  7. 无论你用什么开发环境,安装的过程中请仔细看 log。很多朋友没有看 log 的习惯,报错的时候直接懵掉,根本不知道发生了什么。

VS Code

enter image description here

如你所知,一直以来,前端开发领域并没有一款特别好用的开发和调试工具。

  • WebStorm 很强大,但是吃资源很严重。
  • Sublime Text 插件很多,可惜要收费,而国内的企业还没有养成花钱购买开发工具的习惯。
  • Chrome 的开发者工具很好用,但是要直接调试 TypeScript 很麻烦。

所以,Visual Studio Code(简称 VS Code)才会呈现出爆炸性增长的趋势。它是微软开发的一款前端编辑器,完全开源免费。VS Code 底层是 Electron,界面本身是用 TypeScript 开发的。对于 Angular 开发者来说,当然要强烈推荐 VS Code。最值得一提的是,从1.14开始,可以直接在 VS Code 里面调试 TypeScript 代码。

第一步:环境配置

  • 确保你的 Chrome 安装在默认位置。
  • 确保你的 VS Code 里面安装了 Debugger for Chrome 这个插件。
  • 把 @angular/cli 安装到全局空间 npm install -g @angular/cli,国内用户请使用 cnpm 进行安装。注意,你最好升级到最新版本的 @angular/cli,避免版本兼容问题。
  • 用 @angular/cli 创建新项目 ng new my-app,本来就已经用 @angular/cli 创建的项目请忽略这一步,继续往下走,因为只要是 cli 创建的项目,后面的步骤都是有效的。
  • 用 VS Code 打开项目,进入项目根目录

第二步:配置 launch.json

enter image description here

请参照以上步骤打开 launch.json 配置文件。

enter image description here

请把你本地 launch.json 文件里面的内容改成这样:

{    "version": "0.2.0",    "configurations": [        {            "type": "chrome",            "request": "launch",            "name": "Chrome",            "url": "http://localhost:4200",            "webRoot": "${workspaceRoot}"        }    ]}

第三步:开始 Debug

在你的 app.component.ts 的构造函数里面打个断点,我本地是这样打断点的:

enter image description here

打开终端,进入项目根目录,运行 ng serve 启动项目,然后从 VS Code 的 debug 界面启动 Chrome

enter image description here

注意,你可能需要 F5 刷新一下 Chrome 才能进入断点!

enter image description here

小结

目前,无论你使用什么前端框架,都必然要使用到各种 NodeJS 工具,Angular 也不例外。与其它框架不同,Angular 从一开始就走的“全家桶”式的设计思路,因此 @angular/cli 这款工具里面集成了日常开发需要使用的所有 Node 模块,使用 @angular/cli 可以大幅度降低搭建开发环境的难度。

第2-1课:组件:概述

enter image description here

几乎所有前端框架都在玩“组件化”,而且最近都不约而同地选择了“标签化”这种思路,Angular 也不例外。

对新版本的 Angular 来说,一切都是围绕着“组件化”展开的,组件是 Angular 的核心概念模型。

以下是一个最简单的 Angular 组件定义:

enter image description here

  • @Component:这是一个 Decorator(装饰器),其作用类似于 Java 里面的注解。Decorator 这个语言特性目前(2017-10)处于 Stage 2(草稿)状态,还不是 ECMA 的正式规范。
  • selector:组件的标签名,外部使用者可以这样来使用这个组件:<app-root>。默认情况下,ng 命令生成出来的组件都会带上一个 app 前缀,如果你不喜欢,可以在 angular-cli.json 里面修改 prefix 配置项,设置为空字符串将会不带任何前缀。
  • templateUrl:引用外部的 HTML 模板。如果你想直接编写内联模板,可以使用 template,支持 ES6 引入的“模板字符串”写法
  • styleUrls:引用外部 CSS 样式文件,这是一个数组,也就意味着可以引用多份 CSS 文件。
  • export class AppComponent:这是 ES6 里面引入的模块和 class 定义方式。

本节完整的实例代码请参见这里

第2-2课:组件:把 CSS 预编译器改成 SASS

enter image description here

SASS 是一款非常好用的 CSS 预编译器,Bootstrap 官方从4.0开始已经切换到了 SASS。

目前(2017-10),@angular/cli 创建项目的时候没有自动使用 SASS 作为预编译器,我们需要自己手动修改一些配置文件,请按照以下步骤依次修改:

  • angular-cli.json 里面的 styles.css 后缀改成 .scssenter image description here当你后面再使用 ng g c *** 自动创建组件的时候,默认就会生成 .scss 后缀的样式文件了。

  • angular-cli.json 里面的 styleExt 改成 .scssenter image description here

  • src 下面 style.css 改成 style.scssenter image description here

  • app.component.scssenter image description here

  • app.component.ts 里面对应修改enter image description here

改完之后,重新 ng serve,打开浏览器查看效果。

小结

本节完整的实例代码请参见这里

SASS 的 API 请参考官方网站

SASS 只是一个预编译器,它支持所有 CSS 原生语法。利用 SASS 可以提升你的 CSS 编码效率,增强 CSS 代码的可维护性,但是千万不要幻想从此就可以不用学习 CSS 基础知识了。

第2-3课:组件:模板

enter image description here

模板是编写 Angular 组件最重要的一环,你至少需要深入理解以下知识点才能玩转 Angular 模板:

  1. 对比各种 JS 模板引擎的设计思路
  2. Mustache(八字胡)语法
  3. 模板内的局部变量
  4. 属性绑定、事件绑定、双向绑定
  5. 在模板里面使用结构型指令 *ngIf、*ngFor、ngSwitch
  6. 在模板里面使用属性型指令 NgClass、NgStyle、NgModel
  7. 在模板里面使用管道格式化数据

对比各种 JS 模板引擎的设计思路

几乎每一款前端框架都会提供自己的模板语法,在 jQuery 如日中天的时代,有 Handlebars 那种功能超强的模板。最近有 React 推崇的 JSX 模板写法,当然还有 Angular 提供的那种与“指令”紧密结合的模板语法。

综合来说,无论是哪一种前端模板,大家都比较推崇“轻逻辑”( logic-less )的设计思路。

何为“轻逻辑”?

简而言之,所谓“轻逻辑”就是说,你不能在模板里面编写非常复杂的 JavaScript 表达式。比如,Angular 的模板语法就有规定:

  • 你不能在模板里面 new 对象
  • 不能使用=、+=、-=这类的表达式
  • 不能用++、--运算符
  • 不能使用位运算符

为什么要“轻逻辑”?

有一个非常重要的原因,比如你编写了以下 Angular 模板:

<ul>    <li *ngFor="let race of races">        {{race.name}}    </li></ul>

很明显,浏览器不认识 *ngFor 和 {{...}} 这种语法,所以必须在浏览器里面进行“编译”,获得对应的模板函数,然后再把数据传递给模板函数,最终结合起来获得一堆 HTML 标签,然后才能把这一堆标签插入到 DOM 树里面去。

如果启用了 AOT,处理的步骤有一些变化,@angular/cli 会对模板进行“静态编译”,避免在浏览器里面动态编译的过程。

而 Handlebars 这种模板引擎完全是运行时编译模板字符串的,你可以编写以下代码:

//定义模板字符串var source=`<ul>    {{#each races}}        <li>{{name}}</li>    {{/each}}</ul>`;//在运行时把模板字符串编译成JS函数var templateFn=Handlebars.compile(source);//把数据传给模板函数,获得最终的HTMLvar html=templateFn([    {name:'人族'},    {name:'神族'},    {name:'虫族'}]);

注意到 Handlebars.compile 这个调用了吧?这个地方的本质是在运行时把模板字符串“编译”成了一个 JS 函数。

鉴于 JS 解释执行的特性,你可能会担忧这里会有性能问题。这种担忧是合理的,但是 Handlebars 是一款非常优秀的模板引擎,它在内部做了各种优化和缓存处理。模板字符串一般只会在第一次被调用的时候编译一次,Handlebars 会把编译好的函数缓存起来,后面再次调用的时候会从缓存里面获取,而不会多次进行“编译”。

上面我们多次提到了“编译”这个词,所以很显然这里有一个东西是无法避免的,那就是我们必须提供一个 JS 版的“编译器”,让这个“编译器”运行在浏览器里面,这样才能在运行时把用户编写的模板字符串“编译”成模板函数。

有一些模板引擎会真的去用 JS 编写一款“编译器”出来,比如 Angular 和 Handlebars,它们都真的编写了一款 JS( TS )版的编译器。而有一些简单的模板引擎只是用正则表达式做了字符串替换而已,显得特别简陋。这种简陋的模板引擎对模板的写法有非常多的限制,因为它不是真正的编译器,能支持的语法特性非常有限。

所以,评估一款模板引擎的强弱,最核心的东西就是评估它的“编译器”做得怎么样。但是不管怎么说,毕竟是 JS 版的“编译器”,我们不可能把它做得像 g++ 那么强大,也没有必要做得那么强大,因为这个 JS 版的编译器需要在浏览器里面运行,搞得太复杂浏览器拖不动!

以上就是为什么大多数模板引擎都要强调“轻逻辑”的最根本原因。

对于 Angular 来说,强调“轻逻辑”还有另一个原因:在组件的整个生命周期里面,模板函数会被执行很多次。你可以想象, Angular 每次要刷新组件的外观的时候,都需要去调用一下模板函数,如果你在模板里面编写了非常复杂的代码,一定会增加渲染时间,用户一定会感到界面有“卡顿”。

人眼的视觉延迟大约是100ms到400ms之间,如果整个页面的渲染时间超过400ms,界面基本上就卡得没法用了。有一些做游戏的开发者会追求60fps刷新率的细腻感觉,60分之1秒约等于16.7ms,如果 UI 整体的渲染时间超过了16.7ms,就没法达到这个要求了。

轻逻辑( logic-less )带来了效率的提升,也带来了一些不方便,比如很多模板引擎都实现了 if 语句,但是没有实现 else,所以开发者们在编写复杂业务逻辑的时候模板代码会显得非常啰嗦。

目前来说,并没有完美的方案能同时兼顾运行效率和语法表现能力,这里只能取一个平衡。

Mustache 语法

Mustache 语法也就是你们说的双花括号语法{{...}},老外觉得它像八字胡子,很奇怪啊,难道老外喜欢侧着头看东西?

好消息是,很多模板引擎都接受了 Mustache 语法,这样一来学习量又降低了不少,开心吧?

关于 Mustache 语法,你需要掌握3点:

  1. 它可以获取到组件里面定义的属性值。
  2. 它可以自动计算简单的数学表达式,例如:加减乘除、取模。
  3. 它可以获得方法的返回值。

请依次看例子:

插值语法关键代码实例:

<h3>    欢迎来到{{title}}!</h3>
public title = '假的星际争霸2'; 

简单的数学表达式求值:

<h3>1+1={{1+1}}</h3>

调用组件里面定义的方法:

<h3>可以调用方法{{getVal()}}</h3>
public getVal():any{    return 65535;}

模板内的局部变量

<input #heroInput><p>{{heroInput.value}}</p>

有一些朋友会追问,如果我在模板里面定义的局部变量和组件内部的属性重名会怎么样呢?

如果真的出现了重名,Angular 会按照以下优先级来进行处理:

模板局部变量 > 指令中的同名变量 > 组件中的同名属性。

这种优先级规则和 JSP 里面的变量取值规则非常类似,对比一下很好理解对不对?你可以自己写代码测试一下。

属性绑定

属性绑定是用方括号来做的,写法:

<img [src]="imgSrc" />
public imgSrc:string="./assets/imgs/1.jpg";

很明显,这种绑定是单向的。

事件绑定

事件绑定是用圆括号来做的,写法:

<button class="btn btn-success" (click)="btnClick($event)">测试事件</button>

对应 Component 内部的方法定义:

public btnClick(event):void{    alert("测试事件绑定!");}

双向绑定

双向绑定是通过方括号里面套一个圆括号来做的,模板写法:

<font-resizer [(size)]="fontSizePx"></font-resizer>

对应组件内部的属性定义:

public fontSizePx:number=14;

AngularJS 是第一个把“双向数据绑定”这个特性带到前端来的框架,这也是 AngularJS 当年最受开发者追捧的特性,之一。

根据 AngularJS 团队当年讲的故事,“双向数据绑定”这个特性可以大幅度压缩前端代码的规模。大家可以回想一下 jQuery 时代的做法,如果要实现类似的效果,是不是要自己去编写大量的代码?尤其是那种大规模的表单,一大堆的赋值和取值操作,都是非常丑陋的“面条”代码,而有了“双向数据绑定”特性之后,一个绑定表达式就搞定。

目前,主流的几款前端框架都已经接受了“双向数据绑定”这个特性。

当然,也有一些人不喜欢“双向数据绑定”,还有人专门写了文章来进行批判,也算是前端一景。

在模板里面使用结构型指令

Angular 有3个内置的结构型指令:*ngIf、*ngFor、ngSwitch。ngSwitch 的语法比较啰嗦,使用频率小一些。

*ngIf 代码实例:

<p *ngIf="isShow" style="background-color:#ff3300">显示还是不显示?</p><button class="btn btn-success" (click)="toggleShow()">控制显示隐藏</button>
public isShow:boolean=true;public toggleShow():void{    this.isShow=!this.isShow;}

*ngFor 代码实例:

<li *ngFor="let race of races;let i=index;">    {{i+1}}-{{race.name}}</li>
public races:Array<any>=[    {name:"人族"},    {name:"虫族"},    {name:"神族"}];

*ngSwitch 代码实例:

<div [ngSwitch]="mapStatus">    <p *ngSwitchCase="0">下载中...</p>    <p *ngSwitchCase="1">正在读取...</p>    <p *ngSwitchDefault>系统繁忙...</p></div>
public mapStatus:number=1;

特别注意:一个 HTML 标签上只能同时使用一个结构型的指令。

因为“结构型”指令会修改 DOM 结构,如果在一个标签上使用多个结构型指令,大家都一起去修改 DOM 结构,到时候到底谁说了算?

那么需要在同一个 HTML 上使用多个结构型指令应该怎么办呢?有两个办法:

  • 加一层空的 div 标签
  • 加一层<ng-container>

在模板里面使用属性型指令

使用频率比较高的3个内置指令是:NgClass、NgStyle、NgModel。

NgClass 使用案例代码:

<div [ngClass]="currentClasses">同时批量设置多个样式</div><button class="btn btn-success" (click)="setCurrentClasses()">设置</button>
public currentClasses: {};public canSave: boolean = true;public isUnchanged: boolean = true;public isSpecial: boolean = true;setCurrentClasses() {    this.currentClasses = {        'saveable': this.canSave,        'modified': this.isUnchanged,        'special': this.isSpecial    };}
.saveable{    font-size: 18px;} .modified {    font-weight: bold;}.special{    background-color: #ff3300;}

NgStyle 使用案例代码:

<div [ngStyle]="currentStyles">    用NgStyle批量修改内联样式!</div><button class="btn btn-success" (click)="setCurrentStyles()">设置</button>
public currentStyles: {}public canSave:boolean=false;public isUnchanged:boolean=false;public isSpecial:boolean=false;setCurrentStyles() {    this.currentStyles = {        'font-style':  this.canSave      ? 'italic' : 'normal',        'font-weight': !this.isUnchanged ? 'bold'   : 'normal',        'font-size':   this.isSpecial    ? '36px'   : '12px'    };}

ngStyle 这种方式相当于在代码里面写 CSS 样式,比较丑陋,违反了注意点分离的原则,而且将来不太好修改,非常不建议这样写。

NgModel 使用案例代码:

<p class="text-danger">ngModel只能用在表单类的元素上面</p>    <input [(ngModel)]="currentRace.name"><p>{{currentRace.name}}</p>
public currentRace:any={name:"随机种族"};

请注意,如果你需要使用 NgModel 来进行双向数据绑定,必须要在对应的模块里面 import FormsModule。

管道

管道的一个典型作用是用来格式化数据,来一个最简单的例子:

{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}
public currentTime: Date = new Date();

Angular里面一共内置了12个管道:

enter image description here

在复杂的业务场景里面,12个管道肯定不够用,如果需要自定义管道,请查看这里的例子

管道还有另一个典型的作用,就是用来做国际化,后面有一个独立的小节专门演示 Angular 的国际化写法。

小结

本节完整可运行的实例代码请参见这里请检出 template 分支。

第2-4课:组件:组件间通讯
第2-5课:组件:生命周期钩子
第2-6课:组件:动效
第2-7课:组件:动态组件
第2-8课: 组件:ShadowDOM
第2-9课:组件:内容投影
第2-10课:组件:@ContentChild 和 @ContentChildren
第2-11课:组件:@ViewChild 与 @ViewChildren
第2-12课:与 Polymer 封装组件的方式简单对比
第2-13课:封装并发布你自己的组件库
第3-1课:指令
第3-2课:自定义指令
第3-3课:在组件里面直接操作DOM
第4课:模块 @NgModule
第5-1课:路由:概述
第5-2课:路由:基本用法
第5-3课:路由:模块预加载
第5-4课: 路由:路由守卫
第5-5课:路由:多重出口
第6-1课:表单:快速上手
第6-2课: 表单:双向数据绑定
第6-3课:表单:表单校验
第6-4课:表单:模型驱动型表单
第6-5课:表单:动态表单
第7课:服务
第8课: RxJS 快速上手教程
第9课:国际化
第10课:前端自动化测试
第11课:参考资源

阅读全文: http://gitbook.cn/gitchat/column/59dae2081e6d652a5a9c3603

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页