什么是组件?

组件就是"公共的页面",组件可以被多个页面复用,他抽离了页面逻辑,降低了代码的耦合性,提升了代码的可读性。 一个组件作为一个独立的个体只专注于做好一件事,且有明确的输入以及输出。

组件基础

创建组件

ng g c first

在webstorm的终端中执行如上命令后即可生成first组件。 一个基础的组件共包含三个文件,HTML、TS、SCSS/CSS文件。 image.png 如果我们想改变组件的存放文件,可使用如下形式

ng g c 路径/first

执行后会自动在APP文件夹下生成路径对应的文件夹并在其下新建组件

代码解析

打开FirstComponent类文件(first.component.ts),代码如下

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

@Component({
  selector: 'app-first',
  templateUrl: './first.component.html',
  styleUrls: ['./first.component.scss']
})
export class FirstComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

如上代码中:

  • @Component() @Component是一个装饰器,类似于JAVA中的注解,在类上添加此装饰器以代表此是一个组件。
  • selector selector是组件的CSS选择器也是组件对外的标签名,如上代码中通过selector指定了此组件的CSS选择器及标签名为app-selector。
  • templateUrl templateUrl制定了组件模板文件(HTML)的位置(相对路径)。
  • styleUrls styleUrls制定了组件私有样式表文件(SCSS/CSS)的位置(相对路径)。
  • ngOnInit() angular的一个生命周期钩子,ngOnInit是在组件第一次显示数据绑定和设置组件的输入属性之后自动执行的,可以用来初始化组件。

组件生命周期

指令和组件的实例有一个生命周期:新建、更新和销毁,当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法。 image.png

组件通讯

angular是一个组件化、模块化的框架,一个angular组件页面会引用到多个别的组件。 image.png 一般来说,组件间的关系如上图所示中存在下面几种:

  • 父子关系
  • 非父子关系(兄弟关系、无直接关系) 针对于不同的关系,常见的通讯方式如下:
  • 父子关系:@Input/@Output/模板变量/@ViewChild
  • 非父子关系(兄弟关系、无直接关系):Service/localStorage

父子组件通讯

在父子组件间通讯一般使用@Input和@Ouput实现。

@Input:用于在组件中规定自身需要的输入属性,可用于属性和方法上。

@Ouput:用于在组件中规定自身提供的输出属性,可用于属性和方法上。

@Input()

@Input可以用在属性上,也可以用在方法上。虽然都是用来定义组件需要的输入属性,但两者之间依旧存在一定差距。其可以在使用时显式指定输入属性的名称,如果不指定则默认使用被修饰的属性名或方法名作为输入属性名称。 通过@Input指定需要的输入属性后,即可在父组件中使用属性绑定[]对该输入属性进行绑定。

  @Input()
  inputValue1: string;
  _inputValue2: string;

  constructor() {
  }

  @Input('inputValue1')
  set inputValue2(value: string) {
    this._inputValue2 = value;
  }
  ...
  <app-child [inputValue1]="value"></app-child>

在属性上使用@Input只能被动接收传入参数并直接交由模板进行显示,而不能对传入参数进行预处理。 在方法上使用@Input可以在接收到传入参数后对参数进行预处理后再交由模板进行显示。 根据上文中组件生命周期图可以知道,传入参数的赋值是在constructor之后ngOnChanges之前,所以我们不能在constructor中对传入参数进行任何处理。

  @Input()
  inputValue1: string;
  _inputValue2: string;

  constructor() {
    this.inputValue1.concat('test'); //报错,inputValue1为undefined
  }

  @Input('inputValue1')
  set inputValue2(value: string) {
    this._inputValue2 = value.concat("test");//无问题
  }

@Output()

@Output规定的输出事件,其与@Input规定的数据流向相反,是由子组件传递至父组件,当使用@Output规定了自身提供的事件输出之后,就可以父组件中使用事件绑定来监听相应的事件。 @Output所定义的输出属性必须使用EventEmitter来实现。

  @Output()
  outputEvent: EventEmitter<any> = new EventEmitter<any>();
EventEmitter

EventEmitter是用于指令和组件以同步或异步方式发出自定义事件,并通过订阅实例来注册这些事件的处理程序。也就是说,它是一个管理一系列订阅者并向其发布事件的对象。 EventEmitter与Observable类似,但多一个emit方法,其常用方法如下:

  • emit() 用于发出包含给定值得事件。
  • subscribe() 注册此实例发出的事件的处理程序。
  @Output()
  outputEvent: EventEmitter<any> = new EventEmitter<any>();

  constructor() {
  }

  output(value: string) {
    this.outputEvent.emit(value);
  }

也就是说,EventEmitter是发布-订阅者的一个实现,emit是发布,subscribe是订阅。当在父子组件传值也就是@Output中使用时子组件使用emit发布事件,父组件使用subscribe订阅事件,通过此方式以事件形式完成子组件向父组件传值。