# 在父子指令及组件之间共享数据link

Angular 中的一个常见模式就是在父组件和一个或多个子组件之间共享数据。可以用 @Input()@Output()来实现这个模式。

本章包含代码片段的可工作实例参阅现场演练/ 下载范例

考虑以下层次结构:

<parent-component>
  <child-component></child-component>
</parent-component>

&lt;parent-component&gt;充当了 &lt;child-component&gt;的上下文。

@Input()@Output()为子组件提供了一种与其父组件通信的方法。@Input()允许父组件更新子组件中的数据。相反,@Output()允许子组件向父组件发送数据。

# 把数据发送到子组件

子组件或指令中的 @Input()装饰器表示该属性可以从其父组件中获取值。

Input data flow diagram of data flowing from parent to child 要使用 @Input(),就必须对父组件和子组件进行配置。

# 配置子组件

要使用 @Input()装饰器,首先要导入 Input,然后用 @Input()装饰该属性,如下例所示。

src/app/item-detail/item-detail.component.ts

import { Component, Input } from '@angular/core'; // First, import Input
export class ItemDetailComponent {
  @Input() item = ''; // decorate the property with @Input()
}

在这个例子中,@Input()会修饰属性 item,它的类型为 string,但 @Input()属性可以是任意类型,比如 numberstringbooleanobjectitem的值来自父组件。

接下来,在子组件模板中添加以下内容:

src/app/item-detail/item-detail.component.html

<p>
  Today's item: {{item}}
</p>

# 配置父组件

下一步是在父组件的模板中绑定该属性。在这个例子中,父组件模板是 app.component.html

  • 使用子组件的 selector (&lt;app-item-detail&gt;) 作为父组件模板中的指令。

  • 使用属性绑定把子组件的 item属性绑定到父组件的 currentItem属性上。

src/app/app.component.html

<app-item-detail [item]="currentItem"></app-item-detail>
  • 在父组件类中,为 currentItem指定一个值:

src/app/app.component.ts

export class AppComponent {
  currentItem = 'Television';
}

通过 @Input(),Angular 把 currentItem的值传给子组件,以便 item渲染为 Television

下图展示了这种结构:

Property binding diagram of the target, item, in square brackets set to the source, currentItem, on the right of an equal sign 方括号 []中的目标就是子组件中用 @Input()装饰的那个属性。绑定源(等号的右边部分)则是父组件传给内嵌组件的数据。

# 监视 @Input()的变更

要想监视 @Input()属性的变化,可以用 Angular 的生命周期钩子OnChanges。更多详情和范例,请参阅生命周期钩子一章的 OnChanges 部分。

# 把数据发送到父组件

子组件或指令中的 @Output()装饰器允许数据从子组件传给父组件。

Output diagram of the data flow going from child to parent @Output()在子组件中标记了一个属性,作为数据从子组件传递到父组件的途径。

子组件使用 @Output()属性来引发事件,以通知父组件这一变化。为了引发事件,@Output()必须是 EventEmitter类型,它是 @angular/core中用来发出自定义事件的类。

下面的例子给出了如何在组件中设置 @Output(),来把数据从 HTML 的 &lt;input&gt;推送到父组件的数组中。

要使用 @Output(),就必须配置父组件和子组件。

# 配置子组件

下面的例子中有一个 &lt;input&gt;,用户可以输入一个值,然后点击一个引发事件 &lt;button&gt;然后,EventEmitter数据中继到父组件。

  • 在子组件类中导入 OutputEventEmitter

import { Output, EventEmitter } from '@angular/core';
  • 在组件类中,用 @Output()装饰一个属性。下面的例子中 newItemEvent这个 @Output()的类型为 EventEmitter,这意味着它是一个事件。

src/app/item-output/item-output.component.ts


@Output() newItemEvent = new EventEmitter<string>();

上述声明中的差异点如下:

装饰器部件 Declaration parts 详情 Details
@Output() 一个装饰器函数,它把该属性标记为数据从子组件进入父组件的一种途径。A decorator function marking the property as a way for data to go from the child to the parent.
newItemEvent 这个 @Output() 的名字。The name of the @Output().
EventEmitter<string&gt; 这个 @Output() 的类型。The @Output()'s type.
new EventEmitter<string>() 要求 Angular 创建一个新的事件发射器,它发出的数据是 string 类型的。

关于 EventEmitter的详细信息,请参阅 EventEmitter API 文档

  • 在同一个组件类中创建一个 addNewItem()方法:

src/app/item-output/item-output.component.ts

export class ItemOutputComponent {

  @Output() newItemEvent = new EventEmitter<string>();

  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}

addNewItem()函数使用 newItemEvent这个 @Output()来引发一个事件,该事件带有用户输入到 &lt;input&gt;中的值。

# 配置子组件的模板

子组件的模板有两个控件。第一个是带有模板引用变量#newItem&lt;input&gt;,用户可在其中输入条目名称。#newItem变量的 value属性存储了用户输入到 &lt;input&gt;中的值。

src/app/item-output/item-output.component.html

<label for="item-input">Add an item:</label>
<input type="text" id="item-input" #newItem>
<button type="button" (click)="addNewItem(newItem.value)">Add to parent's list</button>

第二个元素是带有 click事件绑定的 &lt;button&gt;元素。

(click)事件绑定到了子组件类中 addNewItem()方法。addNewItem()方法接受一个 #newItem.value属性的值作为参数。

# 配置父组件

此范例中的 AppComponent有一个 items列表,以及一个向数组中添加更多条目的方法。

src/app/app.component.ts

export class AppComponent {
  items = ['item1', 'item2', 'item3', 'item4'];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}

addItem()方法接受一个字符串形式的参数,然后把该字符串添加到 items数组中。

# 配置父组件的模板

  • 在父模板中,把父组件的方法绑定到子组件的事件上。

  • 把子组件选择器(&lt;app-item-output&gt;)放在父组件的模板 app.component.html中。

src/app/app.component.html

<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

事件绑定 (newItemEvent)='addItem($event)'会把子组件中的 newItemEvent事件连接到父组件的 addItem()方法。

$event中包含用户在子组件模板上的 &lt;input&gt;中键入的数据。

要了解 @Output()的工作方式,你可以把以下内容添加到父组件的模板中:

<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>

*ngFor会迭代 items数组中的条目。当你在子组件的 &lt;input&gt;中输入一个值并单击该按钮时,子组件就会发出该事件,而父组件的 addItem()方法会把这个值追加到其 items数组中,并且列表中会渲染出这个新条目。

# 同时使用 @Input()@Output()

可以在同一个子组件上使用 @Input()@Output(),范例如下:

src/app/app.component.html

<app-input-output
  [item]="currentItem"
  (deleteRequest)="crossOffItem($event)">
</app-input-output>

目标 item是子组件类中的一个 @Input()属性,它会从父组件的 currentItem属性中获取它的值。当你单击“删除”时,子组件就会引发一个事件 deleteRequest,它会作为父组件中 crossOffItem()方法的参数。

下图展示了子组件 &lt;app-input-output&gt;@Input()@Output()的各个部分。

Diagram of an input target and an output target each bound to a source. 这里的子选择器是 &lt;app-input-output&gt;,它所带的 itemdeleteRequest是子组件类中的 @Input()@Output()属性。而 currentItem属性和 crossOffItem()方法都位于父组件类中。

要想用“盒子里的香蕉” ()]语法来组合属性和事件绑定,参阅[双向绑定。

Last Updated: 5/13/2023, 10:57:08 AM