组件通信 #

一、组件通信概述 #

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