1. 父元件至子元件: @Input
子元件内的@Input
装饰器定义属性,父元件再透过属性繫结(Property Binding)
将资料传递给子元件。ChildComponent
import { Component, OnInit, Input } from '@angular/core';@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.scss']})export class ChildComponent implements OnInit { @Input() bankName: string; @Input() accountId: number; constructor() { } ngOnInit(): void { }}
ChildComponent HTML
<p>{{ bankName }}</p><p>{{ accountId }}</p>
ParentComponent
import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-parent', templateUrl: './parent.component.html', styleUrls: ['./parent.component.scss']})export class ParentComponent implements OnInit { bankName = 'ABC Bank'; accountId = 123456789; constructor() { } ngOnInit(): void { }}
ParentComponent HTML
<app-child [bankName]="bankName" [accountId]="accountId"></app-child>
结果:
2. 子元件至父元件:@Output
子元件藉由@Output
装饰器定义属性,该属性为EventEmitter
实体,可以设定要传送的资料型别,透过事件繫结(Event Binding)
通知父元件有事件发生。
ChildComponent
import { Component, OnInit, EventEmitter, Output } from '@angular/core';@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.scss']})export class ChildComponent implements OnInit { @Output() counterEvt = new EventEmitter<string>(); constructor() { } ngOnInit(): void { } counterChange(cal: string) { this.counterEvt.emit(cal); }}
ChildComponent HTML
<button (click)="counterChange('add')">加</button><button (click)="counterChange('minus')">减</button>
ParentComponent
import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-parent', templateUrl: './parent.component.html', styleUrls: ['./parent.component.scss']})export class ParentComponent implements OnInit { counter = 0; constructor() { } ngOnInit(): void { } counterCal(cal: string) { cal === 'add' ? this.counter++ : this.counter--; }}
ParentComponent HTML
<p>counter : {{ counter }}</p><app-child (counterEvt)="counterCal($event)"></app-child>
基于元件的维护性与程式的逻辑性,我们只会在子元件送出事件资料(加或减),当父元件接收到事件资料后,真正的实作应该要在父元件完成,而不是在子元件完成。
结果:
3. 子元件至父元件: @ViewChild
以上的方式可以让父元件-子元件传递资料,但还是有限制:父元件无法存取子元件的属性与方法。
这时可以使用‵@ViewChild
在父元件建立子元件的实体,让父元件获得子元件的属性与方法。
实作一个简单的timer。
ChildComponent
import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-child', templateUrl: './child.component.html', styleUrls: ['./child.component.scss']})export class ChildComponent implements OnInit { seconds = 0; timer: any; constructor() { } ngOnInit(): void { } start() { this.timer = setInterval(() => { this.seconds++; }, 1000); } stop() { clearInterval(this.timer); }}
所有计时逻辑都在子元件中。
ParentComponent
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';import { ChildComponent } from '../child/child.component';@Component({ selector: 'app-parent', templateUrl: './parent.component.html', styleUrls: ['./parent.component.scss']})export class ParentComponent implements OnInit, AfterViewInit { @ViewChild(ChildComponent) childComponent: ChildComponent; constructor() { } ngOnInit(): void { } seconds() { return 0; } ngAfterViewInit() { setTimeout(() => { this.seconds = () => this.childComponent.seconds; }, 1000); } onStart() { this.childComponent.start(); } onStop() { this.childComponent.stop(); }}
父元件显示秒数。
整个生命週期中,必须在ngAfterViewInit
才能取得子元件的实体。
由于我们目前处于Development Mode
,为了避免ExpressionChangedAfterItHasBeenCheckedError
的问题,所以在ngAfterViewInit
使用setTimeout
,详细说明之后会另开文章。
ParentComponent HTML
<button (click)="onStart()">start</button><button (click)="onStop()">stop</button><p>{{ seconds() }}</p><app-child></app-child>
结果:
4. 不相关的元件:service
A元件跟B元件,是两个不相关的原件,可以藉由service
作为他们之间沟通的媒介。
实作在A元件输入讯息,在B元件同步显示的功能:DataService
import { Injectable } from '@angular/core';import { Subject } from 'rxjs';@Injectable({ providedIn: 'root'})export class DataService { msgContent = new Subject<string>(); constructor() { } setMessage(value: string) { this.msgContent.next(value); } getMessage() { return this.msgContent.asObservable(); }}
AComponent
import { Component, OnInit } from '@angular/core';import { DataService } from '../data.service';@Component({ selector: 'app-a', templateUrl: './a.component.html', styleUrls: ['./a.component.scss']})export class AComponent implements OnInit { constructor(private dataSvc: DataService) { } ngOnInit(): void { } onMsgChange(message: string) { this.dataSvc.setMessage(message); }}
AComponent HTML
input message : <input (keyup)="onMsgChange($event.target.value)">
BComponent
import { Component, OnInit, OnDestroy } from '@angular/core';import { DataService } from '../data.service';import { Subscription } from 'rxjs';@Component({ selector: 'app-b', templateUrl: './b.component.html', styleUrls: ['./b.component.scss']})export class BComponent implements OnInit, OnDestroy { message: string; subscription: Subscription; constructor(private dataSvc: DataService) { } ngOnInit(): void { this.subscription = this.dataSvc.getMessage().subscribe(val => { this.message = val; }); } ngOnDestroy(): void { this.subscription.unsubscribe(); }}
BComponent HTML
here is message : <span>{{ message }}</span>
AppComponent HTML
<app-a></app-a><p></p><app-b></app-b>
结果:
参考来源:
Angular:如何在多个组件之间通信