1. 组件概述

组件之于ng,正如汽车部件之于汽车。一个模块包含多个组件,像是汽车的一个系统比如动力系统包含多个零件。一个模块的组件不能调用另外一个模块的组件。

Angular应用像是一棵树,组件是叶子,模块是枝干,根模块是树干。

2. 组件创建步骤

创建一个组件包括了三个步骤:
1.从@angular/core中引入Component装饰器;
2.建立一个普通类,并用@Component修饰它;
3.在@Component中,设置了selector自定义标签和template模板。

//contactItem.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'contact-item',
    template: `
        <div>
            <p>张三</p>
        </div>
    `
})

export class ContactItemComponent{}

3. 组件元数据

主要包括了selector、template、styles。

selector:它是组件在HTML代码中标签,是组件的命名标记。它的命名采用“烤肉串式”,如“contact-app”;

templatetemplateUrltemplate提供了内联文档,templateUrl则指定外部文档URL地址,如templateUrl: 'app/components/contact-item.html'

stylesstyleUrlsstyles同理,styles指定内联样式,styleUrls指定外联样式表文件,如styleUrls: ['app/list/item.component.css']。styleUrl的等级高。

4. 组件与模板的交互

模板即组件的宿主元素,它与组件的交互形式有三种:

  • 显示数据
  • 双向数据绑定
  • 监听宿主元素事件以及调用组件方法

显示数据:{{}}显示组件数据,即把数据从内存中展示到页面上去。

import { Component } from '@angular/core';

@Component({
  selector: 'contact-item',
  template: `
    <div>
      <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)方式调用,如<h3>添加联系人<i (click)="addContact()"></i></h3>

5. 组件之间的交互

包含了父子组件交互和非父子关系组件交互。交互的对象为组件的属性或者方法,从而实现数据双向流动。非父子关系组件的交互依靠服务来实现交互通信。

5.1 组件输入输出属性

Angular 2中@Input@Output分别实现了组建数据的输入输出。@Input为其他组件输入到本组件的数据,@Output为本组件输出到其他组件的数据。输入输出的数据除了可以是属性,也可能是一个动作,如点击事件。

//item.component.ts
export class ListItemComponent implements OnInit{
  @Input() contact:any = {};
  @Output() routerNavigate = new EventEmitter<number>();
}
//...

<!-- list.component.html -->
<li *ngFor="let contact of contacts">
  <list-item [contact]="contact (routerNavigate)="routerNavigate($event)"></list-item>
 </li>

note:[ ]是模板到组件内存,而( )是内存到模板。

5.2 父组件向子组件传递数据

子组件通过@Input接受或拦截来自父组件的数据。数据流动的路径为:父组件–>父组件模板–>嵌套的子组件selector–>子组件模板。

//父组件list.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'list',
  template: `
    <ul 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}}</label>
      <span class='contact-tel'>{{contact.telNum}}</span>
    </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}}</label>
      <span class='contact-tel'>{{contactObj.telNum}}</span>
    </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对@Inputcontact进行处理后,通过get方式返回。contactObjListItemComponent的一个属性。

ngOnchanges监听数据变化:用于及时响应NG在属性绑定中发生的数据变化,该方法接收一个对象参数,包含了当前值和变化前的值。该对象参数为SimpleChanges,当前值和变化前的值分别为currentValuepreviousValue。一个例子,编辑联系人后,日志输出变化前后的值:

//父组件,detail.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'detail',
  template: `
    <a 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:
    <h4>Change log:</h4>
    <ul>
      <li *ngFor="let change of changes">{{change}}</li>
    </ul>
  `
})

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: `
    <contact-collect [contact]="detail" (onCollect)="collectTheContact($event)"></contact-collect>
  `
})

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: `
    <i [ngClass]="{collected: contact.collection}" (click)="collectTheContact()">收藏</i>
  `
})

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: `
    <contact-collect (onCollect)="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都会被调用。ngOnChangesngDoCheck不同时使用,ngDoCheck监测的粒度更小。