1 概述
1.1 组件化标准 - WebComponent
1. W3C为统一组件化标准方式,提出Web Component的标准。
2. 每个组件包含自己的html、css、js代码
3. Web Component标准包括以下四个重要的概念
- 自定义元素:可以创建自定义HTML标记和元素
- 模版:模版允许开发人员使用<template>标签去预定义一些内容,但并不加载至页面,而是使用Js代码去初始化它
- Shadow DOM:可以创建完全独立与其他元素的DOM子树。
- HTML导入:一种在HTML文档中引入其他HTML文档的方法,用于导入Web Component机制:<link rel="import" href="example.html" />
2 Angular组件
2.1 Angular组件基础
// customize.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'customize-label',
moduleId: module.id,
template: `innerhtml`,
templateUrl: 'htmlUrl',
styles: [`css style`], //样式只能对该组件内节点有效。
styleUrls: ['cssUrl'], //若styles和styleUrls都定义了,styles内的样式先被解析,然后解析styleUrls,覆盖styles。模版内联样式优先级最高。
})
export class CustomizeComponent { }
数据绑定
//xxx.html
<input type="text" value="{{name}}" [(ngModule)]="name">
<input type="text" [attr.value]="name" (click)="inputClick($event)">
2.2 组件与模块
- 模块将组件、指令、管道、服务、路由等组织起来
- 一个应用可以有多个模块,但有且只有一个根模块(AppModule)。其他模块叫做特性模块
- 根模块是启动应用的入口,根模块必须通过bootstrap员数据来指定应用的根组件。
- NgModule的主要元数据:
- declarations:数据这个模块的视图类。Angular有组件、管道、指令三种视图类。且这些视图类只能属于一个模块。
- exports:导出视图类。其他模块引用这个模块后,就可以使用这里导出的视图类。
- imports:引入该模块依赖的其他模块或路由。
- providers:指定模块依赖的服务。引入后该模块的所有组件都可以使用这些服务。
3 组件交互
组件交互也就是父子关系组件或非父子关系组件的数据双向流动。
非父子关系组件:可通过服务来实现数据交互。
3.1 组件的输入输出属性
方式1
@Input 输入
@Output 输出
//child.component.ts
export class ChildComponent {
@Input() receive: any = {};
@Output() send = new EventEmitter<number>();
}
//parend.component.html
<child-item [receive]="value" (send)="senFunc($event)"></child-item>
//这里的receive是子组件的属性,将父组件的value赋值给子组件。
//这里因为AppModule的declarations引入了子组件,所以父组件可以直接使用子组件(使用child-item标签、给receive属性赋值)。
方式2
inputs
outputs
@Component({
//...
inputs: ['receive'],
outputs: ['send']
})
export class ChildComponent {
receive: any = {};
send = new EventEmitter<number>();
}
3.2 拦截输入属性
子组件接收父组件的数据,可以拦截属性的数据并进行相应的处理。
拦截有两种方式:
1. setter拦截输入属性
2. ngOnChanges监听数据变化
3.2.1 setter
setter与getter通常配套使用。类似于C#中的get、set。
_contact: object = {};
@Input()
set contactObj(contact:object){
this._contact.name = contact;
}
get contactObj(){
return this._contact;
}
//这里内部就使用set get的 contactObj
3.2.2 ngOnChanges
- ngOnChanges是组件的声明周期钩子之一
- ngOnChanges监听数据绑定中发生的数据变化,该方法接收一个对象参数,包含当前值和变化前的值。
- 在ngOnInit之前,或者当数据绑定的输入属性的值发生变化时会触发。
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'change-log',
template:`
...
`,
})
export class ChangeLogComponent {
@Input() contact:any = {};
changes:string[] = [];
ngOnChanges(changes:{[propKey: string]: SimpleChanges}){
let log: string[] = [];
for(let propName in changes){
let changedProp = changes[propName],
from = JSON.stringify(changedProp.previousValue),
to = JSON.stringify(changedProp.currentValue);
log.push(`${propName} changed from ${from} to ${to}`);
}
this.changes.push(log.join(','));
}
}
3.3 子组件向父组件传递数据
- 使用事件传递是子组件向父组件传递数据最常用的方式。
- 子组件实例化一个用来订阅和触发自定义事件的EventEmitter类,并由@Output装饰器修饰。当用户操作时触发事件。父组件通过事件绑定的方式来订阅子组件触发的事件。
//父组件CollectionComponent
import { Component } from '@angular/core';
@Component({
selector: 'collection',
template: `
<contact-collect [contact]="detail" (onCollect)="collectTheContact($event)"></contact-collect>
`
})
export class CollectionComponent {
detail: any = {};
collectTheContact(){
this.detail.collection == 0 ? this.detail.collection = 1 : this.detail.collection = 0;
}
}
//子组件ContactCollectComponent
import { Component, EventEmitter, Input, Output} from '@angualr/core';
@Component({
selector: 'contact-collect',
template: `
<i [ngClass]="{collected:contact.collection}" (click)="collectTheContact()">收藏</i>
`
})
export class ContactCollectComponent {
@Input() contact: any = {};
@Output() onCollect = new EventEmitter<boolean>();
collectTheContact(){
this.onCollect.emit();
}
}
//当子组件点击触发click事件,调用collectTheContact方法,输出对象onCollect执行emit()方法,父组件订阅,然后在父组件中触发了onCollect方法,以此实现子组件数据向父组件传输。
3.4 其他组件交互方式
- 父组件通过局部变量获取子组件引用
- 父组件使用@ViewChild获取子组件的引用
3.4.1 通过局部变量实现数据交互
前面父子组件间的数据交互都是通过输入、输出属性绑定的方法来实现的。但父组件仅仅是将数据流向下级组件,但没有读取子组件成员变量和方法的权限。
在Angular中的”模版局部变量”,可以帮助我们获取子组件的实例引用。通过创建模版局部变量的方式,在父组件的模版中为子组件创建一个局部变量,从而获取子组件公共成员变量与方法。
模版局部变量的作用域范围仅存在于定义该模版局部变量的子组件。
import { Component } from '@angular/core';
@Component({
selector: 'collection',
template: `
<contact-collect (click)="collect.collectTheContact()" #collect></contact-collect>
//#collect 在子组件标签上绑定一个局部变量,以此来获取子组件类的实力对象。
//这里collect对象指向的就是子组件对象,所以可以调用子组件的collectTheContact()方法。
//普通标签也可以用此方法获取DOM对象。
`
})
export class CollectionComponent {}
//子组件ContactCollectComponent
import { Component } from '@angular/core';
@Component({
selector: 'contact-collect',
template: `
<i [ngClass]="{collect:detail.collection}">收藏</i>
`
})
export class ContactCollectComponent {
detail: any = {}; //默认public
collectTheContact(){ //默认public
this.detail.collection == 0 ? this.detail.collection = 1 : this.detail.collection = 0;
}
}
缺点
使用模版局部变量实现父组件调用子组件成员变量或方法实现数据交互的方法,看似好用,但有局限性:只能在模版中使用,而不能直接在父组件类中使用。
3.4.2 使用@ViewChild实现数据交互
上面提到了使用模版局部变量的缺点,所以需要有一个能弥补这个缺点的另一种数据交互方式,不然不够灵活。
当父组件需要获取到子组件中变量或者方法的读写权限时,可以通过@ViewChild注入的方式来实现。
组件中元数据ViewChild的作用是声明对子组件元素的实例引用,它提供了一个参数来选择将要引用的组件元素,这个参数可以是一个类的实例,也可以是一个字符串,他们实现的功能是一样的,知识表现形式不同。
- 参数为类实例,表示父组件将绑定一个指令或者子组件实例
- 参数为字符串类型,表示将祈祷选择器的作用,即相当于在父组件中绑定了一个模版局部变量,获取到子组件的一份实例对象的引用。
参数为字符串类型的方式,与之前的模版局部变量方式是一样的。
参数为类实例的方式:
//父组件
import { Component, AfterViewInit, ViewChild } from '@angular/core';
@Component({
selector: 'collection',
template: `
<contact-collect (click)="collectTheContact()"></contact-collect>
`
})
export class CollectionComponent {
@ViewChild(ContactCollectComponent) contactCollect: ContactCollectComponent;
ngAfterViewInit(){
//...
}
collectTheContact(){
this.contactCollect.collectTheContact();
}
}
4 组件内容嵌入
备注一下
这里之前一点没学过,备注一下。
内容嵌入(ng-content)是组件的一个高级功能特性,使用组建的内容嵌入特性能很好地扩充组件的功能,方便代码的复用。
内容嵌入通常用来创建可复用的组件,最常见的如模态框、导航栏。使用内容嵌入,是的这些组件具有一致的样式,但内容有可以自定义。
//NgContentExampleComponent
import { Component } from '@angular/core';
@Component({
selector: 'example-content',
template: `
<div>
<h4>ng-content 示例</h4>
<div style="background-color: gray;padding: 5px;margin: 2px;">
<!-- 下面这一行会替换宿主组件的example-content标签内的header元素内容 -->
<ng-content select="header"></ng-content>
</div>
</div>
`
})
//使用根组件作为嵌入组件的宿主
import { Component } from '@angular/core';
@Component({
selector: 'app'
template: `
<example-content>
<header>Header content</header>
<!-- 将自定义的内容放到example-content标签之间 -->
</example-content>
`
})
export class AppComponent {}
//渲染后结果
<app>
<example-content>
<div>
<h4>ng-content 示例</h4>
<div style="background-color: gray;padding: 5px;margin: 2px;">
<header>Header content</header>
</div>
</div>
</example-content>
</div>
</app>
分析一下
1. 宿主模版中的元素内的被选择的元素,替换掉了嵌入的模版中标签。
2. 嵌入的模版中的元素中的select属性是一个选择器。选择到的元素将会替换这个嵌入模版。
3. 选择器语法与css相同。
同时使用多个嵌入内容
略,就是嵌入组件内多了几个标签,然后每个标签选择器不同。
疑问
这里可复用组件需要用到两个组件:1插座组件2可复用组件。
如果将可复用组件写成通用组件,效果也是一样,而且也是需要两个组件。为什么要选择可复用组件?
5 组件生命周期
组件的声明周期就是从组件的创建、渲染等直到从DOM中移除等一系列的钩子。组件可以实现多个生命周期钩子(接口),这些钩子包含在@angular/core库中。
声明周期钩子:(Angular会以以下顺序依次调用钩子)
1. ngOnChanges
2. ngOnInit
3. ngDoCheck
4. ngAfterContentInit
5. ngAfterContentChecked
6. ngAfterViewInit
7. ngAfterViewChecked
8. ngOnDestory
5.1 ngOnChanges
响应组件输入值发生变化时触发的事件。该方法在ngOnInit之前,或当数据绑定输入属性值变化时触发。
5.2 ngOnInit
该钩子方法会在第一次ngOnChanges之后被调用。
使用ngOnInit有以下两个重要原因:
1. 组件构造后不久就要进行复杂的初始化
2. 需要在输入属性设置完成之后才构建组件。
通常在ngOnInit中获取数据,而不再构造函数中,为什么?
1. 构造函数做的事应尽量简单,如初始化等。
2. 有助于Angular自动化测试。
5.3 ngDoCheck
用于变化检测,该钩子方法会在每次变化检测发生时被调用。
每一个变化检测周期内,不管数据值是否发生了变化,ngDoCheck都会被调用。但要慎用,例如鼠标移动触发mousemove事件,此钩子会被频繁触发。另外该钩子内不能写一些复杂的代码,否则影响性能。
绝大多数下,ngDoCheck和ngOnChanges不应该一起使用。ngOnChanges能做的事情,ngDoCheck也能做到,而且监测的颗粒度更小,可以完成更灵活的变化监测逻辑。
5.4 ngAfterContentInit
在组件中使用将外部内容嵌入到组件视图后就会调用ngAfterContentInit,它在第一次ngDoCheck执行后调用,且只执行一次。
5.5 ngAfterContentChecked
在组件使用了自定义内容的情况下,Angular在这些外部内容嵌入到组件视图后,或者每次变化监测的时候都会调用ngAfterContentChecked。
5.6 ngAfterViewInit
ngAfterViewInit会在angular创建了组件的视图及其子视图之后被调用。
5.7 ngAfterViewChecked
ngAfterViewChecked在Angular创建了组件的视图及其子组件视图之后被调用一次,并且在每次子组件变化监测时也会被调用。
5.8 ngOnDestroy
ngOnDestroy在销毁指令/组件之前触发。那些不会被垃圾回收器自动回收的资源(比如已订阅的观察者事件、绑定过的DOM事件、通过setTimeout活setInterval设置过的计时器等)都应当在ngOnDestroy中手动销毁,从而避免内存泄漏等问题。
6 变化监测
先消化消化再学
7 扩展阅读
7.1 组件元数据一览表
名称 | 类型 | 作用 |
---|---|---|
selector | string | 自定义组件的标签,用于匹配数据 |
inputs | string[] | 指定组件的输入属性 |
outputs | string[] | 指定组件的输出属性 |
host | {[key: string]: string;} | 指定指令/组件的事件、动作和属性等 |
providers | any[] | 指定该组件及其所有子组件(含ContentChildren)可用的服务(依赖注入) |
exportAs | string | 给指令分配一个变量,使得可以在模版中调用 |
moduleId | string | 包含该组件模块的id,它被用于解析模版和样式的相对路径(webpack暂时不可用) |
queries | {[key: string]: any;} | 设置需要被注入到组件的查询 |
viewProviders | any[] | 指定该组件及其所有子组件(不含ContentChildren)可用的服务 |
changeDetection | ChangeDesectionStrategy | 指定使用的变化监测策略 |
templateUrl | string | 指定组件模版所在的路径 |
template | string | 指定组件的内联模版 |
styleUrls | string[] | 指定组件引用的外部样式文件 |
styles | string[] | 指定组件使用的内联样式 |
animations | AnimationEntryMetadata[] | 设置Angular动画 |
encapsulation | ViewEncapsulation | 设置组件的视图包装选项 |
interpolation | [string,string] | 设置自定义插值标记,默认是双大括号{{ }} |
7.2 元数据说明
暂时略
7.3 深入理解Zones
可以将Zones简单地理解为一个异步事件拦截器,也就是说Zones能够hook到异步任务的执行上下文,并在一些关键点上重写相应的钩子方法,以此来完成某些任务。
版权说明
本博客内所有揭秘angular2学习的文章都是学习《揭秘Angular2》---广发证券互联网金融技术团队著作这本书的学习总结及部分内容摘抄,若有侵权请与本人联系删除侵权内容。
qq:451354
邮箱:451354@qq.com