组件通信 #
一、组件通信概述 #
Angular组件间通信主要有以下几种方式:
text
┌─────────────────────────────────────────────────────┐
│ 组件通信方式 │
├─────────────────────────────────────────────────────┤
│ 父→子 @Input装饰器 │
│ 子→父 @Output装饰器 + EventEmitter │
│ 子→父 @ViewChild装饰器 │
│ 兄弟 通过共同父组件 │
│ 任意 共享服务 + RxJS │
│ 任意 状态管理(NgRx) │
└─────────────────────────────────────────────────────┘
二、父组件向子组件通信 #
2.1 @Input装饰器 #
typescript
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<div class="child">
<h3>{{ title }}</h3>
<p>用户名: {{ user.name }}</p>
<p>年龄: {{ user.age }}</p>
</div>
`
})
export class ChildComponent {
@Input() title: string = '';
@Input() user: User;
}
2.2 父组件传递数据 #
typescript
@Component({
selector: 'app-parent',
template: `
<app-child
[title]="'用户信息'"
[user]="currentUser">
</app-child>
`
})
export class ParentComponent {
currentUser: User = {
name: 'John',
age: 25
};
}
2.3 输入属性setter #
typescript
@Component({
selector: 'app-child',
template: `<p>{{ formattedName }}</p>`
})
export class ChildComponent {
private _name: string = '';
@Input()
set name(value: string) {
this._name = value.trim().toUpperCase();
}
get name(): string {
return this._name;
}
formattedName = '';
ngOnChanges() {
this.formattedName = `Hello, ${this._name}!`;
}
}
2.4 输入属性必填 #
typescript
import { Component, Input, required } from '@angular/core';
@Component({
selector: 'app-child',
template: `<p>{{ title }}</p>`
})
export class ChildComponent {
@Input({ required: true }) title: string;
@Input() subtitle?: string;
}
三、子组件向父组件通信 #
3.1 @Output装饰器 #
typescript
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<button (click)="onSelect()">选择</button>
<button (click)="onDelete()">删除</button>
`
})
export class ChildComponent {
@Output() select = new EventEmitter<string>();
@Output() delete = new EventEmitter<number>();
onSelect() {
this.select.emit('selected-item');
}
onDelete() {
this.delete.emit(123);
}
}
3.2 父组件监听事件 #
typescript
@Component({
selector: 'app-parent',
template: `
<app-child
(select)="onSelect($event)"
(delete)="onDelete($event)">
</app-child>
`
})
export class ParentComponent {
onSelect(item: string) {
console.log('选中:', item);
}
onDelete(id: number) {
console.log('删除:', id);
}
}
3.3 传递复杂数据 #
typescript
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<button (click)="onEdit()">编辑</button>
<button (click)="onDelete()">删除</button>
</div>
`
})
export class UserCardComponent {
@Input() user: User;
@Output() edit = new EventEmitter<User>();
@Output() delete = new EventEmitter<{ id: number; name: string }>();
onEdit() {
this.edit.emit(this.user);
}
onDelete() {
this.delete.emit({
id: this.user.id,
name: this.user.name
});
}
}
四、使用@ViewChild #
4.1 访问子组件 #
typescript
import { Component, ViewChild, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child #child></app-child>
<button (click)="callChildMethod()">调用子组件方法</button>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('child') childComponent: ChildComponent;
ngAfterViewInit() {
console.log(this.childComponent);
}
callChildMethod() {
this.childComponent.doSomething();
}
}
4.2 访问子组件属性 #
typescript
@Component({
selector: 'app-child',
template: `<p>{{ message }}</p>`
})
export class ChildComponent {
message = 'Hello from child';
doSomething() {
console.log('Child method called');
}
}
@Component({
selector: 'app-parent',
template: `
<app-child></app-child>
<p>子组件消息: {{ childMessage }}</p>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild(ChildComponent) child: ChildComponent;
childMessage: string;
ngAfterViewInit() {
this.childMessage = this.child.message;
}
}
4.3 @ViewChildren #
typescript
import { Component, ViewChildren, QueryList } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child *ngFor="let item of items" [item]="item"></app-child>
<button (click)="logAllChildren()">打印所有子组件</button>
`
})
export class ParentComponent {
items = ['Item 1', 'Item 2', 'Item 3'];
@ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
logAllChildren() {
this.children.forEach((child, index) => {
console.log(`Child ${index}:`, child);
});
}
}
五、兄弟组件通信 #
5.1 通过共同父组件 #
typescript
@Component({
selector: 'app-sibling-a',
template: `
<button (click)="sendData()">发送数据给兄弟</button>
`
})
export class SiblingAComponent {
@Output() dataSent = new EventEmitter<string>();
sendData() {
this.dataSent.emit('Hello from A');
}
}
@Component({
selector: 'app-sibling-b',
template: `<p>收到: {{ receivedData }}</p>`
})
export class SiblingBComponent {
@Input() receivedData: string;
}
@Component({
selector: 'app-parent',
template: `
<app-sibling-a (dataSent)="onDataSent($event)"></app-sibling-a>
<app-sibling-b [receivedData]="sharedData"></app-sibling-b>
`
})
export class ParentComponent {
sharedData: string;
onDataSent(data: string) {
this.sharedData = data;
}
}
六、使用服务通信 #
6.1 创建共享服务 #
typescript
import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CommunicationService {
private messageSubject = new Subject<string>();
sendMessage(message: string) {
this.messageSubject.next(message);
}
getMessage(): Observable<string> {
return this.messageSubject.asObservable();
}
}
6.2 组件A发送消息 #
typescript
@Component({
selector: 'app-component-a',
template: `
<input [(ngModel)]="message" />
<button (click)="send()">发送</button>
`
})
export class ComponentA {
message = '';
constructor(private commService: CommunicationService) {}
send() {
this.commService.sendMessage(this.message);
this.message = '';
}
}
6.3 组件B接收消息 #
typescript
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-component-b',
template: `<p>收到消息: {{ receivedMessage }}</p>`
})
export class ComponentB implements OnInit, OnDestroy {
receivedMessage = '';
private subscription: Subscription;
constructor(private commService: CommunicationService) {}
ngOnInit() {
this.subscription = this.commService.getMessage().subscribe(message => {
this.receivedMessage = message;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
七、使用BehaviorSubject #
7.1 创建状态服务 #
typescript
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
interface AppState {
user: User | null;
isLoggedIn: boolean;
}
@Injectable({
providedIn: 'root'
})
export class StateService {
private stateSubject = new BehaviorSubject<AppState>({
user: null,
isLoggedIn: false
});
state$: Observable<AppState> = this.stateSubject.asObservable();
get currentState(): AppState {
return this.stateSubject.getValue();
}
updateUser(user: User | null) {
const state = this.currentState;
this.stateSubject.next({
...state,
user,
isLoggedIn: !!user
});
}
login(user: User) {
this.updateUser(user);
}
logout() {
this.updateUser(null);
}
}
7.2 使用状态服务 #
typescript
@Component({
selector: 'app-header',
template: `
<div *ngIf="(state$ | async)?.isLoggedIn; else login">
欢迎, {{ (state$ | async)?.user?.name }}
<button (click)="logout()">退出</button>
</div>
<ng-template #login>
<button (click)="login()">登录</button>
</ng-template>
`
})
export class HeaderComponent {
state$ = this.stateService.state$;
constructor(private stateService: StateService) {}
login() {
this.stateService.login({ id: 1, name: 'John' });
}
logout() {
this.stateService.logout();
}
}
八、使用Signal(Angular 17+) #
8.1 创建Signal服务 #
typescript
import { Injectable, signal, computed } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CounterService {
private countSignal = signal(0);
readonly count = this.countSignal.asReadonly();
readonly doubleCount = computed(() => this.countSignal() * 2);
increment() {
this.countSignal.update(v => v + 1);
}
decrement() {
this.countSignal.update(v => v - 1);
}
reset() {
this.countSignal.set(0);
}
}
8.2 在组件中使用 #
typescript
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<p>Double: {{ doubleCount() }}</p>
<button (click)="increment()">+1</button>
<button (click)="decrement()">-1</button>
`
})
export class CounterComponent {
private countSignal = signal(0);
count = this.countSignal.asReadonly();
doubleCount = computed(() => this.countSignal() * 2);
increment() {
this.countSignal.update(v => v + 1);
}
decrement() {
this.countSignal.update(v => v - 1);
}
}
九、通信方式选择 #
| 场景 | 推荐方式 |
|---|---|
| 父→子 | @Input |
| 子→父 | @Output + EventEmitter |
| 访问子组件 | @ViewChild |
| 兄弟组件 | 共享服务或父组件中转 |
| 跨层级 | 共享服务 + RxJS |
| 全局状态 | NgRx或Signal服务 |
| 简单状态 | Signal(Angular 17+) |
十、最佳实践 #
10.1 使用不可变数据 #
typescript
@Component({
selector: 'app-parent',
template: `
<app-child [items]="items"></app-child>
<button (click)="addItem()">添加</button>
`
})
export class ParentComponent {
items: string[] = ['Item 1', 'Item 2'];
addItem() {
this.items = [...this.items, `Item ${this.items.length + 1}`];
}
}
10.2 使用OnPush策略 #
typescript
@Component({
selector: 'app-child',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
@Input() user: User;
}
10.3 及时取消订阅 #
typescript
@Component({
selector: 'app-example',
template: `...`
})
export class ExampleComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.service.getData()
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
console.log(data);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
十一、总结 #
| 通信方式 | 适用场景 | 特点 |
|---|---|---|
| @Input | 父→子 | 简单直接 |
| @Output | 子→父 | 事件驱动 |
| @ViewChild | 父访问子 | 直接访问 |
| 服务 | 任意组件 | 解耦灵活 |
| Signal | 简单状态 | 响应式、高性能 |
下一步:组件生命周期
最后更新:2026-03-26