模板表达式“{{}}”不能引用任何全局命名空间中的成员(如:window、document等等)的原因:
我想原因可能是:
- 为了防止名称冲突,angular在解析模板表达式后,会把表达式中的每个变量var替换成模板所属类的实例instance的属性调用instance.var;
所以如果引用全局命名空间中的成员的话,如:document,解析后就会变成:instance.document,而instance.document是不存在的,所以就引发了错误;
数据绑定
组件类和模板之间的数据交互称为数据绑定;它是一种让模板的各部分与组件的各部分相互合作的机制;
数据绑定的语法有四种形式。每种形式都有一个方向:绑定到DOM、绑定自DOM 或者及双向绑定。
-
{{expression}}
插值; -
[属性]
属性绑定; -
(事件)
事件绑定; -
[(属性)]
双向绑定;本质上是结合属性绑定和事件绑定的语法糖;
属性绑定、事件绑定、插值也属于数据绑定的东范畴;属性绑定和事件绑定即可用于父子组件的传递,也可用于组件的数据模型和模板视图之间的数据传递;所以在父子组件通信的过程中,模板充当类似于桥梁的角色,连接二者的功能逻辑,如下图:
Angular的数据流动机制是靠Angular的变化监测机制驱动着的;
插值
- 插值语法是由一对双大括号
{{}}
组成,插件是一半向的数据流动——从数据模型到模板视图;插值中的变量上下文是组件类本身; - 插值表达式可以把计算后的字符串插入到 HTML 元素标签内的文本或对标签的属性进行赋值。
- 一般来说,括号间的素材是一个模板表达式,Angular 先对它求值,再把它转换成字符串。
- 插值表达式是一个特殊的语法,Angular 会把它转换成属性绑定。
变化监测
Angular并不是用ES5提供的getter、setter语言接口来实现变化监测的,而是以适当的时机去检验对呀的值是否被改动,这个适当的时机并不是以固定的某个频率去执行检验,而通常是在用户操作事件(如:单击事件)、setTimeout或XHR回调等这些异步事件触发之后;Angular捕获这些异步事件的工作是通过Zones库实现的;
如下图所示:第个组件背后都维护着一个独立的变化监测器,这个变化监测器记录着所属组件的数据变更状态。由于应用是以组件树的形式组织,因此第个应用也有着对应的一棵变化监测树。当Zones的捕获到某异步事件后,它都会通知Angular执行变化监测操作,每次变化监测操作都始于根组件,并以深度优先的原则向叶组件遍历执行。
给Angular的属性的各个赋值的方式的区别:
假设:组件A的子组件B的属性有attrB;
则在组件A的模板中给子组件B的attrB属性赋值的方式如下:
- 通过输入绑定语法”[ ]":<B [attrB]="value" ></B>;这种方法会有以下特点:
- 只把attrB当作输入属性对待,所以只能用于当元素B有输入属性attrB时;若元素B没有输入属性attrB,则会报错;
- 会把value当作表达式进行计算,然后把表达式value的值绑定到attrB上,即使value不被引号包裹,也会把value当作表达式;
- 当把[attrB]=“value"的赋值操作去掉,只写[attrB]时,不会执行对attrB的赋值操作;
- 通过标签属性赋值语法:<B attrB="value" ></B>;这种方法会有以下特点:
- 分别把attrB当作各种匹配的特性对待;例如:当attrB可以作为标签的属性,又是一个指令,同时又是一个输入属性时,刚会分别对标签属性attrB和输入属性attrB进行赋值,并且使指令attrB生效;
- 会把等号“=”右边的”value”当作字符串赋值到attrB上,即使value不被引号包裹,也会把value当作纯字符串;
- 当把attrB=“value”的赋值操作去掉,只写attrB时,会对attrB的进行赋值,值为空字符串“”,即相当于:attrB="";
给Angular的输出属性赋值的实际意义:
假设:组件A的子组件B的输出属性是attrB;
则在组件A的模板中给子组件B的attrB输出属性赋值的方式为:(attrB)="expression",其中包裹attrB的圆括号是必须的,无论expression有没有被引号包裹,expression都会被当作是表达式,并且这个表达式是被放在Angular自动创建的回调函数的上下文中执行的,即expression被作为Angular自动创建的回调函数的代码执行的,并且这个回调函数拥有局部变量$event($event极有可能是该回调函数的参数),$event是调用EventEmitter的实例方法emit时传入的参数,所以expression中可以引用$event;
@ViewChild()装饰器:
- 当向@ViewChild()装饰器中传入一个类型Type时,被装饰的量引用的是类型Type的第一个子组件;
- 当向@ViewChild()装饰器中传入一个字符串时,被装饰的量引用的是字符串所对应的模版局部变量;
组件内容嵌入:
对于符合嵌入的内容则会被嵌入,不符合的内容会被从Dom树中移除;
Angular中三种指令的侧重点:
指令的作用是增强模板特性,间接地扩散模板语法;
各种指令的侧重点:
- 属性指令:扩展元素的属性,通常被用来改变元素的外观和行为;
- 结构指令:扩展元素的属性,用来改变DOM树的结构;
- 组件:扩展元素的标签,用来自定义标签;
备注:
虽然指令常用来扩展属性,组件常用来扩展标签,但是它们的选择器的定义是任意的,所以,组件的选择器也可以定义成属性选择器,从而以属性的形式使用组件,指令的选择器也可以定义成标签选择器,从而以标签的形式使用指令;
性能优化方法:
- 缩小变化监测范围;
通过变化监测类ChangeDetectorRef的实例的markForCheck(): void方法标记变化监测的路径为根组件到该组件; - 缩小变化监测时间;
通过变化监测类ChangeDetectorRef的实例的detach(): void 分离变化监测器,然后在适当的时机通过 detectChanges(): void 手动监测变化 或者 通过reattach(): void 再重新安装上变化监测器; - 变化监测策略能用OnPush的不用Default;
- 模板中的表达式的计算量能少则少;因为Angular的变化监测机制,使得模板中的表达式执行频率很高;
- 尽量使用NgForTrackBy指令来替代NgFor指令;
服务
- providers配置选项是依赖注入操作的关键,它会为该组件创建一个注入器对象,并新建相应服务的实例存储到这个注入器里,当需要引入相应服务的实例时,通过TypeScript的类型匹配即可从注入器取出相应的服务实例对象;
- 服务的每一 次沩(也就是一使用providers声明),该服务都会被建出新的实例,组件的所有子组件均默认继承父组件的注入器对象,复用该注入器里存储的服务实例。这种机制可以保证服务以单例模式运行,除非某个子组件再次注入(即通过providers选项声明);
- 因为所有模块(这里指Angular应用级别的模块,并非ES6语言中的模块)都共享着同一个应用级别的根注入器,所以,当把服务注入到模块里时,该服务在整个应用里均能使用;
路由
路由的作用是建立URL路径和组件之间的对应关系,根据不同的URL路径匹配出相应的组件渲染;
Angular应用的引导启动
Angular通过引导运行根模块来启动应用,引导的方式有2种:
- 动态引导;
- 静态引导;
Angular应用运行之前,都需要经过编译器对模块、组件等进行编译,编译完成后才开始启动应用并渲染界面;
动态引导和静态引导的区别就在于编译的时机不同,动态引导是将所有代码加载到浏览器后,在浏览器进行编译;而静态引导是将编译过程前置到开发时的工程打包阶段;
由于静态引导应用时,整个应用已经被预先编译,所以编译器并不并不会被打包到项目代码,这使得代码包体积更小,加载更快,而且也省去了浏览器编译这个步骤,因此应用的启动速度也会更快;
动态引导开发流程简明了,适合在 小型项目 或者 大型应用的开发阶段 中使用,而静态引导需要在开发阶段加入预编译流程,稍显复杂但性能提升明显,推荐使用;
模板
- 模板中的script标签会被忽略;
- html、body、base等标签在模板中是无用的;
模板表达式
模板表达式是指在模板中使用的表达式;比如插值中的表达式、属性绑定中等号右边的表达式等等。
模板表达式产生一个值。 Angular 执行这个表达式,并把它赋值给绑定目标的属性,这个绑定目标可能是 HTML 元素、组件或指令。
很多 JavaScript 表达式也是合法的模板表达式,但不是全部。
JavaScript 中那些具有或可能引发副作用的表达式是被禁止的,包括:
赋值 (=, +=, -=, ...)
new运算符
使用;或,的链式表达式
自增或自减操作符 (++和--)
和 JavaScript语 法的其它显著不同包括:
不支持位运算|和&
具有新的模板表达式运算符,比如|、?.和!。
表达式上下文
典型的表达式上下文就是这个组件实例,它是各种绑定值的来源;
表达式中的上下文变量是由模板变量、指令的上下文变量(如果有)和组件的成员叠加而成的。 如果我们要引用的变量名存在于一个以上的命名空间中,那么,模板变量是最优先的,其次是指令的上下文变量,最后是组件的成员。
模板表达式不能引用全局命名空间中的任何东西,比如window或document。它们也不能调用console.log或Math.max。 它们只能引用表达式上下文中的成员。
表达式应该遵循下列指南:
- 没有可见的副作用
- 执行迅速
- 非常简单
- 幂等性
模板语句
模板语句用来响应由绑定目标(如 HTML 元素、组件或指令)触发的事件。 模板语句将在事件绑定一节看到,它出现在=号右侧的引号中,就像这样:(event)="statement"。
模板语句有副作用。 这是事件处理的关键。因为我们要根据用户的输入更新应用状态。
和模板表达式一样,模板语句使用的语言也像 JavaScript。 模板语句解析器和模板表达式解析器有所不同,特别之处在于它支持基本赋值 (=) 和表达式链 (;和,)。
然而,某些 JavaScript 语法仍然是不允许的:
new运算符
自增和自减运算符:++和--
操作并赋值,例如+=和-=
位操作符|和&
模板表达式运算符
语句上下文
和模板表达式中的上下文一样;
动画
Angular动画是基于标准的Web动画API(Web Animations API)构建的,它们在支持此API的浏览器中会用原生方式工作。
至于其它浏览器,就需要一个填充库(polyfill)了。你可以从这里获取web-animations.min.js,并把它加入你的页面中。
模块
Angular 应用是模块化的,并且 Angular 有自己的模块系统,它被称为 Angular 模块或 NgModules。
Angular 模块(无论是根模块还是特性模块)都是一个带有@NgModule装饰器的类;
NgModule是一个装饰器函数,它接收一个用来描述模块属性的元数据对象。其中最重要的属性是:
- declarations - 声明本模块中拥有的视图类。Angular 有三种视图类:组件、指令和管道。
- exports - declarations 的子集,可用于其它模块的组件模板。
- imports - 本模块声明的组件模板需要的类所在的其它模块。
- providers - 服务的创建者,并加入到全局服务列表中,可用于应用任何部分。
- bootstrap - 指定应用的主视图(称为根组件),它是所有其它视图的宿主。只有根模块才能设置bootstrap属性。
- 每个 Angular 应用至少有一个模块(根模块);
- 根模块不需要导出任何东西,因为其它组件不需要导入根模块。
- NgModules 和 JavaScript 模块的比较:
- NgModules是应用级别的模块,是以功能特性为划分依据;
- JavaScript中的模块是语言级别的模块,是以物理文件或者文件夹为划分依据;
组件
组件是一个带模板的指令;@Component装饰器实际上就是一个@Directive装饰器,只是扩展了一些面向模板的特性。
依赖注入
“依赖注入”是提供类的新实例的一种方式,还负责处理好类所需的全部依赖。大多数依赖都是服务。 Angular 使用依赖注入来提供新组件以及组件所需的服务。
Angular 通过查看构造函数的参数类型得知组件需要哪些服务。
当 Angular 创建组件时,会首先为组件所需的服务请求一个注入器 (injector)。
注入器是一个维护服务实例的容器,存放着以前创建的服务实例。 如果所请求的服务实例不在容器中,注入器就会新创建一个服务实例,并且添加到容器中,然后把这个服务返回给 Angular。 当所有请求的服务都被解析完并返回时,Angular 会把这些服务作为参数去调用组件的构造函数。 这就是依赖注入 。
如果注入器还没有HeroService,它怎么知道该如何创建一个呢?
简单点说,我们必须先用注入器(injector)为服务注册一个提供商(provider)。 提供商用来创建或返回服务实例,通常就是这个服务类本身的实例(相当于new 服务())。
需要记住的关于依赖注入的要点是:
- 依赖注入渗透在整个 Angular 框架中,被到处使用。
- 注入器 (injector) 是本机制的核心。
- 注入器负责维护一个容器,用于存放它创建过的服务实例。
- 注入器能使用提供商创建一个新的服务实例。
- 提供商是一个用于创建服务的配方。
- 把提供商注册到注入器。
编程思想
- 组件类应保持精简。组件本身不从服务器获得数据、不进行验证输入,也不直接往控制台写日志。 它们把这些任务委托给服务。
- 组件的任务就是提供用户体验,仅此而已。它介于视图(由模板渲染)和应用逻辑(通常包括模型的某些概念)之间。 设计良好的组件为数据绑定提供属性和方法,把其它琐事都委托给服务。