组件Component
组件是构成angular应用的核心,angular的有序运行依赖于组件的协同工作,组件之于angular应用就像是汽车和汽车零部件的意思。
概述
近几年的前端发展迅速,各种工程化的工具层出不穷:Browserify,Grunt,Gulp,Webpack等,有一些工具没等你学会,已经过时了。为了解决这个问题,W3C提出了Web Component标准。通过标准化的非侵入方式封装组件,每个组件都包含自己的HTML、CSS、JavaScript代码,并且不会对页面上的其他组件产生影响。
WebComponent标准包括如下四个重要的概念。
- ·自定义元素:这个特性允许开发者创建自定义的HTML标签和元素,每个元素都有属于自己的脚本和样式。
- ·模板:模板允许开发者使用<ng-template>标签预先定义一些内容,但并不随页面加载而渲染,而是可以在运行时使用JavaScript来初始化它。
- ·ShadowDOM:通过ShadowDOM可以在文档流中创建一些完全独立于其他元素的DOM子树,这个特性可以让开发者开发一个独立的组件,并且不会干扰其他DOM元素。
- ·HTML导入:一种在HTML文档中引入其他HTML文档的方法,用于导入WebComponent的机制。
关于Web Componnet标准实现最好的是Chrome浏览器。
示例程序在这里就不放出了,baidu上面有。
Google官方出品的Polymer框架则比较接近WebComponent的写法,它是面向未来框架的。同样是出自Google的Angular与WebComponent也有几分相似,虽然AngularJS1.x版本也支持模板和自定义标签,但Angular的组件化程度比AngularJS1.x更加彻底,而且与WebComponent标准更接近。
angular组件
Angular的组件是自描述的——可以和宿主元素交互,知道如何及何时渲染自己,可配置注入服务,有明确的Input&Output定义。所有的Angular组件都可以独立存在,这意味着任何Angular组件都可以作为根组件被引导,也可以被路由加载,或者在其他组件中使用。不过,一个组件不能单独被启动,它必须被包装到模块(NgModule)里,然后通过Bootstrap模块接口引导入口模块来启动Angular应用。
组件是Angular应用的最小逻辑单元,模块则是在组件之上的一层抽象。组件及其他部件如指令、管道、服务、路由等都可以被包含到一个模块中,外部通过引用这个模块来使用一系列封装好的功能。读者可以将Angular应用想像成一棵树,组件是这棵树的叶子,模块便是这棵树的树枝,每个Angular应用都必须要有一个根模块(树干),并且在根模块中必须通过Bootstrap指定根组件,用于启动Angular应用。
组件的基础构成
要创建一个组件需要遵循以下的步骤:
- 组件装饰器:组件需要用@Component()装饰器来进行修饰。@Componnet()装饰器是TypeScript的语法,会被转换成JS,转换后的@Component()变成一个decorator()的js方法。
- 元数据:组件需要元数据,这些元数据被声明到@Component装饰器里面。组件有很多的元数据,下图展示了两个最常用的,实际上还有其他的一些比如:styles,styleUrls,providers等等。
- 模板:组件需要(必须)关联一个模板,模板最终会被渲染到页面上,页面的这个DOM元素(模板)就是组件实例的宿主元素。组件可以和宿主元素进行交互,包括:①显示数据({{}语法})②双向数据绑定([(ngModel)]的语法方式)③监听宿主元素事件,以及调用组件方法((click):利用小括号的语法方式)。
- 组件类:组件实际上也是一个类,组件的逻辑都放到这个类里面。
组件与模块
模块是在组件之上的一层抽象,组件及指令、管道、服务、路由等都能通过模块来组织。下面来看看模块及组件是如何协作的。
模块的构成
模块是由@NgModule装饰器来修饰的,一个angular应用只能有一个根模块,其他的模块都叫做特性模块。根模块必须用bootstrap元数据来指定启动的根组件
,然后通过bootstrapModule()方法来启动应用。
这样,你的应用就能启动起来。
NgModule的元数据主要有以下:
- ·declarations:用于指定属于这个模块的视图类(ViewClass),即指定哪些部件组成了这个模块。Angular有组件、指令和管道三种视图类,这些视图类只能属于一个模块。注意,不要再次声明属于其他模块的类。
- ·exports:导出视图类。当该模块被引入外部模块中时,这个属性指定了外部模块可以使用该模块的哪些视图类,所以它的值类型跟declarations一致(组件、指令和管道)。
- ·imports:引入该模块依赖的其他模块或路由,引入后模块里的组件模板才能引用外部对应的组件、指令和管道。
- ·providers:指定模块依赖的服务,引入后该模块中的所有组件都可以使用这些服务。
组件交互
Angular应用由各种各样的组件组成,这些组件形成了一棵组件树,数据可以在组件树里完成交互,组件间的交互包括父子组件的交互和一些非父子关系组件的交互。组件交互就是组件通过一定的方式来访问其他组件的属性或方法,从而实现数据双向流动。
组件交互有很多种方式可供选择,非父子关系的组件可通过服务来实现数据交互通信。
angular提供了@Input和@Output来表示数据的流入和流出。被@Input修饰的变量属于输入属性,而被@Output修饰的则是输出属性,这里的“输入”“输出”是以当前组件的角度来说的。输出属性一般是以事件的形式,由EventEmitter发送出去的。
还可以在组件的元数据中使用inputs、outputs来设置输入和输出属性,所设置的值必须为字符串数组,元素的名称需要和成员变量相对应。以下代码和上述代码是等价的:
父组件给子组件传递数据
父组件的数据通过子组件的输入属性(@Input修饰的属性)流入子组件,在子组件中完成接收或拦截,以此实现了数据由上而下的传递。
父组件给子组件传递的数据也可以被子组件拦截并进行相应的处理,下面介绍两种方式,一种是setter拦截输入属性另一种是使用ngOnChanges钩子函数监听变化。
①使用setter拦截输入属性:getter和setter通常是配合使用的,用来对属性进行相关约束,setter可以对属性进行再封装处理,以避免错误的外部调用影响到内部的状态改变。如下图:
上图中的set contactObj和get contactObj是对字段_contact的一层封装,有了这层封装,就可以拦截传入的数据,避免错误的状态流入,对外,使用set contactObj这个属性来对数据进行接收,使用get contactObj这个属性来读取值。
②使用ngOnChanges来监听数据的变化:ngOnChanges用于及时响应Angular在属性绑定中发生的数据变化,该方法接收一个对象参数,包含当前值和变化前的值。ngOnChanges在ngOnInit之前,或者当数据绑定的输入属性的值发生变化时会触发。ngOnChanges是组件的生命周期钩子之一。直白点说ngOnChanges的作用就是监视@Input修饰的属性的变化的。这个钩子函数传入的参数的类型是SimpleChanges,它是angular的一个基础类。用于处理数据的前后变化,包含了两个重要的成员,分别是previousValue和currentValue,其中,前者用于获取变化前的数据,后者用于获取当前数据,如下图。
注意:ngOnChanges这个钩子函数使用在子组件中监测数据变化的(这段文字显得有一些愚蠢,但是已经写出来了,删除了怪可惜的)。
子组件向父组件传递数据
使用事件传递是子组件向父组件传递数据最常用的方式。子组件需要实例化一个用来订阅和触发自定义事件的EventEmitter类,这个实例对象是一个由装饰器@Output修饰的输出属性,当有用户操作行为发生时该事件会被触发,父组件则通过事件绑定的方式来订阅来自子组件触发的事件,即子组件触发的具体事件会被其父组件订阅到。下面这个例子展示了父组件和子组件通过事件来实现的数据传递:
父组件:
子组件:
其他组件交互方式
父组件通过局部变量获取子组件的引用
如上我们在模板中使用“#”号定义了一个collect的变量,这个变量叫做模板局部变量。这个变量指向了子组件,那么在这个模板中的其他地方都可以使用这个变量引用来获取子组件公共成员和方法的权限。
父组件通过@ViewChild获取子组件的引用
模板局部变量只能在模板中使用,不能在组件类中使用,使用@ViewChild可以解决这个问题。@ViewChild()装饰器方法可以传入一个类名,也可以传入一个字符串,实现的功能都是一样的。但要注意传入字符串时需要在模板中定义好局部变量名,这个字符串必须是这个定义好的模板局部变量名。
组件内容嵌入
内容嵌入是用angular的<ng-content></ng-content>指令来完成的,这个指令有一个select属性,作用和CSS的选择器的作用是一样的,就是选择要映射的元素,比如select=‘header’表示header元素可以映射到ng-content上,select='.app-header'表示元素的类名为app-header的元素可以映射到ng-content上。
组件生命周期
angular给组件设定了一些生命周期钩子函数,这些钩子函数有如下一些:
- ngOnChanges :上文介绍组件交互时提到过这个钩子,它是用来响应组件输入值发生变化时触发的事件。该方法接收一个SimpleChanges对象,包含当前值和变化前的值。该方法在ngOnInit之前,或者当数据绑定输入属性的值发生变化时会被触发。在Angular中,只要在组件里定义了ngOnChanges方法,当输入数据发生变化时该方法就会被自动调用。需要注意的是,ngOnChanges当且仅当组件输入数据变化时被调用,这里的“输入数据”指的是通过@Input装饰器显式指定的那些变量。
- ngOnInit :ngOnInit钩子用于数据绑定输入属性之后初始化组件。该钩子方法会在第一次ngOnChanges执行后被调用。使用ngOnInit有以下两个重要原因:组件构造后不久就要进行复杂的初始化。需要在输入属性设置完成之后再构建组件。在组件中,经常会使用ngOnInit获取数据。为什么不在组件构造函数中获取数据呢?首先,构造函数做的事,例如成员变量初始化,应该尽可能简单,这对于有经验的开发人员来说,已经是一种共识。另外,这对于Angular自动化测试的一些场景也有非常重要的作用,把与业务相关的初始化代码放到ngOnInit里可以很容易进行Hook操作,而构造函数不能被显式调用,因此无法进行Hook操作。
- ngDoCheck :ngDoCheck用于变化监测,该钩子方法会在每次变化监测发生时被调用。在每一个变化监测周期内,不管数据值是否发生了变化,ngDoCheck都会被调用。但要慎用这个钩子方法,例如鼠标移动时会触发mousemove事件,此时变化监测会被频繁触发,随之ngDoCheck也会被频繁调用。因此,在ngDoCheck方法中不能写一些复杂的代码,否则性能就会受到影响。在绝大多数情况下,ngDoCheck和ngOnChanges不应该一起使用。ngOnChanges能做的事情,ngDoCheck也能做,而且ngDoCheck监测的粒度更小,可以完成更灵活的变化监测逻辑。
- ngAfterContentInit :在组件中使用<ng-content>将外部内容嵌入到组件视图后就会调用ngAfterCon-tentInit,它在第一次ngDoCheck执行后被调用,且只执行一次。
- ngAfterContentChecked:在组件使用了<ng-content>自定义内容的情况下,Angular在这些外部内容嵌入到组件视图后,或者每次变化监测时都会调用ngAfterContentChecked。
- ngAfterViewInit:ngAfterViewInit会在Angular创建了组件的视图及其子视图之后被调用。
- ngAfterViewChecked:ngAfterViewChecked在Angular创建了组件的视图及其子组件视图之后被调用一次,并且在每次子组件变化监测时也会被调用。
- ngOnDestroy:ngOnDestroy在销毁指令/组件之前被触发。那些不会被垃圾回收器自动回收的资源(比如已订阅的观察者事件、绑定过的DOM事件、通过setTimeout或setInterval设置过的计时器,等等)都应当在ngOnDestroy中手动销毁,从而避免发生内存泄漏等问题。
变化监测
Angular提供了数据绑定的功能(在“模板”章节中会详细介绍)。所谓数据绑定就是将组件类的数据和页面的DOM元素关联起来。当数据发生变化时,Angular能够监测到这些变化,并对其所绑定的DOM元素进行相应的更新,反之亦然。异步事件的发生会导致组件中数据的变化,但Angular并不捕获对象的变动,它采用的是在适当的时机来检验对象的值是否被改动。这个时机是由NgZone这个服务掌控的,它获取到了整个应用的执行上下文,能够对相关的异步事件发生、完成或者异常等进行捕获,然后驱动Angular的变化监测机制执行。
在应用程序中,大致有三种引起数据变化的应用场景。第一种是用户的行为操作,即页面操作所引发的用户事件,如click、change、hover等,以此对用户的操作做出响应;第二种是前后端的数据交互,比如从后端服务拉取页面所需要的接口数据,如XML-HttpRequest/WebSocket等,这是一个异步的过程,获取到一份较之前可能有变化的数据;第三种是各类定时任务,即在某个延时后再来响应对应的操作,从而对页面数据做出改变,如setTimeout、setInterval、requestAnimationFrame等,它们都是延后到某个时间才触发相应的操作的。这些能引起数据变化的异步处理总结如下图所示。
上面三种情况的共同特征就是异步处理。当异步事件发生导致数据发生变化时,会通知angular进行变化监测。在视图层做出相应改变等。但实际上angular并没有实现捕获异步事件功能,而是通过Zone.js来进行的。关于Zone.js这个工具以后有时间再将,这里就不展开了。简而言之就是它重写了浏览器对于异步事件API,所以他就能感知到异步事件导致的数据变化,默认情况下angular的应用是运行在某一个子Zone下面的,这和Zone就叫做angular zone。这意味着Angular应用中触发的所有异步事件都可被Angular Zone感知(可在异步事件执行后进行一些必要操作,如触发变化监测等),而Root Zone或其他子Zone是感知不到Angular的异步事件执行的(即不会触发变化监测)。了解这点很重要,后文会述及。为了管理Root Zone和子Zone,Angular为Zone.js又封装了一层,称为NgZone。NgZone本身是一个服务(关于Angular服务后面会讲到)。除管理各个Zone对象外,NgZone还封装了一些支持Angular运行的友好事件,当有异步任务发生、完成或者抛出异常时,都可以监听对应的事件并进行捕获处理。
关于变化监测的内容现就讲这么多,主要我也对这块的内容没怎么理解,平时也不会用到,等后面有时间了专门开一个帖子说吧。