组件之于ng,正如汽车部件之于汽车。一个模块包含多个组件,像是汽车的一个系统比如动力系统包含多个零件。一个模块的组件不能调用另外一个模块的组件。
Angular应用像是一棵树,组件是叶子,模块是枝干,根模块是树干。
创建一个组件包括了三个步骤:
1.从@angular/core中引入Component装饰器;
2.建立一个普通类,并用@Component修饰它;
3.在@Component中,设置了selector自定义标签和template模板。
//contactItem.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'contact-item',
template: `
<p>张三p>
div>
`
})
export class ContactItemComponent{}
3. 组件元数据
主要包括了selector、template、styles。
selector:它是组件在HTML代码中标签,是组件的命名标记。它的命名采用“烤肉串式”,如“contact-app”;
template
和templateUrl
:template
提供了内联文档,templateUrl
则指定外部文档URL地址,如templateUrl: 'app/components/contact-item.html'
;
styles
和styleUrls
:styles
同理,styles
指定内联样式,styleUrls
指定外联样式表文件,如styleUrls: ['app/list/item.component.css']
。styleUrl的等级高。
4. 组件与模板的交互
模板即组件的宿主元素,它与组件的交互形式有三种:
- 显示数据
- 双向数据绑定
- 监听宿主元素事件以及调用组件方法
显示数据:用{{}}
显示组件数据,即把数据从内存中展示到页面上去。
import { Component } from '@angular/core';
@Component({
selector: 'contact-item',
template: `
<p>{{name}}p>
div>
`
})
export class ContactItemComponent{
name: string = '张三';
}
双向数据绑定:[(ngModel)] = "property"
实现了该交互。
import { Component } from '@angular/core';
@Component({
selector: 'contact-item',
template: `
<div>
<input type='text' value="{{name}}" [(ngModel)]="name"/>
<p>{{name}}p>
div>
`
})
export class ContactItemComponent{
name: string = '张三';
}
监听宿主元素事件以及调用组件方法:(eventName)方式调用,如添加联系人
5. 组件之间的交互
包含了父子组件交互和非父子关系组件交互。交互的对象为组件的属性或者方法,从而实现数据双向流动。非父子关系组件的交互依靠服务来实现交互通信。
5.1 组件输入输出属性
Angular 2中@Input
和@Output
分别实现了组建数据的输入输出。@Input
为其他组件输入到本组件的数据,@Output
为本组件输出到其他组件的数据。输入输出的数据除了可以是属性,也可能是一个动作,如点击事件。
//item.component.ts
export class ListItemComponent implements OnInit{
@Input() contact:any = {};
@Output() routerNavigate = new EventEmitter();
}
//...
"let contact of contacts">
<list-item [contact]="contact (routerNavigate)="routerNavigate($event)">
note:[ ]是模板到组件内存,而( )是内存到模板。
5.2 父组件向子组件传递数据
子组件通过@Input
接受或拦截来自父组件的数据。数据流动的路径为:父组件–>父组件模板–>嵌套的子组件selector–>子组件模板。
//父组件list.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'list',
template: `
class='list'>
<li *ngFor="let contact of contacts">
<list-item [contact]="contact">list-item>
li>
ul>
`
})
export class ListComponent implements OnInit{
//...
this.contacts = data;
}
//子组件item.component.ts
@Component({
selector: 'list-item',
template: `
<div class='contact-info'>
<label class='contact-name'>{{contact.name}}
class='contact-tel'>{{contact.telNum}}
div>
`
})
export class ListItemComponent implements OnInit{
@Input() contact:any = {};
}
拦截输入属性有两种方式:
- setter拦截输入属性
- ngOnchanges监听数据变化
setter拦截输入属性:getter和setter配套使用,提供了一套属性读写的封装。父组件到子组件数据流动中子组件可以改写为:
@Component({
selector: 'list-item',
template: `
<div class='contact-info'>
<label class='contact-name'>{{contactObj.name}}
class='contact-tel'>{{contactObj.telNum}}
div>
`
})
export class ListItemComponent implements OnInit{
_contact: object = {};
@Input()
set contactObj(contace: object){
this._contact.name = (contact.name && contact.name.trim() || 'no name set');
this._contact.telNum = contact.telNum || '000-000';
}
get contactObj(){
return this._contact;
}
}
其中,set对@Input
的contact
进行处理后,通过get
方式返回。contactObj
是ListItemComponent
的一个属性。
ngOnchanges监听数据变化:用于及时响应NG在属性绑定中发生的数据变化,该方法接收一个对象参数,包含了当前值和变化前的值。该对象参数为SimpleChanges
,当前值和变化前的值分别为currentValue
和previousValue
。一个例子,编辑联系人后,日志输出变化前后的值:
//父组件,detail.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'detail',
template: `
class='edit' (click)="editContact()">编辑a>
<change-log [contact]="detail">change-log>
`
})
export class DetailComponent implements OnInit{
detail:any = {};
//完成联系人编辑修改
editContact(){
//...
this.detail = data;
}
}
//子组件,changelog.component.ts
import { Component,Input,Onchanges,SimpleChanges } from '@angular/core';
@Component({
selector: 'change-log',
template:
Change log:
- "let change of changes">{{change}}
`
})
export class ChangeLogComponent implements Onchanges{
@Input() contact: any={};
changes: string[] = [];
ngOnChange(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(', ');
}
}
5.3 子组件向父组件传递数据
使用事件传递是子组件向父组件传递数据最常用的方式。子组件需要实例化一个用来订阅和触发自定义事件的EventEmitter
类,这个实例化对象是一个由装饰器@Output
修饰的输出属性。下面是一个例子:
//父组件collection.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'collection',
template: `
"detail" (onCollect)="collectTheContact($event)">
`
})
export class CollectionComponent implements OnInit{
detail: any = {};
collectTheContact(){
this.detail.collection == 0 ? this.detail.collection = 1 : this.detail.collection = 0;
}
}
//子组件contactCollect.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'contact-collect',
template: `
"{collected: contact.collection}" (click)="collectTheContact()">收藏
`
})
export class CollectionComponent implements OnInit{
@Input() contact: any = {};
@Output() onCollect = new EventEmitter<boolean>();
collectTheContact(){
this.onCollect.emit();
}
}
子组件实例化一个boolean
类型的EventEmitter
,当click子组件模板时候,向父组件发送事件,接着父组件同名(与EventEmitter
同名)的事件被触发。
5.4 其他组件交互方式
父子组件的数据传递方式还有两种:
- 通过局部变量实现数据交互
- 使用@ViewChild实现数据交互
通过局部变量实现数据交互:解决了父组件无法调用子组件相关成员变量和方法的问题。它的实现是在父组件模板中的子组件标签上绑定一个以#
号标记的变量符号。该局部变量仅在父组件模板中使用,而不能再父组件类中直接使用。一个例子如下:
import { Component } from '@angular/core';
@Component({
selector: 'collection',
template: `
"collectTheContact($event)" #contact>contact-collect>
`
})
export class CollectionComponent{ }
6. 组件生命周期
组件的生命周期,从组件创建、渲染,到数据变通时间的触发,再到组件从DOM移除,NG提供一系列钩子。这些钩子可以让开发者在这是些事件触发时,执行相应的回调函数。
每个生命周期钩子(接口)都对应着一个名为“ng+接口名”的方法。
NG的生命周期有以下8种,按照先后顺序依次调用钩子方法:
- ngOnChanges
- ngOnInit
- ngDoCheck
- ngAfterContentInit
- ngAfterContentChecked
- ngAfterViewInit
- ngAfterViewChecked
- ngOnDestroy
ngOnChanges
:响应组件输入值发生变化时触发的事件,这里的输入值指的是通过@Input
装饰器显式指定的变量。该方法接收一个SimpleChanges
对象,包含当前值和变化前的值。
ngOnInit
:用于数据绑定输入属性之后初始化组件。通常用来获取数据,并且很容易进行Hook操作。
ngDoCheck
:用于变化检测,该钩子方法会在每次变化监测发生时被调用。在一个变化检测周期内,无论是否发生变化,ngDoCheck
都会被调用。ngOnChanges
和ngDoCheck
不同时使用,ngDoCheck
监测的粒度更小。
//……